diff --git a/.editorconfig b/.editorconfig index f30d26b5..34c29a22 100644 --- a/.editorconfig +++ b/.editorconfig @@ -394,7 +394,7 @@ dotnet_diagnostic.IDE0078.severity = suggestion dotnet_diagnostic.IDE0080.severity = suggestion dotnet_diagnostic.IDE0082.severity = suggestion dotnet_diagnostic.IDE0083.severity = suggestion -dotnet_diagnostic.IDE0090.severity = suggestion +dotnet_diagnostic.IDE0090.severity = error dotnet_diagnostic.IDE0100.severity = suggestion dotnet_diagnostic.IDE0110.severity = suggestion dotnet_diagnostic.IDE0120.severity = suggestion diff --git a/Dat/FileParsing/LocoBinaryReader.cs b/Dat/FileParsing/LocoBinaryReader.cs index 2d15d1f7..588eebd9 100644 --- a/Dat/FileParsing/LocoBinaryReader.cs +++ b/Dat/FileParsing/LocoBinaryReader.cs @@ -1,6 +1,6 @@ using Dat.Converters; using Dat.Types; -using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Objects.Shared; using Definitions.ObjectModels.Objects.Sound; using Definitions.ObjectModels.Objects.Vehicle; @@ -331,7 +331,6 @@ public GearboxMotorSound ReadGearboxMotorSound() SpeedFrequencyFactor = ReadByte(), }; - public CargoOffset[][][] ReadCargoOffsets() { const int rotationSize = 4; diff --git a/Dat/FileParsing/LocoBinaryWriter.cs b/Dat/FileParsing/LocoBinaryWriter.cs index fc985925..2b95fae4 100644 --- a/Dat/FileParsing/LocoBinaryWriter.cs +++ b/Dat/FileParsing/LocoBinaryWriter.cs @@ -1,8 +1,8 @@ using Common; using Dat.Converters; using Dat.Types; -using Definitions.ObjectModels.Objects.Building; using Definitions.ObjectModels.Objects.Shared; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Objects.Sound; using Definitions.ObjectModels.Objects.Vehicle; using Definitions.ObjectModels.Types; diff --git a/Dat/FileParsing/SawyerStreamReader.cs b/Dat/FileParsing/SawyerStreamReader.cs index a1966406..d69926b7 100644 --- a/Dat/FileParsing/SawyerStreamReader.cs +++ b/Dat/FileParsing/SawyerStreamReader.cs @@ -7,6 +7,7 @@ using Definitions.ObjectModels; using Definitions.ObjectModels.Objects.Sound; using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; using System.Text; namespace Dat.FileParsing; @@ -137,9 +138,9 @@ static void ValidateLocoStruct(S5Header s5Header, ILocoStruct locoStruct, ILogge } } - if (!locoStruct.Validate()) + foreach (var failedValidation in locoStruct.Validate(new ValidationContext(locoStruct))) { - warnings.Add($"\"{s5Header.Name}\" failed validation"); + warnings.Add($"\"{s5Header.Name}\" failed validation: {failedValidation}"); } if (warnings.Count != 0) @@ -252,6 +253,8 @@ public static (G1Header Header, List Table) ReadImageTable(Loco var imageData = br.ReadToEnd(); g1Header.ImageData = [.. imageData]; + var graphicsElements = new List(); + // set image data for (var i = 0; i < g1Header.NumEntries; ++i) { @@ -272,15 +275,19 @@ public static (G1Header Header, List Table) ReadImageTable(Loco currElement.ImageData = [.. imageData[(int)currElement.Offset..(int)nextOffset]]; } - // if rleCompressed, uncompress it, except if the duplicate-previous flag is also set - by the current code here, the previous - // image (which was also compressed) is now uncompressed, so we don't need do double-uncompress it. + // if rleCompressed, decompress it, except if the duplicate-previous flag is also set - by the current code here, the previous + // image (which was also compressed) is now decompressed, so we don't need do double-decompress it. if (currElement.Flags.HasFlag(DatG1ElementFlags.IsRLECompressed) && !currElement.Flags.HasFlag(DatG1ElementFlags.DuplicatePrevious)) { currElement.ImageData = DecodeRLEImageData(currElement); } + + var ge = currElement.Convert(); + ge.Name = DefaultImageTableNameProvider.GetImageName(i); + graphicsElements.Add(ge); } - return (g1Header, g1Element32s.Select(x => x.Convert()).ToList()); + return (g1Header, graphicsElements); } static uint GetNextNonDuplicateOffset(List g1Element32s, int i, uint imageDateLength) diff --git a/Dat/FileParsing/SawyerStreamWriter.cs b/Dat/FileParsing/SawyerStreamWriter.cs index 972d85f4..ab010809 100644 --- a/Dat/FileParsing/SawyerStreamWriter.cs +++ b/Dat/FileParsing/SawyerStreamWriter.cs @@ -7,6 +7,7 @@ using Definitions.ObjectModels; using Definitions.ObjectModels.Objects.Sound; using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; using System.Text; namespace Dat.FileParsing; @@ -490,9 +491,10 @@ public static void WriteLocoObject(Stream stream, LocoObject obj) public static MemoryStream WriteLocoObject(string objName, ObjectType objectType, ObjectSource objectSource, SawyerEncoding encoding, ILogger logger, LocoObject obj, bool allowWritingAsVanilla) { - if (!obj.Object.Validate()) + var validationResults = new List(); + if (!Validator.TryValidateObject(obj.Object, new ValidationContext(obj.Object), validationResults)) { - throw new ArgumentException($"{objName} was invalid", nameof(obj)); + throw new ArgumentException($"{objName} was invalid: {string.Join(", ", validationResults.Select(r => r.ErrorMessage))}", nameof(obj)); } using var rawObjStream = new MemoryStream(); @@ -615,7 +617,7 @@ public static void SaveG1(string filename, G1Dat g1) { using (var fs = File.OpenWrite(filename)) { - WriteImageTable(fs, g1.GraphicsElements); + WriteImageTable(fs, g1.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/AirportObjectLoader.cs b/Dat/Loaders/AirportObjectLoader.cs index ee2d8a52..2d608aea 100644 --- a/Dat/Loaders/AirportObjectLoader.cs +++ b/Dat/Loaders/AirportObjectLoader.cs @@ -18,13 +18,15 @@ public static class Constants internal static class StructSizes { - public const int Dat = 0xBA; public const int BuildingPartAnimation = 0x02; public const int AirportBuilding = 0x04; public const int MovementNode = 0x08; public const int MovementEdge = 0x0C; } + public static ObjectType ObjectType => ObjectType.Airport; + public static DatObjectType DatObjectType => DatObjectType.Airport; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -32,8 +34,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new AirportObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -64,26 +64,29 @@ public static LocoObject Load(Stream stream) model.var_B6 = br.ReadBytes(0xBA - 0xB6); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Airport), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model, numBuildingParts, numBuildingVariations, numMovementNodes, numMovementEdges); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Airport, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } private static void LoadVariable(LocoBinaryReader br, AirportObject model, int numBuildingParts, int numBuildingVariations, byte numMovementNodes, byte numMovementEdges) { - model.BuildingHeights = br.ReadBuildingHeights(numBuildingParts); - model.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts); - model.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations); + model.BuildingComponents.BuildingHeights = br.ReadBuildingHeights(numBuildingParts); + model.BuildingComponents.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts); + model.BuildingComponents.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations); // building positions while (br.PeekByte() != LocoConstants.Terminator) @@ -97,6 +100,7 @@ private static void LoadVariable(LocoBinaryReader br, AirportObject model, int n }; model.BuildingPositions.Add(ab); } + br.SkipTerminator(); // movement nodes @@ -146,8 +150,8 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // Image, not part of object definition bw.WriteEmptyImageId(); // Image offset, not part of object definition bw.Write(model.AllowedPlaneTypes); - bw.Write((uint8_t)model.BuildingHeights.Count); - bw.Write((uint8_t)model.BuildingVariations.Count); + bw.Write((uint8_t)model.BuildingComponents.BuildingHeights.Count); + bw.Write((uint8_t)model.BuildingComponents.BuildingVariations.Count); bw.WriteEmptyPointer(); // BuildingHeights bw.WriteEmptyPointer(); // BuildingAnimations bw.WriteEmptyPointer(Constants.BuildingVariationCount); // BuildingVariations @@ -166,7 +170,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.var_B6); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -175,15 +179,15 @@ public static void Save(Stream stream, LocoObject obj) SaveVariable(bw, model); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } private static void SaveVariable(LocoBinaryWriter bw, AirportObject model) { - bw.Write(model.BuildingHeights); - bw.Write(model.BuildingAnimations); - bw.Write(model.BuildingVariations); + bw.Write(model.BuildingComponents.BuildingHeights); + bw.Write(model.BuildingComponents.BuildingAnimations); + bw.Write(model.BuildingComponents.BuildingVariations); // positions foreach (var x in model.BuildingPositions) @@ -193,6 +197,7 @@ private static void SaveVariable(LocoBinaryWriter bw, AirportObject model) bw.Write(x.X); bw.Write(x.Y); } + bw.WriteTerminator(); // movement nodes diff --git a/Dat/Loaders/BridgeObjectLoader.cs b/Dat/Loaders/BridgeObjectLoader.cs index c705f59c..3fd0e68c 100644 --- a/Dat/Loaders/BridgeObjectLoader.cs +++ b/Dat/Loaders/BridgeObjectLoader.cs @@ -16,10 +16,8 @@ public static class Constants public const int MaxNumRoadMods = 7; } - public static class StructSizes - { - public const int Dat = 0x2C; - } + public static ObjectType ObjectType => ObjectType.Bridge; + public static DatObjectType DatObjectType => DatObjectType.Bridge; public static LocoObject Load(Stream stream) { @@ -28,8 +26,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new BridgeObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -54,19 +50,22 @@ public static LocoObject Load(Stream stream) model.DesignedYear = br.ReadUInt16(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Bridge), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable model.CompatibleTrackObjects = br.ReadS5HeaderList(compatibleTrackCount); model.CompatibleRoadObjects = br.ReadS5HeaderList(compatibleRoadCount); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; - return new LocoObject(ObjectType.Bridge, model, stringTable, imageTable); + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); + + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -99,7 +98,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.DesignedYear); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -109,7 +108,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteS5HeaderList(model.CompatibleRoadObjects); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } @@ -150,7 +149,6 @@ internal enum DatBridgeObjectFlags : uint8_t None = 0, HasRoof = 1 << 0, } - } static class BridgeFlagsConverter diff --git a/Dat/Loaders/BuildingObjectLoader.cs b/Dat/Loaders/BuildingObjectLoader.cs index b918fad5..32e1caed 100644 --- a/Dat/Loaders/BuildingObjectLoader.cs +++ b/Dat/Loaders/BuildingObjectLoader.cs @@ -21,10 +21,17 @@ public static class Constants public static class StructSizes { - public const int Dat = 0xBE; public const int BuildingPartAnimation = 0x02; } + public static class ImageGroups + { + public const int Base = 4; + } + + public static ObjectType ObjectType => ObjectType.Building; + public static DatObjectType DatObjectType => DatObjectType.Building; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -32,8 +39,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new BuildingObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -67,26 +72,29 @@ public static LocoObject Load(Stream stream) br.SkipPointer(Constants.MaxElevatorHeightSequences); // ElevatorHeightSequences, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Building), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model, numBuildingParts, numBuildingVariations, numElevatorSequences); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Building, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } private static void LoadVariable(LocoBinaryReader br, BuildingObject model, byte numBuildingParts, byte numBuildingVariations, byte numElevatorSequences) { - model.BuildingHeights = br.ReadBuildingHeights(numBuildingParts); - model.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts); - model.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations); + model.BuildingComponents.BuildingHeights = br.ReadBuildingHeights(numBuildingParts); + model.BuildingComponents.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts); + model.BuildingComponents.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations); model.ProducedCargo = br.ReadS5HeaderList(Constants.MaxProducedCargoType); model.RequiredCargo = br.ReadS5HeaderList(Constants.MaxRequiredCargoType); @@ -108,8 +116,8 @@ public static void Save(Stream stream, LocoObject obj) { bw.WriteEmptyStringId(); // Name offset, not part of object definition bw.WriteEmptyImageId(); // Image offset, not part of object definition - bw.Write((uint8_t)model.BuildingAnimations.Count); // NumBuildingParts - bw.Write((uint8_t)model.BuildingVariations.Count); + bw.Write((uint8_t)model.BuildingComponents.BuildingAnimations.Count); // NumBuildingParts + bw.Write((uint8_t)model.BuildingComponents.BuildingVariations.Count); bw.WriteEmptyPointer(); bw.WriteEmptyPointer(); bw.WriteEmptyPointer(Constants.BuildingVariationCount); @@ -137,7 +145,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyPointer(Constants.MaxElevatorHeightSequences); // ElevatorHeightSequences, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -146,15 +154,15 @@ public static void Save(Stream stream, LocoObject obj) SaveVariable(model, bw); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } private static void SaveVariable(BuildingObject model, LocoBinaryWriter bw) { - bw.Write(model.BuildingHeights); - bw.Write(model.BuildingAnimations); - bw.Write(model.BuildingVariations); + bw.Write(model.BuildingComponents.BuildingHeights); + bw.Write(model.BuildingComponents.BuildingAnimations); + bw.Write(model.BuildingComponents.BuildingVariations); bw.WriteS5HeaderList(model.ProducedCargo, Constants.MaxProducedCargoType); bw.WriteS5HeaderList(model.RequiredCargo, Constants.MaxRequiredCargoType); diff --git a/Dat/Loaders/CargoObjectLoader.cs b/Dat/Loaders/CargoObjectLoader.cs index 6a9ea950..b3412616 100644 --- a/Dat/Loaders/CargoObjectLoader.cs +++ b/Dat/Loaders/CargoObjectLoader.cs @@ -8,13 +8,8 @@ namespace Dat.Loaders; public abstract class CargoObjectLoader : IDatObjectLoader { - public static class Constants - { } - - public static class StructSizes - { - public const int Dat = 0x1F; - } + public static ObjectType ObjectType => ObjectType.Cargo; + public static DatObjectType DatObjectType => DatObjectType.Cargo; public static LocoObject Load(Stream stream) { @@ -23,8 +18,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new CargoObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -47,18 +40,21 @@ public static LocoObject Load(Stream stream) model.UnitSize = br.ReadByte(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Cargo), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Cargo, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -89,7 +85,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.UnitSize); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -98,7 +94,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/CliffEdgeObjectLoader.cs b/Dat/Loaders/CliffEdgeObjectLoader.cs index 4dc30b13..2e475f29 100644 --- a/Dat/Loaders/CliffEdgeObjectLoader.cs +++ b/Dat/Loaders/CliffEdgeObjectLoader.cs @@ -3,42 +3,39 @@ using Definitions.ObjectModels; using Definitions.ObjectModels.Objects.CliffEdge; using Definitions.ObjectModels.Types; -using System.ComponentModel; namespace Dat.Loaders; public abstract class CliffEdgeObjectLoader : IDatObjectLoader { - public static class StructSizes - { - public const int DatStructSize = 0x06; - } + public static ObjectType ObjectType => ObjectType.CliffEdge; + public static DatObjectType DatObjectType => DatObjectType.CliffEdge; public static LocoObject Load(Stream stream) { using (var br = new LocoBinaryReader(stream)) { var model = new CliffEdgeObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition br.SkipImageId(); // Image offset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, StructSizes.DatStructSize, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.CliffEdge), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.CliffEdge, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -50,7 +47,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // Image offset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, StructSizes.DatStructSize, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -59,18 +56,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } - -[TypeConverter(typeof(ExpandableObjectConverter))] -[LocoStructSize(0x06)] -[LocoStructType(DatObjectType.CliffEdge)] -internal record DatCliffEdgeObject( - [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [property: LocoStructOffset(0x02), LocoString, Browsable(false)] image_id Image - ) -{ - public bool Validate() => true; -} diff --git a/Dat/Loaders/ClimateObjectLoader.cs b/Dat/Loaders/ClimateObjectLoader.cs index 8cd0d789..92ccc8df 100644 --- a/Dat/Loaders/ClimateObjectLoader.cs +++ b/Dat/Loaders/ClimateObjectLoader.cs @@ -14,10 +14,8 @@ public static class Constants public const int Seasons = 4; } - public static class StructSizes - { - public const int Dat = 0x0A; - } + public static ObjectType ObjectType => ObjectType.Climate; + public static DatObjectType DatObjectType => DatObjectType.Climate; public static LocoObject Load(Stream stream) { @@ -26,8 +24,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new ClimateObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -41,18 +37,19 @@ public static LocoObject Load(Stream stream) _ = br.ReadByte(); // pad // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Climate), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + // N/A but Climate has an empty image table for some reason + _ = SawyerStreamReader.ReadImageTable(br).Table; - return new LocoObject(ObjectType.Climate, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable); } } @@ -74,7 +71,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write((uint8_t)0); // pad // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -83,7 +80,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, new ImageTable().GraphicsElements); } } } diff --git a/Dat/Loaders/CompetitorObjectLoader.cs b/Dat/Loaders/CompetitorObjectLoader.cs index 9b859b6b..a4c3aec2 100644 --- a/Dat/Loaders/CompetitorObjectLoader.cs +++ b/Dat/Loaders/CompetitorObjectLoader.cs @@ -3,8 +3,6 @@ using Definitions.ObjectModels; using Definitions.ObjectModels.Objects.Competitor; using Definitions.ObjectModels.Types; -using System.ComponentModel; -using static Dat.Loaders.CompetitorObjectLoader; namespace Dat.Loaders; @@ -15,10 +13,8 @@ public static class Constants public const int ImagesLength = 9; } - public static class StructSizes - { - public const int Dat = 0x38; - } + public static ObjectType ObjectType => ObjectType.Competitor; + public static DatObjectType DatObjectType => DatObjectType.Competitor; public static LocoObject Load(Stream stream) { @@ -27,8 +23,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new CompetitorObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // First name offset, not part of object definition @@ -43,18 +37,21 @@ public static LocoObject Load(Stream stream) model.var_37 = br.ReadByte(); // unused // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Competitor), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; - return new LocoObject(ObjectType.Competitor, model, stringTable, imageTable); + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); + + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -77,7 +74,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.var_37); // unused // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -86,7 +83,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } @@ -140,20 +137,3 @@ internal enum DatCompetitorPlaystyle : uint32_t unk12 = 1 << 12, } } - -[TypeConverter(typeof(ExpandableObjectConverter))] -[LocoStructSize(0x38)] -[LocoStructType(DatObjectType.Competitor)] -internal record DatCompetitorObject( - [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id FullName, - [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id LastName, - [property: LocoStructOffset(0x04)] DatCompetitorNamePrefix AvailableNamePrefixes, // bitset - [property: LocoStructOffset(0x08)] DatCompetitorPlaystyle AvailablePlaystyles, // bitset - [property: LocoStructOffset(0x0C)] DatCompetitorEmotion Emotions, // bitset - [property: LocoStructOffset(0x10), Browsable(false), LocoArrayLength(CompetitorObjectLoader.Constants.ImagesLength)] image_id[] Images, - [property: LocoStructOffset(0x34)] uint8_t Intelligence, - [property: LocoStructOffset(0x35)] uint8_t Aggressiveness, - [property: LocoStructOffset(0x36)] uint8_t Competitiveness, - [property: LocoStructOffset(0x37), LocoPropertyMaybeUnused] uint8_t var_37 - ) -{ } diff --git a/Dat/Loaders/CurrencyObjectLoader.cs b/Dat/Loaders/CurrencyObjectLoader.cs index 44e680a3..16ed729b 100644 --- a/Dat/Loaders/CurrencyObjectLoader.cs +++ b/Dat/Loaders/CurrencyObjectLoader.cs @@ -4,6 +4,7 @@ using Definitions.ObjectModels.Objects.Currency; using Definitions.ObjectModels.Types; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace Dat.Loaders; @@ -17,6 +18,9 @@ public static class StructSizes public const int Dat = 0x0C; } + public static ObjectType ObjectType => ObjectType.Currency; + public static DatObjectType DatObjectType => DatObjectType.Currency; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -24,8 +28,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new CurrencyObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -36,18 +38,21 @@ public static LocoObject Load(Stream stream) model.Factor = br.ReadByte(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Currency), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Currency, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -66,7 +71,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.Factor); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -75,9 +80,11 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } + + public bool TryGetImageName(int id, [MaybeNullWhen(false)] out string value) => throw new NotImplementedException(); } [TypeConverter(typeof(ExpandableObjectConverter))] diff --git a/Dat/Loaders/DockObjectLoader.cs b/Dat/Loaders/DockObjectLoader.cs index 9d7e93a6..0e696576 100644 --- a/Dat/Loaders/DockObjectLoader.cs +++ b/Dat/Loaders/DockObjectLoader.cs @@ -21,6 +21,9 @@ public static class StructSizes public const int Dat = 0x28; } + public static ObjectType ObjectType => ObjectType.Dock; + public static DatObjectType DatObjectType => DatObjectType.Dock; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -28,8 +31,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new DockObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -54,20 +55,23 @@ public static LocoObject Load(Stream stream) }; // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Dock), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable - model.BuildingHeights = br.ReadBuildingHeights(numBuildingParts); - model.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts); - model.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations); + model.BuildingComponents.BuildingHeights = br.ReadBuildingHeights(numBuildingParts); + model.BuildingComponents.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts); + model.BuildingComponents.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Dock, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -86,8 +90,8 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // Image, not part of object definition bw.WriteEmptyImageId(); // UnkImage, not part of object definition bw.Write((uint16_t)model.Flags.Convert()); - bw.Write((uint8_t)model.BuildingAnimations.Count); - bw.Write((uint8_t)model.BuildingVariations.Count); + bw.Write((uint8_t)model.BuildingComponents.BuildingAnimations.Count); + bw.Write((uint8_t)model.BuildingComponents.BuildingVariations.Count); bw.WriteEmptyPointer(); // BuildingPartHeights bw.WriteEmptyPointer(); // BuildingPartAnimations bw.WriteEmptyPointer(); // BuildingVariationParts @@ -97,7 +101,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.BoatPosition.Y); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -106,15 +110,15 @@ public static void Save(Stream stream, LocoObject obj) SaveVariable(model, bw); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } private static void SaveVariable(DockObject model, LocoBinaryWriter bw) { - bw.Write(model.BuildingHeights); - bw.Write(model.BuildingAnimations); - bw.Write(model.BuildingVariations); + bw.Write(model.BuildingComponents.BuildingHeights); + bw.Write(model.BuildingComponents.BuildingAnimations); + bw.Write(model.BuildingComponents.BuildingVariations); } [Flags] diff --git a/Dat/Loaders/HillShapesObjectLoader.cs b/Dat/Loaders/HillShapesObjectLoader.cs index 680a640f..3365fc3f 100644 --- a/Dat/Loaders/HillShapesObjectLoader.cs +++ b/Dat/Loaders/HillShapesObjectLoader.cs @@ -16,6 +16,9 @@ public static class StructSizes public const int Dat = 0x0E; } + public static ObjectType ObjectType => ObjectType.HillShapes; + public static DatObjectType DatObjectType => DatObjectType.HillShapes; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -23,8 +26,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new HillShapesObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -35,18 +36,21 @@ public static LocoObject Load(Stream stream) model.IsHeightMap = ((DatHillShapeFlags)br.ReadUInt16()).HasFlag(DatHillShapeFlags.IsHeightMap); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.HillShapes), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.HillShapes, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -65,7 +69,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write((uint16_t)(model.IsHeightMap ? DatHillShapeFlags.IsHeightMap : DatHillShapeFlags.None)); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -74,7 +78,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/IDatObjectLoader.cs b/Dat/Loaders/IDatObjectLoader.cs index 79f53233..fa35bd40 100644 --- a/Dat/Loaders/IDatObjectLoader.cs +++ b/Dat/Loaders/IDatObjectLoader.cs @@ -1,4 +1,6 @@ +using Dat.Data; using Definitions.ObjectModels; +using Definitions.ObjectModels.Types; namespace Dat.Loaders; @@ -13,6 +15,9 @@ public interface IDatObjectLoader // where TDetails : IDatDetails { //public static abstract TDetails DatDetails { get; } + public static abstract ObjectType ObjectType { get; } + public static abstract DatObjectType DatObjectType { get; } + public static abstract LocoObject Load(Stream stream); public static abstract void Save(Stream stream, LocoObject obj); } diff --git a/Dat/Loaders/IndustryObjectLoader.cs b/Dat/Loaders/IndustryObjectLoader.cs index 83d9efdf..4d11c127 100644 --- a/Dat/Loaders/IndustryObjectLoader.cs +++ b/Dat/Loaders/IndustryObjectLoader.cs @@ -28,6 +28,9 @@ public static class StructSizes public const int IndustryObjectUnk38 = 0x02; } + public static ObjectType ObjectType => ObjectType.Industry; + public static DatObjectType DatObjectType => DatObjectType.Industry; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -35,8 +38,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new IndustryObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -72,8 +73,9 @@ public static LocoObject Load(Stream stream) model.ScaffoldingColour = (Colour)br.ReadByte(); for (var i = 0; i < Constants.InitialProductionRateCount; ++i) { - model.InitialProductionRate.Add(new() { Min = br.ReadUInt16(), Max = br.ReadUInt16()}); + model.InitialProductionRate.Add(new() { Min = br.ReadUInt16(), Max = br.ReadUInt16() }); } + br.SkipByte(Constants.MaxProducedCargoType); // ProducedCargo, not part of object definition br.SkipByte(Constants.MaxRequiredCargoType); // RequiredCargo, not part of object definition model.MapColour = (Colour)br.ReadByte(); @@ -89,25 +91,28 @@ public static LocoObject Load(Stream stream) model.MonthlyClosureChance = br.ReadByte(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Industry), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model, numBuildingParts, numBuildingVariations); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Industry, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } private static void LoadVariable(LocoBinaryReader br, IndustryObject model, byte numBuildingParts, byte numBuildingVariations) { - model.BuildingHeights = br.ReadBuildingHeights(numBuildingParts); - model.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts); + model.BuildingComponents.BuildingHeights = br.ReadBuildingHeights(numBuildingParts); + model.BuildingComponents.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts); // animation sequences for (var i = 0; i < Constants.AnimationSequencesCount; ++i) @@ -122,9 +127,10 @@ private static void LoadVariable(LocoBinaryReader br, IndustryObject model, byte { model.var_38.Add(new() { var_00 = br.ReadByte(), var_01 = br.ReadByte() }); } + br.SkipTerminator(); - model.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations); + model.BuildingComponents.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations); model.UnkBuildingData = [.. br.ReadBytes(model.MaxNumBuildings)]; model.ProducedCargo = br.ReadS5HeaderList(Constants.MaxProducedCargoType); model.RequiredCargo = br.ReadS5HeaderList(Constants.MaxRequiredCargoType); @@ -151,8 +157,8 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // BaseBuildingImageId, not part of object definition bw.WriteEmptyImageId(); // BaseFarmImageIds, not part of object definition bw.Write(model.FarmImagesPerGrowthStage); - bw.Write((uint8_t)model.BuildingHeights.Count); - bw.Write((uint8_t)model.BuildingVariations.Count); + bw.Write((uint8_t)model.BuildingComponents.BuildingHeights.Count); + bw.Write((uint8_t)model.BuildingComponents.BuildingVariations.Count); bw.WriteEmptyPointer(); // BuildingHeights, not part of object definition bw.WriteEmptyPointer(); // BuildingAnimations, not part of object definition bw.WriteEmptyPointer(Constants.AnimationSequencesCount); // AnimationSequences, not part of object definition @@ -176,6 +182,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(rate.Min); bw.Write(rate.Max); } + bw.WriteEmptyBytes(Constants.MaxProducedCargoType); bw.WriteEmptyBytes(Constants.MaxRequiredCargoType); bw.Write((uint8_t)model.MapColour); @@ -191,7 +198,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.MonthlyClosureChance); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -200,14 +207,14 @@ public static void Save(Stream stream, LocoObject obj) SaveVariable(model, bw); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } private static void SaveVariable(IndustryObject model, LocoBinaryWriter bw) { - bw.Write(model.BuildingHeights); - bw.Write(model.BuildingAnimations); + bw.Write(model.BuildingComponents.BuildingHeights); + bw.Write(model.BuildingComponents.BuildingAnimations); // animation sequences foreach (var x in model.AnimationSequences) @@ -222,9 +229,10 @@ private static void SaveVariable(IndustryObject model, LocoBinaryWriter bw) bw.Write(x.var_00); bw.Write(x.var_01); } + bw.WriteTerminator(); - bw.Write(model.BuildingVariations); + bw.Write(model.BuildingComponents.BuildingVariations); bw.Write((ReadOnlySpan)[.. model.UnkBuildingData]); bw.WriteS5HeaderList(model.ProducedCargo, Constants.MaxProducedCargoType); bw.WriteS5HeaderList(model.RequiredCargo, Constants.MaxRequiredCargoType); diff --git a/Dat/Loaders/InterfaceSkinObjectLoader.cs b/Dat/Loaders/InterfaceSkinObjectLoader.cs index 8774feb9..c53ae22a 100644 --- a/Dat/Loaders/InterfaceSkinObjectLoader.cs +++ b/Dat/Loaders/InterfaceSkinObjectLoader.cs @@ -3,7 +3,6 @@ using Definitions.ObjectModels; using Definitions.ObjectModels.Objects.InterfaceSkin; using Definitions.ObjectModels.Types; -using System.ComponentModel; namespace Dat.Loaders; @@ -17,6 +16,9 @@ public static class StructSizes public const int Dat = 0x18; } + public static ObjectType ObjectType => ObjectType.InterfaceSkin; + public static DatObjectType DatObjectType => DatObjectType.InterfaceSkin; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -24,8 +26,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new InterfaceSkinObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -50,18 +50,21 @@ public static LocoObject Load(Stream stream) model.TimeToolbarColour = (Colour)br.ReadByte(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.InterfaceSkin), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.InterfaceSkin, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -94,7 +97,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write((uint8_t)model.TimeToolbarColour); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -103,33 +106,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } - -[LocoStructSize(0x18)] -[LocoStructType(DatObjectType.InterfaceSkin)] -internal record DatInterfaceSkinObject( - [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [property: LocoStructOffset(0x02), Browsable(false)] image_id Image, - [property: LocoStructOffset(0x06)] DatColour MapTooltipObjectColour, - [property: LocoStructOffset(0x07)] DatColour MapTooltipCargoColour, - [property: LocoStructOffset(0x08)] DatColour TooltipColour, - [property: LocoStructOffset(0x09)] DatColour ErrorColour, - [property: LocoStructOffset(0x0A)] DatColour WindowPlayerColor, - [property: LocoStructOffset(0x0B)] DatColour WindowTitlebarColour, - [property: LocoStructOffset(0x0C)] DatColour WindowColour, - [property: LocoStructOffset(0x0D)] DatColour WindowConstructionColour, - [property: LocoStructOffset(0x0E)] DatColour WindowTerraFormColour, - [property: LocoStructOffset(0x0F)] DatColour WindowMapColour, - [property: LocoStructOffset(0x10)] DatColour WindowOptionsColour, - [property: LocoStructOffset(0x11)] DatColour Colour_11, - [property: LocoStructOffset(0x12)] DatColour TopToolbarPrimaryColour, - [property: LocoStructOffset(0x13)] DatColour TopToolbarSecondaryColour, - [property: LocoStructOffset(0x14)] DatColour TopToolbarTertiaryColour, - [property: LocoStructOffset(0x15)] DatColour TopToolbarQuaternaryColour, - [property: LocoStructOffset(0x16)] DatColour PlayerInfoToolbarColour, - [property: LocoStructOffset(0x17)] DatColour TimeToolbarColour - ) -{ } diff --git a/Dat/Loaders/LandObjectLoader.cs b/Dat/Loaders/LandObjectLoader.cs index b0dbc8db..c649cb3d 100644 --- a/Dat/Loaders/LandObjectLoader.cs +++ b/Dat/Loaders/LandObjectLoader.cs @@ -17,6 +17,9 @@ public static class StructSizes public const int Dat = 0x1E; } + public static ObjectType ObjectType => ObjectType.Land; + public static DatObjectType DatObjectType => DatObjectType.Land; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -24,8 +27,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new LandObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -46,10 +47,10 @@ public static LocoObject Load(Stream stream) br.SkipByte(); // pad // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Land), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable model.CliffEdgeHeader = br.ReadS5Header(); @@ -59,9 +60,12 @@ public static LocoObject Load(Stream stream) } // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Land, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -90,7 +94,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write((uint8_t)0); // pad // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -104,7 +108,7 @@ public static void Save(Stream stream, LocoObject obj) } // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/LevelCrossingObjectLoader.cs b/Dat/Loaders/LevelCrossingObjectLoader.cs index 59333b25..2f8cb438 100644 --- a/Dat/Loaders/LevelCrossingObjectLoader.cs +++ b/Dat/Loaders/LevelCrossingObjectLoader.cs @@ -9,13 +9,8 @@ namespace Dat.Loaders; public abstract class LevelCrossingObjectLoader : IDatObjectLoader { - public static class Constants - { } - - public static class StructSizes - { - public const int Dat = 0x12; - } + public static ObjectType ObjectType => ObjectType.LevelCrossing; + public static DatObjectType DatObjectType => DatObjectType.LevelCrossing; public static LocoObject Load(Stream stream) { @@ -24,8 +19,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new LevelCrossingObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -41,18 +34,21 @@ public static LocoObject Load(Stream stream) br.SkipImageId(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.LevelCrossing), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.LevelCrossing, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -76,7 +72,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // Image offset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -85,7 +81,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/RegionObjectLoader.cs b/Dat/Loaders/RegionObjectLoader.cs index c6680e00..f24e82c6 100644 --- a/Dat/Loaders/RegionObjectLoader.cs +++ b/Dat/Loaders/RegionObjectLoader.cs @@ -15,10 +15,12 @@ public static class Constants public static class StructSizes { - public const int Dat = 0x12; public const int CargoInfluenceTownFilterType = 0x01; } + public static ObjectType ObjectType => ObjectType.Region; + public static DatObjectType DatObjectType => DatObjectType.Region; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -26,8 +28,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new RegionObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -39,23 +39,27 @@ public static LocoObject Load(Stream stream) { model.CargoInfluenceTownFilter.Add((CargoInfluenceTownFilterType)br.ReadByte()); // Cargo influence town filter } + br.SkipByte(Constants.MaxCargoInfluenceObjects * StructSizes.CargoInfluenceTownFilterType); // Cargo influence town filter br.SkipByte(); // pad // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Region), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable model.CargoInfluenceObjects = br.ReadS5HeaderList(numCargoInfluenceObjects); model.DependentObjects = br.ReadS5HeaderList(); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + // N/A but Region has an empty image table for some reason + var imageList = SawyerStreamReader.ReadImageTable(br).Table; - return new LocoObject(ObjectType.Region, model, stringTable, imageTable); + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); + + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -76,10 +80,11 @@ public static void Save(Stream stream, LocoObject obj) { bw.Write((uint8_t)model.CargoInfluenceTownFilter[i]); // Cargo influence town filter } + bw.Write((uint8_t)0); // pad // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -90,7 +95,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteTerminator(); // end of dependent objects // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/RoadExtraObjectLoader.cs b/Dat/Loaders/RoadExtraObjectLoader.cs index 8c910ce7..481fdbe4 100644 --- a/Dat/Loaders/RoadExtraObjectLoader.cs +++ b/Dat/Loaders/RoadExtraObjectLoader.cs @@ -14,13 +14,14 @@ public static class StructSizes public const int DatStructSize = 0x12; } + public static ObjectType ObjectType => ObjectType.RoadExtra; + public static DatObjectType DatObjectType => DatObjectType.RoadExtra; + public static LocoObject Load(Stream stream) { using (var br = new LocoBinaryReader(stream)) { var model = new RoadExtraObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -36,15 +37,18 @@ public static LocoObject Load(Stream stream) ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, StructSizes.DatStructSize, nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.RoadExtra), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.RoadExtra, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -70,7 +74,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/RoadObjectLoader.cs b/Dat/Loaders/RoadObjectLoader.cs index f7cba061..06a6b24c 100644 --- a/Dat/Loaders/RoadObjectLoader.cs +++ b/Dat/Loaders/RoadObjectLoader.cs @@ -22,6 +22,9 @@ public static class StructSizes public const int Dat = 0x30; } + public static ObjectType ObjectType => ObjectType.Road; + public static DatObjectType DatObjectType => DatObjectType.Road; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -29,8 +32,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new RoadObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -58,18 +59,21 @@ public static LocoObject Load(Stream stream) br.SkipByte(); // pad_2F, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Road), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model, numBridges, numStations, numMods, numCompatible); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Road, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -114,7 +118,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write((uint8_t)0); // pad_2F, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -127,7 +131,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteS5HeaderList(model.Stations); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/RoadStationObjectLoader.cs b/Dat/Loaders/RoadStationObjectLoader.cs index 0100d2b4..41ecf952 100644 --- a/Dat/Loaders/RoadStationObjectLoader.cs +++ b/Dat/Loaders/RoadStationObjectLoader.cs @@ -23,6 +23,9 @@ public static class StructSizes public const int Dat = 0x6E; } + public static ObjectType ObjectType => ObjectType.RoadStation; + public static DatObjectType DatObjectType => DatObjectType.RoadStation; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -30,8 +33,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new RoadStationObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -53,18 +54,21 @@ public static LocoObject Load(Stream stream) br.SkipPointer(Constants.CargoOffsetBytesSize); // CargoOffsetBytes, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.RoadStation), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model, compatibleRoadObjectCount); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.RoadStation, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -109,7 +113,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyPointer(Constants.CargoOffsetBytesSize); // CargoOffsetBytes, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -118,7 +122,7 @@ public static void Save(Stream stream, LocoObject obj) SaveVariable(model, bw); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/ScaffoldingObjectLoader.cs b/Dat/Loaders/ScaffoldingObjectLoader.cs index 639682c5..90f9b264 100644 --- a/Dat/Loaders/ScaffoldingObjectLoader.cs +++ b/Dat/Loaders/ScaffoldingObjectLoader.cs @@ -19,6 +19,9 @@ public static class StructSizes public const int Dat = 0x12; } + public static ObjectType ObjectType => ObjectType.Scaffolding; + public static DatObjectType DatObjectType => DatObjectType.Scaffolding; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -26,8 +29,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new ScaffoldingObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -44,18 +45,21 @@ public static LocoObject Load(Stream stream) } // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Scaffolding), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Scaffolding, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -80,7 +84,7 @@ public static void Save(Stream stream, LocoObject obj) } // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -89,7 +93,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/ScenarioTextObjectLoader.cs b/Dat/Loaders/ScenarioTextObjectLoader.cs index 318284eb..8ce507bc 100644 --- a/Dat/Loaders/ScenarioTextObjectLoader.cs +++ b/Dat/Loaders/ScenarioTextObjectLoader.cs @@ -16,6 +16,9 @@ public static class StructSizes public const int Dat = 0x06; } + public static ObjectType ObjectType => ObjectType.ScenarioText; + public static DatObjectType DatObjectType => DatObjectType.ScenarioText; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -23,8 +26,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new ScenarioTextObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -32,18 +33,19 @@ public static LocoObject Load(Stream stream) br.SkipByte(0x06 - 0x04); // pad, not used // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.ScenarioText), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + // N/A but ScenaroText has an empty image table for some reason + _ = SawyerStreamReader.ReadImageTable(br).Table; - return new LocoObject(ObjectType.ScenarioText, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, null); } } @@ -58,7 +60,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyBytes(0x06 - 0x04); // padding // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -67,7 +69,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, new ImageTable().GraphicsElements); } } } diff --git a/Dat/Loaders/SnowObjectLoader.cs b/Dat/Loaders/SnowObjectLoader.cs index 1b1ec1cb..31404d1e 100644 --- a/Dat/Loaders/SnowObjectLoader.cs +++ b/Dat/Loaders/SnowObjectLoader.cs @@ -13,6 +13,9 @@ internal static class StructSizes public const int Dat = 0x06; } + public static ObjectType ObjectType => ObjectType.Snow; + public static DatObjectType DatObjectType => DatObjectType.Snow; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -20,26 +23,27 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new SnowObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition br.SkipImageId(); // Image offset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Snow), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Snow, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -53,7 +57,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // Image offset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -62,7 +66,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/SoundObjectLoader.cs b/Dat/Loaders/SoundObjectLoader.cs index d2cce9b3..9e2f7d37 100644 --- a/Dat/Loaders/SoundObjectLoader.cs +++ b/Dat/Loaders/SoundObjectLoader.cs @@ -23,6 +23,9 @@ public static class StructSizes public const int SoundObjectData = 0x1E; } + public static ObjectType ObjectType => ObjectType.Sound; + public static DatObjectType DatObjectType => DatObjectType.Sound; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -30,8 +33,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new SoundObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -41,10 +42,10 @@ public static LocoObject Load(Stream stream) model.Volume = br.ReadUInt32(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Sound), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model); @@ -52,7 +53,7 @@ public static LocoObject Load(Stream stream) // image table // N/A - return new LocoObject(ObjectType.Sound, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable); } } @@ -86,7 +87,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.Volume); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -132,8 +133,13 @@ internal record DatSoundObjectData( public DatSoundObjectData() : this(0, 0, 0, new DatSoundEffectWaveFormat()) { } - public bool Validate() - => Offset >= 0; + public IEnumerable Validate(ValidationContext validationContext) + { + if (Length > 0 && Offset < 0) + { + yield return new ValidationResult("If Length is greater than 0, Offset must be non-negative.", [nameof(Offset), nameof(Length)]); + } + } } [LocoStructSize(0x0C)] @@ -195,5 +201,6 @@ public ReadOnlySpan SaveVariable() } } - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Loaders/SteamObjectLoader.cs b/Dat/Loaders/SteamObjectLoader.cs index 1257942b..0bd4643d 100644 --- a/Dat/Loaders/SteamObjectLoader.cs +++ b/Dat/Loaders/SteamObjectLoader.cs @@ -21,6 +21,9 @@ public static class StructSizes public const int ImageAndHeight = 2; } + public static ObjectType ObjectType => ObjectType.Steam; + public static DatObjectType DatObjectType => DatObjectType.Steam; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -28,8 +31,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new SteamObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -49,18 +50,21 @@ public static LocoObject Load(Stream stream) br.SkipObjectId(Constants.MaxSoundEffects); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Steam), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model, numSoundEffects); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Steam, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -108,7 +112,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyObjectId(Constants.MaxSoundEffects); // _SoundEffects, not used // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -117,7 +121,7 @@ public static void Save(Stream stream, LocoObject obj) SaveVariable(model, bw); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } @@ -128,6 +132,7 @@ private static void SaveVariable(SteamObject model, LocoBinaryWriter bw) bw.Write(fit.ImageOffset); bw.Write(fit.Height); } + bw.WriteTerminator(); // end of frame info type 0 foreach (var fit in model.FrameInfoType1) @@ -135,6 +140,7 @@ private static void SaveVariable(SteamObject model, LocoBinaryWriter bw) bw.Write(fit.ImageOffset); bw.Write(fit.Height); } + bw.WriteTerminator(); // end of frame info type 1 bw.WriteS5HeaderList(model.SoundEffects); diff --git a/Dat/Loaders/StreetLightObjectLoader.cs b/Dat/Loaders/StreetLightObjectLoader.cs index 0baec0ed..739ea372 100644 --- a/Dat/Loaders/StreetLightObjectLoader.cs +++ b/Dat/Loaders/StreetLightObjectLoader.cs @@ -19,13 +19,14 @@ public static class StructSizes public const int Dat = 0x0C; } + public static ObjectType ObjectType => ObjectType.StreetLight; + public static DatObjectType DatObjectType => DatObjectType.StreetLight; + public static LocoObject Load(Stream stream) { using (var br = new LocoBinaryReader(stream)) { var model = new StreetLightObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); @@ -35,15 +36,18 @@ public static LocoObject Load(Stream stream) } // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.StreetLight), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.StreetLight, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -66,7 +70,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/TownNamesObjectLoader.cs b/Dat/Loaders/TownNamesObjectLoader.cs index 74afc913..004986e7 100644 --- a/Dat/Loaders/TownNamesObjectLoader.cs +++ b/Dat/Loaders/TownNamesObjectLoader.cs @@ -5,6 +5,7 @@ using Definitions.ObjectModels.Objects.TownNames; using Definitions.ObjectModels.Types; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Loaders; @@ -21,13 +22,14 @@ public static class StructSizes public const int Category = 0x1A; } + public static ObjectType ObjectType => ObjectType.TownNames; + public static DatObjectType DatObjectType => DatObjectType.TownNames; + public static LocoObject Load(Stream stream) { using (var br = new LocoBinaryReader(stream)) { var model = new TownNamesObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); @@ -44,7 +46,7 @@ public static LocoObject Load(Stream stream) } // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.TownNames), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A @@ -52,7 +54,7 @@ public static LocoObject Load(Stream stream) // image table // N/A - return new LocoObject(ObjectType.TownNames, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable); } } @@ -82,26 +84,3 @@ public static void Save(Stream stream, LocoObject obj) } } - -[LocoStructSize(0x1A)] -[LocoStructType(DatObjectType.TownNames)] -internal record DatTownNamesObject( - [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [property: LocoStructOffset(0x02), LocoArrayLength(6)] Category[] Categories -) : ILocoStruct, ILocoStructVariableData -{ - byte[] tempUnkVariableData; - - public ReadOnlySpan LoadVariable(ReadOnlySpan remainingData) - { - // town names is interesting - loco has not RE'd the whole object and there are no graphics, so we just - // skip the rest of the data/object - tempUnkVariableData = remainingData.ToArray(); - return remainingData[remainingData.Length..]; - } - - public ReadOnlySpan SaveVariable() - => tempUnkVariableData; - - public bool Validate() => true; -} diff --git a/Dat/Loaders/TrackExtraObjectLoader.cs b/Dat/Loaders/TrackExtraObjectLoader.cs index b2d73d38..6e093f1f 100644 --- a/Dat/Loaders/TrackExtraObjectLoader.cs +++ b/Dat/Loaders/TrackExtraObjectLoader.cs @@ -17,6 +17,9 @@ public static class StructSizes public const int Dat = 0x12; } + public static ObjectType ObjectType => ObjectType.TrackExtra; + public static DatObjectType DatObjectType => DatObjectType.TrackExtra; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -24,8 +27,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new TrackExtraObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -38,18 +39,21 @@ public static LocoObject Load(Stream stream) br.SkipImageId(); // BaseImageOffset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.TrackExtra), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.TrackExtra, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -70,7 +74,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // BaseImageOffset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -79,7 +83,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/TrackObjectLoader.cs b/Dat/Loaders/TrackObjectLoader.cs index 49f864e4..c3d55689 100644 --- a/Dat/Loaders/TrackObjectLoader.cs +++ b/Dat/Loaders/TrackObjectLoader.cs @@ -17,10 +17,8 @@ public static class Constants public const int MaxMods = 4; } - public static class StructSizes - { - public const int Dat = 0x36; - } + public static ObjectType ObjectType => ObjectType.Track; + public static DatObjectType DatObjectType => DatObjectType.Track; public static LocoObject Load(Stream stream) { @@ -29,8 +27,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new TrackObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -60,10 +56,10 @@ public static LocoObject Load(Stream stream) br.SkipByte(); // pad_35, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Track), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable model.CompatibleTracksAndRoads = br.ReadS5HeaderList(numCompatibleTracksAndRoads); @@ -74,9 +70,12 @@ public static LocoObject Load(Stream stream) model.Stations = br.ReadS5HeaderList(numStations); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Track, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -114,7 +113,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write((uint8_t)0); // pad_35, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -128,7 +127,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteS5HeaderList(model.Stations); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/TrackSignalObjectLoader.cs b/Dat/Loaders/TrackSignalObjectLoader.cs index 459c17af..2ee5bf0f 100644 --- a/Dat/Loaders/TrackSignalObjectLoader.cs +++ b/Dat/Loaders/TrackSignalObjectLoader.cs @@ -19,6 +19,9 @@ internal static class StructSizes public const int Dat = 0x1E; } + public static ObjectType ObjectType => ObjectType.TrackSignal; + public static DatObjectType DatObjectType => DatObjectType.TrackSignal; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -26,8 +29,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new TrackSignalObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -46,18 +47,21 @@ public static LocoObject Load(Stream stream) model.ObsoleteYear = br.ReadUInt16(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.TrackSignal), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable model.CompatibleTrackObjects = br.ReadS5HeaderList(compatibleTrackCount); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.TrackSignal, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -84,7 +88,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.ObsoleteYear); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -93,7 +97,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteS5HeaderList(model.CompatibleTrackObjects); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/TrackStationObjectLoader.cs b/Dat/Loaders/TrackStationObjectLoader.cs index ffa59010..cae91b1c 100644 --- a/Dat/Loaders/TrackStationObjectLoader.cs +++ b/Dat/Loaders/TrackStationObjectLoader.cs @@ -24,6 +24,9 @@ public static class StructSizes public const int CargoOffset = 6; } + public static ObjectType ObjectType => ObjectType.TrackStation; + public static DatObjectType DatObjectType => DatObjectType.TrackStation; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -31,8 +34,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new TrackStationObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -55,18 +56,21 @@ public static LocoObject Load(Stream stream) br.SkipPointer(Constants.var_6E_Length); // CargoOffsetBytes, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.TrackStation), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model, compatibleTrackObjectCount); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.TrackStation, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -120,7 +124,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyPointer(Constants.var_6E_Length); // var_6E, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -129,7 +133,7 @@ public static void Save(Stream stream, LocoObject obj) SaveVariable(model, bw); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/TreeObjectLoader.cs b/Dat/Loaders/TreeObjectLoader.cs index 4a2b028a..5fe93e77 100644 --- a/Dat/Loaders/TreeObjectLoader.cs +++ b/Dat/Loaders/TreeObjectLoader.cs @@ -19,6 +19,9 @@ public static class StructSizes public const int Dat = 0x4C; } + public static ObjectType ObjectType => ObjectType.Tree; + public static DatObjectType DatObjectType => DatObjectType.Tree; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -26,8 +29,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new TreeObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -52,18 +53,21 @@ public static LocoObject Load(Stream stream) model.DemolishRatingReduction = br.ReadInt16(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Tree), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Tree, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -96,7 +100,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.DemolishRatingReduction); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -105,7 +109,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } @@ -152,27 +156,3 @@ public static TreeFlagsUnk Convert(this DatTreeFlagsUnk datTreeFlagsUnk) public static DatTreeFlagsUnk Convert(this TreeFlagsUnk treeFlagsUnk) => (DatTreeFlagsUnk)treeFlagsUnk; } - -[LocoStructSize(0x4C)] -[LocoStructType(DatObjectType.Tree)] -internal record DatTreeObject( - [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [property: LocoStructOffset(0x02)] uint8_t Clearance, - [property: LocoStructOffset(0x03)] uint8_t Height, - [property: LocoStructOffset(0x04)] uint8_t var_04, - [property: LocoStructOffset(0x05)] uint8_t var_05, - [property: LocoStructOffset(0x06)] uint8_t NumRotations, - [property: LocoStructOffset(0x07)] uint8_t NumGrowthStages, - [property: LocoStructOffset(0x08)] DatTreeObjectFlags Flags, - [property: LocoStructOffset(0x0A), LocoArrayLength(6), Browsable(false)] image_id[] Sprites, - [property: LocoStructOffset(0x22), LocoArrayLength(6), Browsable(false)] image_id[] SnowSprites, - [property: LocoStructOffset(0x3A), Browsable(false)] uint16_t ShadowImageOffset, - [property: LocoStructOffset(0x3C)] DatTreeFlagsUnk var_3C, // something with images - [property: LocoStructOffset(0x3D)] uint8_t SeasonState, - [property: LocoStructOffset(0x3E)] uint8_t Season, - [property: LocoStructOffset(0x3F)] uint8_t CostIndex, - [property: LocoStructOffset(0x40)] int16_t BuildCostFactor, - [property: LocoStructOffset(0x42)] int16_t ClearCostFactor, - [property: LocoStructOffset(0x44)] uint32_t Colours, - [property: LocoStructOffset(0x48)] int16_t Rating, - [property: LocoStructOffset(0x4A)] int16_t DemolishRatingReduction); diff --git a/Dat/Loaders/TunnelObjectLoader.cs b/Dat/Loaders/TunnelObjectLoader.cs index dec153b4..be18a654 100644 --- a/Dat/Loaders/TunnelObjectLoader.cs +++ b/Dat/Loaders/TunnelObjectLoader.cs @@ -16,6 +16,9 @@ public static class StructSizes public const int Dat = 0x06; } + public static ObjectType ObjectType => ObjectType.Tunnel; + public static DatObjectType DatObjectType => DatObjectType.Tunnel; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -23,26 +26,27 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new TunnelObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition br.SkipImageId(); // Image offset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Tunnel), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Tunnel, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -56,7 +60,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // Image offset, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -65,7 +69,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } diff --git a/Dat/Loaders/Vehicle/VehicleObjectLoader.cs b/Dat/Loaders/Vehicle/VehicleObjectLoader.cs index b2fdbba3..60409b71 100644 --- a/Dat/Loaders/Vehicle/VehicleObjectLoader.cs +++ b/Dat/Loaders/Vehicle/VehicleObjectLoader.cs @@ -28,13 +28,16 @@ public static class Constants public static class StructSizes { - public const int Dat = 0x15E; + //public const int Dat = 0x15E; public const int SoundData = 0x1B; public const int FrictionSound = 0x0B; public const int SimpleMotorSound = 0x11; public const int GearboxMotorSound = 0x1B; } + public static ObjectType ObjectType => ObjectType.Vehicle; + public static DatObjectType DatObjectType => DatObjectType.Vehicle; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -42,25 +45,26 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new VehicleObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed LoadFixed(br, model, out var numRequiredTrackExtras, out var numCompatibleVehicles, out var numStartSounds); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Vehicle), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable LoadVariable(br, model, numRequiredTrackExtras, numCompatibleVehicles, numStartSounds); // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Vehicle, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -277,7 +281,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyBytes(Constants.MaxStartSounds * 1); // StartSounds, not part of object // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -286,7 +290,7 @@ public static void Save(Stream stream, LocoObject obj) SaveVariable(model, bw); // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/WallObjectLoader.cs b/Dat/Loaders/WallObjectLoader.cs index d8fed4b7..25294775 100644 --- a/Dat/Loaders/WallObjectLoader.cs +++ b/Dat/Loaders/WallObjectLoader.cs @@ -17,6 +17,9 @@ internal static class StructSizes public const int Dat = 0x0A; } + public static ObjectType ObjectType => ObjectType.Wall; + public static DatObjectType DatObjectType => DatObjectType.Wall; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -24,8 +27,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new WallObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -36,18 +37,21 @@ public static LocoObject Load(Stream stream) model.Flags2 = ((DatWallObjectFlags2)br.ReadByte()).Convert(); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Wall), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Wall, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -66,7 +70,7 @@ public static void Save(Stream stream, LocoObject obj) bw.Write((uint8_t)model.Flags2.Convert()); // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -75,7 +79,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } diff --git a/Dat/Loaders/WaterObjectLoader.cs b/Dat/Loaders/WaterObjectLoader.cs index b2ff5348..22ca4642 100644 --- a/Dat/Loaders/WaterObjectLoader.cs +++ b/Dat/Loaders/WaterObjectLoader.cs @@ -16,6 +16,9 @@ public static class StructSizes public const int Dat = 0x0E; } + public static ObjectType ObjectType => ObjectType.Water; + public static DatObjectType DatObjectType => DatObjectType.Water; + public static LocoObject Load(Stream stream) { var initialStreamPosition = stream.Position; @@ -23,8 +26,6 @@ public static LocoObject Load(Stream stream) using (var br = new LocoBinaryReader(stream)) { var model = new WaterObject(); - var stringTable = new StringTable(); - var imageTable = new List(); // fixed br.SkipStringId(); // Name offset, not part of object definition @@ -35,18 +36,21 @@ public static LocoObject Load(Stream stream) br.SkipImageId(); // MapPixelImage, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table - stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Water), null); + var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null); // variable // N/A // image table - imageTable = SawyerStreamReader.ReadImageTable(br).Table; + var imageList = SawyerStreamReader.ReadImageTable(br).Table; + + // define groups + var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList); - return new LocoObject(ObjectType.Water, model, stringTable, imageTable); + return new LocoObject(ObjectType, model, stringTable, imageTable); } } @@ -65,7 +69,7 @@ public static void Save(Stream stream, LocoObject obj) bw.WriteEmptyImageId(); // MapPixelImage, not part of object definition // sanity check - ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position)); + ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position)); // string table SawyerStreamWriter.WriteStringTable(stream, obj.StringTable); @@ -74,21 +78,7 @@ public static void Save(Stream stream, LocoObject obj) // N/A // image table - SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements); + SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements); } } } - -[LocoStructSize(0x0E)] -[LocoStructType(DatObjectType.Water)] -internal record DatWaterObject( - [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [property: LocoStructOffset(0x02)] uint8_t CostIndex, - [property: LocoStructOffset(0x03), LocoPropertyMaybeUnused] uint8_t var_03, - [property: LocoStructOffset(0x04)] int16_t CostFactor, - [property: LocoStructOffset(0x06), Browsable(false)] image_id Image, - [property: LocoStructOffset(0x0A), Browsable(false)] image_id MapPixelImage - ) -{ - -} diff --git a/Dat/Types/Audio/DatMusicWaveFormat.cs b/Dat/Types/Audio/DatMusicWaveFormat.cs index 3e73c419..d235ba7a 100644 --- a/Dat/Types/Audio/DatMusicWaveFormat.cs +++ b/Dat/Types/Audio/DatMusicWaveFormat.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.Audio; @@ -47,38 +48,36 @@ public ReadOnlySpan Write() return ((MemoryStream)bs.BaseStream).ToArray(); } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (Signature != 0x46464952) // "RIFF" { - return false; + yield return new ValidationResult("Signature must be 'RIFF'", [nameof(Signature)]); } if (RiffType != 0x45564157) // "WAVE" { - return false; + yield return new ValidationResult("RiffType must be 'WAVE'", [nameof(RiffType)]); } if (FormatMarker is not 0x20746d66 and not 0x00746d66) // "fmt\0" or "fmt" { - return false; + yield return new ValidationResult("FormatMarker must be 'fmt ' or 'fmt\\0'", [nameof(FormatMarker)]); } if (FormatType != 1) // expected PCM { - return false; + yield return new ValidationResult("FormatType must be 1 (PCM)", [nameof(FormatType)]); } if (BitsPerSample != 16) { - return false; + yield return new ValidationResult("BitsPerSample must be 16", [nameof(BitsPerSample)]); } if (DataMarker != 0x61746164) { - return false; + yield return new ValidationResult("DataMarker must be 'data'", [nameof(DataMarker)]); } - - return true; } } diff --git a/Dat/Types/Audio/DatSoundEffectWaveFormat.cs b/Dat/Types/Audio/DatSoundEffectWaveFormat.cs index 36f79ca3..aa386249 100644 --- a/Dat/Types/Audio/DatSoundEffectWaveFormat.cs +++ b/Dat/Types/Audio/DatSoundEffectWaveFormat.cs @@ -1,6 +1,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.Audio; @@ -35,6 +36,6 @@ public ReadOnlySpan Write() return ((MemoryStream)bs.BaseStream).ToArray(); } - public bool Validate() - => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/DatG1Element32.cs b/Dat/Types/DatG1Element32.cs index a38f8ee7..6804461b 100644 --- a/Dat/Types/DatG1Element32.cs +++ b/Dat/Types/DatG1Element32.cs @@ -1,6 +1,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types; @@ -67,5 +68,6 @@ public static byte[] GetImageDataForSave(DatG1ElementFlags flags, byte[] imageDa ? SawyerStreamWriter.EncodeRLEImageData(flags, imageData, width, height) : imageData; - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/G1Dat.cs b/Dat/Types/G1Dat.cs index 4f7cd838..7e70c7d5 100644 --- a/Dat/Types/G1Dat.cs +++ b/Dat/Types/G1Dat.cs @@ -1,20 +1,47 @@ using Definitions.ObjectModels; using Definitions.ObjectModels.Types; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace Dat.Types; [TypeConverter(typeof(ExpandableObjectConverter))] -public class G1Dat(G1Header g1Header, List g1Elements) : IHasGraphicsElements, IImageTableNameProvider +public class G1Dat { - public G1Header G1Header { get; set; } = g1Header; - public List GraphicsElements { get; set; } = g1Elements; + public G1Header G1Header { get; set; } + public ImageTable ImageTable { get; set; } public bool IsSteamG1 - => GraphicsElements.Count == 3896; + => ImageTable.GraphicsElements.Count == 3896; - public bool TryGetImageName(int id, out string? value) + public G1Dat(G1Header g1Header, List graphicsElements) + { + G1Header = g1Header; + ImageTable = new ImageTable + { + Groups = + [ + ("terrain-masks", [.. graphicsElements[0..417], .. graphicsElements[3629..3896]]), + ("palettes", [.. graphicsElements[417..428], .. graphicsElements[2170..2304]]), + ("arrows", [.. graphicsElements[428..444], .. graphicsElements[449..457], .. graphicsElements[3492..3504]]), + ("unk", graphicsElements[444..449]), + ("supports", graphicsElements[457..1117]), + ("glyphs", graphicsElements[1117..2169]), + ("loading-bar", graphicsElements[2326..2335]), + ("interface", [.. graphicsElements[2335..2470], .. graphicsElements[3477..3479], .. graphicsElements[2305..2326], .. graphicsElements[3539..3547]]), + ("height-markers", graphicsElements[2470..3238]), + ("numerical-markers", graphicsElements[3238..3302]), + ("unk", graphicsElements[3302..3362]), + ("particles", graphicsElements[3362..3477]), + ("masks", [.. graphicsElements[3479..3492], graphicsElements[3504]]), + ("object-types", graphicsElements[3505..3539]), + ("title", graphicsElements[3547..3629]), + ] + }; + } + + public bool TryGetImageName(int id, [MaybeNullWhen(false)] out string value) => id < 3550 ? BaseImageIdNameMap.TryGetValue(id, out value) : (IsSteamG1 ? SteamImageIdNameMap : GoGImageIdNameMap).TryGetValue(id, out value); diff --git a/Dat/Types/G1Header.cs b/Dat/Types/G1Header.cs index e286e2a5..a2cc238b 100644 --- a/Dat/Types/G1Header.cs +++ b/Dat/Types/G1Header.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types; @@ -12,5 +13,6 @@ public record G1Header( public static int StructLength => 0x08; public byte[] ImageData = []; - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/Animation.cs b/Dat/Types/SCV5/Animation.cs index 23eb5a5f..400e0257 100644 --- a/Dat/Types/SCV5/Animation.cs +++ b/Dat/Types/SCV5/Animation.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -8,6 +9,6 @@ public class Animation : ILocoStruct { [LocoArrayLength(0x06)] public uint8_t[] var_0 { get; set; } = []; - public bool Validate() - => throw new NotImplementedException(); + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/Company.cs b/Dat/Types/SCV5/Company.cs index 7d0a668a..95b540e2 100644 --- a/Dat/Types/SCV5/Company.cs +++ b/Dat/Types/SCV5/Company.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -17,5 +18,6 @@ public class Company : ILocoStruct public uint8_t ChallengeProgress { get; set; } // 0x8C4E [LocoArrayLength(0x8FA8 - 0x8C4F)] public uint8_t[] var_8C4F { get; set; } - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/Entity.cs b/Dat/Types/SCV5/Entity.cs index b917fe18..2fc93306 100644 --- a/Dat/Types/SCV5/Entity.cs +++ b/Dat/Types/SCV5/Entity.cs @@ -1,6 +1,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; [LocoStructSize(0x80)] @@ -8,6 +9,6 @@ public class Entity : ILocoStruct { [LocoArrayLength(0x80)] public uint8_t[] var_0 { get; set; } = []; - public bool Validate() - => throw new NotImplementedException(); + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/GameState.cs b/Dat/Types/SCV5/GameState.cs index d99d8945..44464ffe 100644 --- a/Dat/Types/SCV5/GameState.cs +++ b/Dat/Types/SCV5/GameState.cs @@ -2,6 +2,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -149,7 +150,8 @@ public record GameStateScenarioA( { //public const int StructLength = 0x4A0644; - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } [TypeConverter(typeof(ExpandableObjectConverter))] @@ -162,7 +164,8 @@ public record GameStateScenarioB( //[property: LocoStructOffset(0x123480), LocoArrayLength((int)Limits.kMaxEntities)] Entity[] Entities // this isn't actually part of the data chunk in a scenario! ) : ILocoStruct { - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } [TypeConverter(typeof(ExpandableObjectConverter))] @@ -175,7 +178,8 @@ public record GameStateScenarioC( [property: LocoStructOffset(0x3B580), LocoArrayLength((int)Limits.kMaxWaves), Browsable(false)] uint8_t[] Orders ) : ILocoStruct { - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } [TypeConverter(typeof(ExpandableObjectConverter))] @@ -312,5 +316,6 @@ public record GameStateSave([property: LocoStructOffset(0x00), LocoArrayLength(2 ) : ILocoStruct, IGameState { - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/Industry.cs b/Dat/Types/SCV5/Industry.cs index 2d5c8636..ab0d4ed8 100644 --- a/Dat/Types/SCV5/Industry.cs +++ b/Dat/Types/SCV5/Industry.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -8,5 +9,6 @@ public class Industry : ILocoStruct { [LocoArrayLength(0x453)] public uint8_t[] var_0 { get; set; } - public bool Validate() => throw new NotImplementedException(); + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/Message.cs b/Dat/Types/SCV5/Message.cs index c459b9e4..b4a11e3c 100644 --- a/Dat/Types/SCV5/Message.cs +++ b/Dat/Types/SCV5/Message.cs @@ -1,6 +1,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -10,5 +11,6 @@ public class Message : ILocoStruct { [LocoArrayLength(0xD4)] public uint8_t[] var_0 { get; set; } - public bool Validate() => throw new NotImplementedException(); + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/S5File.cs b/Dat/Types/SCV5/S5File.cs index 93a8d0cf..7015cc99 100644 --- a/Dat/Types/SCV5/S5File.cs +++ b/Dat/Types/SCV5/S5File.cs @@ -2,6 +2,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -96,7 +97,6 @@ uint32_t Checksum ) : ILocoStruct { - public bool Validate() => true; public const int StructLength = 0x20; public const int RequiredObjectsCount = 859; @@ -104,6 +104,9 @@ uint32_t Checksum public List[,]? TileElementMap { get; set; } byte[] OriginalTileElementData { get; set; } = []; + public IEnumerable Validate(ValidationContext validationContext) + => []; + public byte[] Write() { var hdr = SawyerStreamWriter.WriteChunk(Header, SawyerEncoding.Rotate); diff --git a/Dat/Types/SCV5/S5FileHeader.cs b/Dat/Types/SCV5/S5FileHeader.cs index 53d64008..938d5039 100644 --- a/Dat/Types/SCV5/S5FileHeader.cs +++ b/Dat/Types/SCV5/S5FileHeader.cs @@ -1,6 +1,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -16,5 +17,7 @@ public record S5FileHeader( : ILocoStruct { public const int StructLength = 0x20; - public bool Validate() => true; + + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/SaveDetails.cs b/Dat/Types/SCV5/SaveDetails.cs index 0021f702..9fdfa533 100644 --- a/Dat/Types/SCV5/SaveDetails.cs +++ b/Dat/Types/SCV5/SaveDetails.cs @@ -1,6 +1,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -20,5 +21,7 @@ public record SaveDetails( : ILocoStruct { public const int StructLength = 0xC618; - public bool Validate() => true; + + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/ScenarioObjective.cs b/Dat/Types/SCV5/ScenarioObjective.cs index 72d36e71..0398f9b3 100644 --- a/Dat/Types/SCV5/ScenarioObjective.cs +++ b/Dat/Types/SCV5/ScenarioObjective.cs @@ -1,6 +1,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -17,5 +18,6 @@ public record ScenarioObjective( [property: LocoStructOffset(0x10)] uint8_t TimeLimitYears) : ILocoStruct { - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/ScenarioOptions.cs b/Dat/Types/SCV5/ScenarioOptions.cs index cea34edb..7f58ff61 100644 --- a/Dat/Types/SCV5/ScenarioOptions.cs +++ b/Dat/Types/SCV5/ScenarioOptions.cs @@ -1,6 +1,7 @@ using Dat.FileParsing; using Definitions.ObjectModels; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -50,5 +51,7 @@ public record ScenarioOptions( : ILocoStruct { public const int StructLength = 0x431A; - public bool Validate() => true; + + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/Station.cs b/Dat/Types/SCV5/Station.cs index e84f1f9b..00b63d1b 100644 --- a/Dat/Types/SCV5/Station.cs +++ b/Dat/Types/SCV5/Station.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -8,5 +9,6 @@ public class Station : ILocoStruct { [LocoArrayLength(0x3D2)] public uint8_t[] var_0 { get; set; } - public bool Validate() => throw new NotImplementedException(); + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/Town.cs b/Dat/Types/SCV5/Town.cs index 8b492aae..5101ac78 100644 --- a/Dat/Types/SCV5/Town.cs +++ b/Dat/Types/SCV5/Town.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -8,5 +9,6 @@ public class Town : ILocoStruct { [LocoArrayLength(0x270)] public uint8_t[] var_0 { get; set; } - public bool Validate() => throw new NotImplementedException(); + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/SCV5/Wave.cs b/Dat/Types/SCV5/Wave.cs index be0d10d4..6fd53976 100644 --- a/Dat/Types/SCV5/Wave.cs +++ b/Dat/Types/SCV5/Wave.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types.SCV5; @@ -8,5 +9,6 @@ public class Wave : ILocoStruct { [LocoArrayLength(0x06)] public uint8_t[] var_0 { get; set; } - public bool Validate() => throw new NotImplementedException(); + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Dat/Types/Typedefs.cs b/Dat/Types/Typedefs.cs index 1a397169..97febc31 100644 --- a/Dat/Types/Typedefs.cs +++ b/Dat/Types/Typedefs.cs @@ -1,5 +1,6 @@ using Dat.FileParsing; using Definitions.ObjectModels; +using System.ComponentModel.DataAnnotations; namespace Dat.Types; @@ -9,7 +10,8 @@ public record DatPos2( [property: LocoStructOffset(0x02)] coord_t Y = 0 ) : ILocoStruct { - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } [LocoStructSize(0x06)] @@ -19,5 +21,6 @@ public record DatPos3( [property: LocoStructOffset(0x04)] coord_t Z = 0 ) : ILocoStruct { - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/DataSanitiser/Program.cs b/DataSanitiser/Program.cs index ed983674..d1d41893 100644 --- a/DataSanitiser/Program.cs +++ b/DataSanitiser/Program.cs @@ -1,19 +1,154 @@ -using Microsoft.EntityFrameworkCore; using Common.Logging; +using Dat.Data; using Dat.FileParsing; using Definitions.Database; +using Definitions.ObjectModels; +using Definitions.ObjectModels.Objects.Airport; +using Definitions.ObjectModels.Objects.Bridge; +using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Cargo; +using Definitions.ObjectModels.Objects.CliffEdge; +using Definitions.ObjectModels.Objects.Climate; +using Definitions.ObjectModels.Objects.Competitor; +using Definitions.ObjectModels.Objects.Currency; +using Definitions.ObjectModels.Objects.Dock; +using Definitions.ObjectModels.Objects.HillShape; +using Definitions.ObjectModels.Objects.Industry; +using Definitions.ObjectModels.Objects.InterfaceSkin; +using Definitions.ObjectModels.Objects.Land; +using Definitions.ObjectModels.Objects.LevelCrossing; +using Definitions.ObjectModels.Objects.Region; +using Definitions.ObjectModels.Objects.Road; +using Definitions.ObjectModels.Objects.RoadExtra; +using Definitions.ObjectModels.Objects.RoadStation; +using Definitions.ObjectModels.Objects.Scaffolding; +using Definitions.ObjectModels.Objects.ScenarioText; +using Definitions.ObjectModels.Objects.Snow; +using Definitions.ObjectModels.Objects.Sound; +using Definitions.ObjectModels.Objects.Steam; +using Definitions.ObjectModels.Objects.Streetlight; +using Definitions.ObjectModels.Objects.TownNames; +using Definitions.ObjectModels.Objects.Track; +using Definitions.ObjectModels.Objects.TrackExtra; +using Definitions.ObjectModels.Objects.TrackSignal; +using Definitions.ObjectModels.Objects.TrackStation; +using Definitions.ObjectModels.Objects.Tree; +using Definitions.ObjectModels.Objects.Tunnel; +using Definitions.ObjectModels.Objects.Vehicle; +using Definitions.ObjectModels.Objects.Wall; +using Definitions.ObjectModels.Objects.Water; +using Definitions.ObjectModels.Types; +using Index; +using Microsoft.EntityFrameworkCore; using System.IO.Hashing; using System.Reflection; -using Index; -using Definitions.ObjectModels.Types; -using Definitions.ObjectModels.Objects.Industry; using IndustryObject = Definitions.ObjectModels.Objects.Industry.IndustryObject; -using Definitions.ObjectModels.Objects.Vehicle; using VehicleObject = Definitions.ObjectModels.Objects.Vehicle.VehicleObject; -using Dat.Data; -using Definitions.ObjectModels.Objects.Cargo; -using Definitions.ObjectModels.Objects.TrackStation; -using Definitions.ObjectModels.Objects.Track; + +static ObjectType TypeToStruct(Type type) + => type switch + { + var t when t == typeof(AirportObject) => ObjectType.Airport, + var t when t == typeof(BridgeObject) => ObjectType.Bridge, + var t when t == typeof(BuildingObject) => ObjectType.Building, + var t when t == typeof(CargoObject) => ObjectType.Cargo, + var t when t == typeof(CliffEdgeObject) => ObjectType.CliffEdge, + var t when t == typeof(ClimateObject) => ObjectType.Climate, + var t when t == typeof(CompetitorObject) => ObjectType.Competitor, + var t when t == typeof(CurrencyObject) => ObjectType.Currency, + var t when t == typeof(DockObject) => ObjectType.Dock, + var t when t == typeof(HillShapesObject) => ObjectType.HillShapes, + var t when t == typeof(IndustryObject) => ObjectType.Industry, + var t when t == typeof(InterfaceSkinObject) => ObjectType.InterfaceSkin, + var t when t == typeof(LandObject) => ObjectType.Land, + var t when t == typeof(LevelCrossingObject) => ObjectType.LevelCrossing, + var t when t == typeof(RegionObject) => ObjectType.Region, + var t when t == typeof(RoadExtraObject) => ObjectType.RoadExtra, + var t when t == typeof(RoadObject) => ObjectType.Road, + var t when t == typeof(RoadStationObject) => ObjectType.RoadStation, + var t when t == typeof(ScaffoldingObject) => ObjectType.Scaffolding, + var t when t == typeof(ScenarioTextObject) => ObjectType.ScenarioText, + var t when t == typeof(SnowObject) => ObjectType.Snow, + var t when t == typeof(SoundObject) => ObjectType.Sound, + var t when t == typeof(SteamObject) => ObjectType.Steam, + var t when t == typeof(StreetLightObject) => ObjectType.StreetLight, + var t when t == typeof(TownNamesObject) => ObjectType.TownNames, + var t when t == typeof(TrackExtraObject) => ObjectType.TrackExtra, + var t when t == typeof(TrackObject) => ObjectType.Track, + var t when t == typeof(TrackSignalObject) => ObjectType.TrackSignal, + var t when t == typeof(TrackStationObject) => ObjectType.TrackStation, + var t when t == typeof(TreeObject) => ObjectType.Tree, + var t when t == typeof(TunnelObject) => ObjectType.Tunnel, + var t when t == typeof(VehicleObject) => ObjectType.Vehicle, + var t when t == typeof(WallObject) => ObjectType.Wall, + var t when t == typeof(WaterObject) => ObjectType.Water, + _ => throw new ArgumentOutOfRangeException(nameof(type), $"unknown struct type {type.FullName}") + }; + +static void QueryCostIndex() +{ + var dir = "Q:\\Games\\Locomotion\\Server\\Objects"; + var logger = new Logger(); + var index = ObjectIndex.LoadOrCreateIndex(dir, logger); + //var index = ObjectIndex.CreateIndex(dir, logger); + //index.SaveIndexAsync(Path.Combine(dir, "objectIndex.json")).Wait(); + + var results = new List<(ObjectIndexEntry Obj, ObjectSource ObjectSource, byte CostIndex)>(); + + // Pseudocode plan: + // 1. Get all loaded assemblies (or just the relevant ones, e.g., Assembly.GetExecutingAssembly()). + // 2. For each type, check if it's a class, not abstract, implements ILocoStruct, and has a property named "CostIndex". + // 3. Iterate over these types in a foreach loop. + + var locoStructTypesWithCostIndex = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where(t => + t.IsClass && + !t.IsAbstract && + typeof(ILocoStruct).IsAssignableFrom(t) && + t.GetProperty("CostIndex", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase) != null) + .Select(TypeToStruct) + .ToHashSet(); + + foreach (var type in locoStructTypesWithCostIndex) + { + Console.WriteLine($"Type: {type} implements ILocoStruct and has a CostIndex property."); + } + + //foreach (var obj in index.Objects.Where(x => x.ObjectSource is ObjectSource.LocomotionSteam or ObjectSource.LocomotionGoG && locoStructTypesWithCostIndex.Contains(x.ObjectType))) + + foreach (var obj in index.Objects.Where(x => locoStructTypesWithCostIndex.Contains(x.ObjectType))) + { + try + { + var o = SawyerStreamReader.LoadFullObject(Path.Combine(dir, obj.FileName), logger); + if (o.LocoObject != null) + { + var type = o.LocoObject.Object.GetType(); + var costIndexProperty = type.GetProperty("CostIndex", BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + + var costIndex = (byte)costIndexProperty.GetValue(o.LocoObject.Object); + + var header = o.DatFileInfo.S5Header; + var source = OriginalObjectFiles.GetFileSource(header.Name, header.Checksum); + + results.Add((obj, source, costIndex)); + } + } + catch (Exception ex) + { + Console.WriteLine($"{obj.FileName} - {ex.Message}"); + } + } + + Console.WriteLine(results.Count); + foreach (var result in results.OrderBy(x => x.CostIndex)) + { + // Print object index entry and its enabled flags + Console.WriteLine($"{result.Obj.DisplayName} - {result.ObjectSource} - CostIndex={result.CostIndex}"); + } +} +QueryCostIndex(); static void QueryTrackStationOneSidedTrack() { @@ -75,7 +210,7 @@ static void QueryTrackStationOneSidedTrack() Console.WriteLine($"{result.Obj.DisplayName} - Checksum: 0x{result.Obj.DatChecksum:X} - Source: {result.ObjectSource} - Flags: {string.Join('|', result.Flags)}"); } } -QueryTrackStationOneSidedTrack(); +//QueryTrackStationOneSidedTrack(); static void QueryIndustryHasShadows() { diff --git a/Definitions/DTO/Comparers/DtoStringTableDescriptorComparer.cs b/Definitions/DTO/Comparers/DtoStringTableDescriptorComparer.cs index cc95a87d..373ae15f 100644 --- a/Definitions/DTO/Comparers/DtoStringTableDescriptorComparer.cs +++ b/Definitions/DTO/Comparers/DtoStringTableDescriptorComparer.cs @@ -64,6 +64,7 @@ public int GetHashCode([DisallowNull] DtoStringTableDescriptor obj) hash = HashCode.Combine(hash, lang, text); } } + return hash; } } diff --git a/Definitions/Database/DataTables/Objects/TblObjectDock.cs b/Definitions/Database/DataTables/Objects/TblObjectDock.cs index bd3db2f1..1fcdb505 100644 --- a/Definitions/Database/DataTables/Objects/TblObjectDock.cs +++ b/Definitions/Database/DataTables/Objects/TblObjectDock.cs @@ -30,8 +30,8 @@ public static TblObjectDock FromObject(TblObject tbl, DockObject obj) SellCostFactor = obj.SellCostFactor, CostIndex = obj.CostIndex, Flags = obj.Flags, - NumBuildingPartAnimations = (uint8_t)obj.BuildingAnimations.Count, - NumBuildingVariationParts = (uint8_t)obj.BuildingVariations.Count, + NumBuildingPartAnimations = (uint8_t)obj.BuildingComponents.BuildingAnimations.Count, + NumBuildingVariationParts = (uint8_t)obj.BuildingComponents.BuildingVariations.Count, DesignedYear = obj.DesignedYear, ObsoleteYear = obj.ObsoleteYear, BoatPositionX = obj.BoatPosition.X, diff --git a/Definitions/ObjectModels/IImageTableNameProvider.cs b/Definitions/ObjectModels/IImageTableNameProvider.cs index e76b2346..44488704 100644 --- a/Definitions/ObjectModels/IImageTableNameProvider.cs +++ b/Definitions/ObjectModels/IImageTableNameProvider.cs @@ -1,22 +1,49 @@ using Definitions.ObjectModels.Types; +using System.Diagnostics.CodeAnalysis; namespace Definitions.ObjectModels; public interface IHasGraphicsElements { - public List GraphicsElements { get; set; } + List GraphicsElements { get; set; } // todo: probably change to IEnumerable } public interface IImageTableNameProvider { - public bool TryGetImageName(int id, out string? value); + bool TryGetImageName(T model, int id, [MaybeNullWhen(false)] out string value) where T : ILocoStruct; +} + +public interface IImageTableNameProvider where T : ILocoStruct +{ + bool TryGetImageName(T model, int id, [MaybeNullWhen(false)] out string value); +} + +public abstract class ImageTableNamer : IImageTableNameProvider, IImageTableNameProvider where T : ILocoStruct +{ + public abstract bool TryGetImageName(T model, int id, [MaybeNullWhen(false)] out string value); + + public bool TryGetImageName(T1 model, int id, [MaybeNullWhen(false)] out string value) where T1 : ILocoStruct + { + if (model is T tModel) + { + return TryGetImageName(tModel, id, out value); // call your specialized method + } + + value = DefaultImageTableNameProvider.GetImageName(id); + return false; + } } public class DefaultImageTableNameProvider : IImageTableNameProvider { - public bool TryGetImageName(int id, out string? value) + public static DefaultImageTableNameProvider Instance { get; } = new(); + + public bool TryGetImageName(T model, int id, [MaybeNullWhen(false)] out string value) where T : ILocoStruct { - value = id.ToString(); + value = GetImageName(id); return true; } + + public static string GetImageName(int id) + => $"{id}-unnamed"; } diff --git a/Definitions/ObjectModels/ILocoStruct.cs b/Definitions/ObjectModels/ILocoStruct.cs index ba8bf38d..f0f7f18d 100644 --- a/Definitions/ObjectModels/ILocoStruct.cs +++ b/Definitions/ObjectModels/ILocoStruct.cs @@ -1,6 +1,8 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels; public interface ILocoStruct { - bool Validate(); + IEnumerable Validate(ValidationContext validationContext); } diff --git a/Definitions/ObjectModels/ImageTable.cs b/Definitions/ObjectModels/ImageTable.cs new file mode 100644 index 00000000..44d2b9fa --- /dev/null +++ b/Definitions/ObjectModels/ImageTable.cs @@ -0,0 +1,37 @@ +using Definitions.ObjectModels.Types; + +namespace Definitions.ObjectModels; + +public class ImageTable : IHasGraphicsElements +{ + public PaletteMap PaletteMap + { + get; + set + { + ArgumentNullException.ThrowIfNull(value); + field = value; + + foreach (var g in Groups) + { + foreach (var ge in g.GraphicsElements) + { + if (!field.TryConvertG1ToRgba32Bitmap(ge, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var image)) + { + throw new Exception("Failed to convert image"); + } + ge.Image = image; + } + } + } + } + + // public/old interface + public List GraphicsElements + { + get => [.. Groups.SelectMany(x => x.GraphicsElements)]; + set => Groups.Add(("All", value)); + } + + public List<(string Name, List GraphicsElements)> Groups { get; set; } = []; +} diff --git a/Definitions/ObjectModels/ImageTableGrouper.cs b/Definitions/ObjectModels/ImageTableGrouper.cs new file mode 100644 index 00000000..73eb0994 --- /dev/null +++ b/Definitions/ObjectModels/ImageTableGrouper.cs @@ -0,0 +1,2129 @@ +using Definitions.ObjectModels.Objects.Cargo; +using Definitions.ObjectModels.Objects.CliffEdge; +using Definitions.ObjectModels.Objects.Competitor; +using Definitions.ObjectModels.Objects.HillShape; +using Definitions.ObjectModels.Objects.InterfaceSkin; +using Definitions.ObjectModels.Objects.Land; +using Definitions.ObjectModels.Objects.Road; +using Definitions.ObjectModels.Objects.RoadStation; +using Definitions.ObjectModels.Objects.Streetlight; +using Definitions.ObjectModels.Objects.Track; +using Definitions.ObjectModels.Objects.TrackExtra; +using Definitions.ObjectModels.Objects.TrackSignal; +using Definitions.ObjectModels.Objects.TrackStation; +using Definitions.ObjectModels.Objects.Wall; +using Definitions.ObjectModels.Objects.Water; +using Definitions.ObjectModels.Types; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Definitions.ObjectModels; + +public static class ImageTableNamer +{ + class CliffEdgeObjectNamer : ImageTableNamer + { + public static CliffEdgeObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(CliffEdgeObject model, int id, [MaybeNullWhen(false)] out string value) + { + if (id is >= 0 and <= 63) + { + var direction = id / 16 % 2 == 0 ? "west" : "east"; + var side = id is >= 16 and <= 47 ? "right" : "left"; + var level = id % 16; + value = $"south {direction} {side} {level}"; + return true; + } + + if (id is >= 64 and <= 69) + { + return ImageIdNameMap.TryGetValue(id, out value); + } + + value = null; + return false; + } + + static readonly Dictionary ImageIdNameMap = new() + { + { 64, "north west slope 1" }, + { 65, "north west slope 2" }, + { 66, "north west slope 3" }, + { 67, "north east slope 1" }, + { 68, "north east slope 2" }, + { 69, "north east slope 3" }, + }; + } + + class TrackSignalObjectNamer : ImageTableNamer + { + public static TrackSignalObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(TrackSignalObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 80, "redLights" }, + { 88, "redLights2" }, + { 96, "greenLights" }, + { 104, "greenLights2" }, + }; + } + + class WaterObjectNamer : ImageTableNamer + { + public static WaterObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(WaterObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "zoom1 wave overlay full" }, + { 1, "zoom1 wave overlay west" }, + { 2, "zoom1 wave overlay east" }, + { 3, "zoom1 wave overlay north" }, + { 4, "zoom1 wave overlay south" }, + { 5, "zoom1 wave overlay full" }, + { 6, "zoom1 wave half-tile west" }, + { 7, "zoom1 wave half-tile east" }, + { 8, "zoom1 wave half-tile north" }, + { 9, "zoom1 wave half-tile south" }, + { 10, "zoom2 wave overlay full" }, + { 11, "zoom2 wave overlay west" }, + { 12, "zoom2 wave overlay east" }, + { 13, "zoom2 wave overlay north" }, + { 14, "zoom2 wave overlay south" }, + { 15, "zoom2 wave overlay full" }, + { 16, "zoom2 wave half-tile west" }, + { 17, "zoom2 wave half-tile east" }, + { 18, "zoom2 wave half-tile north" }, + { 19, "zoom2 wave half-tile south" }, + { 20, "zoom3 wave overlay full" }, + { 21, "zoom3 wave overlay west" }, + { 22, "zoom3 wave overlay east" }, + { 23, "zoom3 wave overlay north" }, + { 24, "zoom3 wave overlay south" }, + { 25, "zoom3 wave overlay full" }, + { 26, "zoom3 wave half-tile west" }, + { 27, "zoom3 wave half-tile east" }, + { 28, "zoom3 wave half-tile north" }, + { 29, "zoom3 wave half-tile south" }, + { 30, "zoom4 wave overlay full" }, + { 31, "zoom4 wave overlay west" }, + { 32, "zoom4 wave overlay east" }, + { 33, "zoom4 wave overlay north" }, + { 34, "zoom4 wave overlay south" }, + { 35, "zoom4 wave overlay full" }, + { 36, "zoom4 wave half-tile west" }, + { 37, "zoom4 wave half-tile east" }, + { 38, "zoom4 wave half-tile north" }, + { 39, "zoom4 wave half-tile south" }, + { 40, "minimap palette" }, + { 41, "water colour palette" }, + { 42, "water icon animation 0" }, + { 43, "water icon animation 1" }, + { 44, "water icon animation 2" }, + { 45, "water icon animation 3" }, + { 46, "water icon animation 4" }, + { 47, "water icon animation 5" }, + { 48, "water icon animation 6" }, + { 49, "water icon animation 7" }, + { 50, "water icon animation 8" }, + { 51, "water icon animation 9" }, + { 52, "water icon animation 10" }, + { 54, "water icon animation 11" }, + { 55, "water icon animation 12" }, + { 56, "water icon animation 13" }, + { 57, "water icon animation 14" }, + { 53, "water icon animation 15" }, + { 58, "pick up water vehicle" }, + { 59, "place down water vehicle" }, + { 60, "water animation frame 0" }, + { 61, "water animation frame 1" }, + { 62, "water animation frame 2" }, + { 63, "water animation frame 3" }, + { 64, "water animation frame 4" }, + { 65, "water animation frame 5" }, + { 66, "water animation frame 6" }, + { 67, "water animation frame 7" }, + { 68, "water animation frame 8" }, + { 69, "water animation frame 9" }, + { 70, "water animation frame 10" }, + { 71, "water animation frame 11" }, + { 72, "water animation frame 12" }, + { 73, "water animation frame 13" }, + { 74, "water animation frame 14" }, + { 75, "water animation frame 15" }, + }; + } + + class WallObjectNamer : ImageTableNamer + { + public static WallObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(WallObject model, int id, [MaybeNullWhen(false)] out string value) + { + var result = ImageIdNameMap.TryGetValue(id, out value); + + if (id is >= 0 and <= 5 && model.Flags1.HasFlag(WallObjectFlags1.DoubleSided)) + { + value = $"{value} back"; + } + + if (id is >= 6 and <= 11 && model.Flags1.HasFlag(WallObjectFlags1.HasGlass)) + { + value = $"{value} glass overlay"; + } + else + { + value = $"{value} front"; + } + + return result; + } + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "Flat SE" }, + { 1, "Flat NE" }, + { 2, "Sloped SE" }, + { 3, "Sloped NE" }, + { 4, "Sloped NW" }, + { 5, "Sloped SW" }, + { 6, "Flat SE" }, + { 7, "Flat NE" }, + { 8, "Sloped SE" }, + { 9, "Sloped NE" }, + { 10, "Sloped NW" }, + { 11, "Sloped SW" }, + }; + } + + class InterfaceSkinObjectNamer : ImageTableNamer + { + public static InterfaceSkinObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(InterfaceSkinObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "preview_image" }, + { 1, "toolbar_pause" }, + { 2, "toolbar_pause_hover" }, + { 3, "toolbar_loadsave" }, + { 4, "toolbar_loadsave_hover" }, + { 5, "toolbar_zoom" }, + { 6, "toolbar_zoom_hover" }, + { 7, "toolbar_rotate" }, + { 8, "toolbar_rotate_hover" }, + { 9, "toolbar_terraform" }, + { 10, "toolbar_terraform_hover" }, + { 11, "toolbar_audio_active" }, + { 12, "toolbar_audio_active_hover" }, + { 13, "toolbar_audio_inactive" }, + { 14, "toolbar_audio_inactive_hover" }, + { 15, "toolbar_view" }, + { 16, "toolbar_view_hover" }, + { 17, "toolbar_towns" }, + { 18, "toolbar_towns_hover" }, + { 19, "toolbar_empty_opaque" }, + { 20, "toolbar_empty_opaque_hover" }, + { 21, "toolbar_empty_transparent" }, + { 22, "toolbar_empty_transparent_hover" }, + { 23, "toolbar_industries" }, + { 24, "toolbar_industries_hover" }, + { 25, "toolbar_airports" }, + { 26, "toolbar_airports_hover" }, + { 27, "toolbar_ports" }, + { 28, "toolbar_ports_hover" }, + { 29, "toolbar_cogwheels" }, + { 30, "toolbar_cogwheels_hover" }, + { 31, "toolbar_build_vehicle_train" }, + { 32, "toolbar_build_vehicle_train_hover" }, + { 33, "toolbar_build_vehicle_bus" }, + { 34, "toolbar_build_vehicle_bus_hover" }, + { 35, "toolbar_build_vehicle_truck" }, + { 36, "toolbar_build_vehicle_truck_hover" }, + { 37, "toolbar_build_vehicle_tram" }, + { 38, "toolbar_build_vehicle_tram_hover" }, + { 39, "toolbar_build_vehicle_airplane" }, + { 40, "toolbar_build_vehicle_airplane_hover" }, + { 41, "toolbar_build_vehicle_boat" }, + { 42, "toolbar_build_vehicle_boat_hover" }, + { 43, "toolbar_stations" }, + { 44, "toolbar_stations_hover" }, + { 45, "tab_awards" }, + { 46, "toolbar_menu_airport" }, + { 47, "toolbar_menu_ship_port" }, + { 48, "tab_cargo_ratings" }, + { 49, "tab_colour_scheme_frame0" }, + { 50, "tab_colour_scheme_frame1" }, + { 51, "tab_colour_scheme_frame2" }, + { 52, "tab_colour_scheme_frame3" }, + { 53, "tab_colour_scheme_frame4" }, + { 54, "tab_colour_scheme_frame5" }, + { 55, "tab_colour_scheme_frame6" }, + { 56, "tab_colour_scheme_frame7" }, + { 57, "tab_population_frame0" }, + { 58, "tab_population_frame1" }, + { 59, "tab_population_frame2" }, + { 60, "tab_population_frame3" }, + { 61, "tab_population_frame4" }, + { 62, "tab_population_frame5" }, + { 63, "tab_population_frame6" }, + { 64, "tab_population_frame7" }, + { 65, "tab_performance_index_frame0" }, + { 66, "tab_performance_index_frame1" }, + { 67, "tab_performance_index_frame2" }, + { 68, "tab_performance_index_frame3" }, + { 69, "tab_performance_index_frame4" }, + { 70, "tab_performance_index_frame5" }, + { 71, "tab_performance_index_frame6" }, + { 72, "tab_performance_index_frame7" }, + { 73, "tab_cargo_units_frame0" }, + { 74, "tab_cargo_units_frame1" }, + { 75, "tab_cargo_units_frame2" }, + { 76, "tab_cargo_units_frame3" }, + { 77, "tab_cargo_units_frame4" }, + { 78, "tab_cargo_units_frame5" }, + { 79, "tab_cargo_units_frame6" }, + { 80, "tab_cargo_units_frame7" }, + { 81, "tab_cargo_distance_frame0" }, + { 82, "tab_cargo_distance_frame1" }, + { 83, "tab_cargo_distance_frame2" }, + { 84, "tab_cargo_distance_frame3" }, + { 85, "tab_cargo_distance_frame4" }, + { 86, "tab_cargo_distance_frame5" }, + { 87, "tab_cargo_distance_frame6" }, + { 88, "tab_cargo_distance_frame7" }, + { 89, "tab_production_frame0" }, + { 90, "tab_production_frame1" }, + { 91, "tab_production_frame2" }, + { 92, "tab_production_frame3" }, + { 93, "tab_production_frame4" }, + { 94, "tab_production_frame5" }, + { 95, "tab_production_frame6" }, + { 96, "tab_production_frame7" }, + { 97, "tab_wrench_frame0" }, + { 98, "tab_wrench_frame1" }, + { 99, "tab_wrench_frame2" }, + { 100, "tab_wrench_frame3" }, + { 101, "tab_wrench_frame4" }, + { 102, "tab_wrench_frame5" }, + { 103, "tab_wrench_frame6" }, + { 104, "tab_wrench_frame7" }, + { 105, "tab_wrench_frame8" }, + { 106, "tab_wrench_frame9" }, + { 107, "tab_wrench_frame10" }, + { 108, "tab_wrench_frame11" }, + { 109, "tab_wrench_frame12" }, + { 110, "tab_wrench_frame13" }, + { 111, "tab_wrench_frame14" }, + { 112, "tab_wrench_frame15" }, + { 113, "tab_finances_frame0" }, + { 114, "tab_finances_frame1" }, + { 115, "tab_finances_frame2" }, + { 116, "tab_finances_frame3" }, + { 117, "tab_finances_frame4" }, + { 118, "tab_finances_frame5" }, + { 119, "tab_finances_frame6" }, + { 120, "tab_finances_frame7" }, + { 121, "tab_finances_frame8" }, + { 122, "tab_finances_frame9" }, + { 123, "tab_finances_frame10" }, + { 124, "tab_finances_frame11" }, + { 125, "tab_finances_frame12" }, + { 126, "tab_finances_frame13" }, + { 127, "tab_finances_frame14" }, + { 128, "tab_finances_frame15" }, + { 129, "tab_cup_frame0" }, + { 130, "tab_cup_frame1" }, + { 131, "tab_cup_frame2" }, + { 132, "tab_cup_frame3" }, + { 133, "tab_cup_frame4" }, + { 134, "tab_cup_frame5" }, + { 135, "tab_cup_frame6" }, + { 136, "tab_cup_frame7" }, + { 137, "tab_cup_frame8" }, + { 138, "tab_cup_frame9" }, + { 139, "tab_cup_frame10" }, + { 140, "tab_cup_frame11" }, + { 141, "tab_cup_frame12" }, + { 142, "tab_cup_frame13" }, + { 143, "tab_cup_frame14" }, + { 144, "tab_cup_frame15" }, + { 145, "tab_ratings_frame0" }, + { 146, "tab_ratings_frame1" }, + { 147, "tab_ratings_frame2" }, + { 148, "tab_ratings_frame3" }, + { 149, "tab_ratings_frame4" }, + { 150, "tab_ratings_frame5" }, + { 151, "tab_ratings_frame6" }, + { 152, "tab_ratings_frame7" }, + { 153, "tab_ratings_frame8" }, + { 154, "tab_ratings_frame9" }, + { 155, "tab_ratings_frame10" }, + { 156, "tab_ratings_frame11" }, + { 157, "tab_ratings_frame12" }, + { 158, "tab_ratings_frame13" }, + { 159, "tab_ratings_frame14" }, + { 160, "tab_ratings_frame15" }, + { 161, "tab_transported_frame0" }, + { 162, "tab_transported_frame1" }, + { 163, "tab_transported_frame2" }, + { 164, "tab_transported_frame3" }, + { 165, "tab_transported_frame4" }, + { 166, "tab_transported_frame5" }, + { 167, "tab_transported_frame6" }, + { 168, "tab_cogs_frame0" }, + { 169, "tab_cogs_frame1" }, + { 170, "tab_cogs_frame2" }, + { 171, "tab_cogs_frame3" }, + { 172, "tab_scenario_details" }, + { 173, "tab_company" }, + { 174, "tab_companies" }, + { 175, "toolbar_menu_zoom_in" }, + { 176, "toolbar_menu_zoom_out" }, + { 177, "toolbar_menu_rotate_clockwise" }, + { 178, "toolbar_menu_rotate_anti_clockwise" }, + { 179, "toolbar_menu_plant_trees" }, + { 180, "toolbar_menu_bulldozer" }, + { 181, "tab_company_details" }, + { 182, "all_stations" }, + { 183, "rail_stations" }, + { 184, "road_stations" }, + { 185, "airports" }, + { 186, "ship_ports" }, + { 187, "toolbar_menu_build_walls" }, + { 188, "phone" }, + { 189, "toolbar_menu_towns" }, + { 190, "toolbar_menu_stations" }, + { 191, "toolbar_menu_industries" }, + { 192, "tab_routes_frame_0" }, + { 193, "tab_routes_frame_1" }, + { 194, "tab_routes_frame_2" }, + { 195, "tab_routes_frame_3" }, + { 196, "tab_messages" }, + { 197, "tab_message_settings" }, + { 198, "tab_cargo_delivered_frame0" }, + { 199, "tab_cargo_delivered_frame1" }, + { 200, "tab_cargo_delivered_frame2" }, + { 201, "tab_cargo_delivered_frame3" }, + { 202, "tab_cargo_payment_rates" }, + { 203, "tab_vehicle_train_frame0" }, + { 204, "tab_vehicle_train_frame1" }, + { 205, "tab_vehicle_train_frame2" }, + { 206, "tab_vehicle_train_frame3" }, + { 207, "tab_vehicle_train_frame4" }, + { 208, "tab_vehicle_train_frame5" }, + { 209, "tab_vehicle_train_frame6" }, + { 210, "tab_vehicle_train_frame7" }, + { 211, "tab_vehicle_aircraft_frame0" }, + { 212, "tab_vehicle_aircraft_frame1" }, + { 213, "tab_vehicle_aircraft_frame2" }, + { 214, "tab_vehicle_aircraft_frame3" }, + { 215, "tab_vehicle_aircraft_frame4" }, + { 216, "tab_vehicle_aircraft_frame5" }, + { 217, "tab_vehicle_aircraft_frame6" }, + { 218, "tab_vehicle_aircraft_frame7" }, + { 219, "tab_vehicle_bus_frame0" }, + { 220, "tab_vehicle_bus_frame1" }, + { 221, "tab_vehicle_bus_frame2" }, + { 222, "tab_vehicle_bus_frame3" }, + { 223, "tab_vehicle_bus_frame4" }, + { 224, "tab_vehicle_bus_frame5" }, + { 225, "tab_vehicle_bus_frame6" }, + { 226, "tab_vehicle_bus_frame7" }, + { 227, "tab_vehicle_tram_frame0" }, + { 228, "tab_vehicle_tram_frame1" }, + { 229, "tab_vehicle_tram_frame2" }, + { 230, "tab_vehicle_tram_frame3" }, + { 231, "tab_vehicle_tram_frame4" }, + { 232, "tab_vehicle_tram_frame5" }, + { 233, "tab_vehicle_tram_frame6" }, + { 234, "tab_vehicle_tram_frame7" }, + { 235, "tab_vehicle_truck_frame0" }, + { 236, "tab_vehicle_truck_frame1" }, + { 237, "tab_vehicle_truck_frame2" }, + { 238, "tab_vehicle_truck_frame3" }, + { 239, "tab_vehicle_truck_frame4" }, + { 240, "tab_vehicle_truck_frame5" }, + { 241, "tab_vehicle_truck_frame6" }, + { 242, "tab_vehicle_truck_frame7" }, + { 243, "tab_vehicle_ship_frame0" }, + { 244, "tab_vehicle_ship_frame1" }, + { 245, "tab_vehicle_ship_frame2" }, + { 246, "tab_vehicle_ship_frame3" }, + { 247, "tab_vehicle_ship_frame4" }, + { 248, "tab_vehicle_ship_frame5" }, + { 249, "tab_vehicle_ship_frame6" }, + { 250, "tab_vehicle_ship_frame7" }, + { 251, "build_vehicle_train_frame_0" }, + { 252, "build_vehicle_train_frame_1" }, + { 253, "build_vehicle_train_frame_2" }, + { 254, "build_vehicle_train_frame_3" }, + { 255, "build_vehicle_train_frame_4" }, + { 256, "build_vehicle_train_frame_5" }, + { 257, "build_vehicle_train_frame_6" }, + { 258, "build_vehicle_train_frame_7" }, + { 259, "build_vehicle_train_frame_8" }, + { 260, "build_vehicle_train_frame_9" }, + { 261, "build_vehicle_train_frame_10" }, + { 262, "build_vehicle_train_frame_11" }, + { 263, "build_vehicle_train_frame_12" }, + { 264, "build_vehicle_train_frame_13" }, + { 265, "build_vehicle_train_frame_14" }, + { 266, "build_vehicle_train_frame_15" }, + { 267, "build_vehicle_aircraft_frame_0" }, + { 268, "build_vehicle_aircraft_frame_1" }, + { 269, "build_vehicle_aircraft_frame_2" }, + { 270, "build_vehicle_aircraft_frame_3" }, + { 271, "build_vehicle_aircraft_frame_4" }, + { 272, "build_vehicle_aircraft_frame_5" }, + { 273, "build_vehicle_aircraft_frame_6" }, + { 274, "build_vehicle_aircraft_frame_7" }, + { 275, "build_vehicle_aircraft_frame_8" }, + { 276, "build_vehicle_aircraft_frame_9" }, + { 277, "build_vehicle_aircraft_frame_10" }, + { 278, "build_vehicle_aircraft_frame_11" }, + { 279, "build_vehicle_aircraft_frame_12" }, + { 280, "build_vehicle_aircraft_frame_13" }, + { 281, "build_vehicle_aircraft_frame_14" }, + { 282, "build_vehicle_aircraft_frame_15" }, + { 283, "build_vehicle_bus_frame_0" }, + { 284, "build_vehicle_bus_frame_1" }, + { 285, "build_vehicle_bus_frame_2" }, + { 286, "build_vehicle_bus_frame_3" }, + { 287, "build_vehicle_bus_frame_4" }, + { 288, "build_vehicle_bus_frame_5" }, + { 289, "build_vehicle_bus_frame_6" }, + { 290, "build_vehicle_bus_frame_7" }, + { 291, "build_vehicle_bus_frame_8" }, + { 292, "build_vehicle_bus_frame_9" }, + { 293, "build_vehicle_bus_frame_10" }, + { 294, "build_vehicle_bus_frame_11" }, + { 295, "build_vehicle_bus_frame_12" }, + { 296, "build_vehicle_bus_frame_13" }, + { 297, "build_vehicle_bus_frame_14" }, + { 298, "build_vehicle_bus_frame_15" }, + { 299, "build_vehicle_tram_frame_0" }, + { 300, "build_vehicle_tram_frame_1" }, + { 301, "build_vehicle_tram_frame_2" }, + { 302, "build_vehicle_tram_frame_3" }, + { 303, "build_vehicle_tram_frame_4" }, + { 304, "build_vehicle_tram_frame_5" }, + { 305, "build_vehicle_tram_frame_6" }, + { 306, "build_vehicle_tram_frame_7" }, + { 307, "build_vehicle_tram_frame_8" }, + { 308, "build_vehicle_tram_frame_9" }, + { 309, "build_vehicle_tram_frame_10" }, + { 310, "build_vehicle_tram_frame_11" }, + { 311, "build_vehicle_tram_frame_12" }, + { 312, "build_vehicle_tram_frame_13" }, + { 313, "build_vehicle_tram_frame_14" }, + { 314, "build_vehicle_tram_frame_15" }, + { 315, "build_vehicle_truck_frame_0" }, + { 316, "build_vehicle_truck_frame_1" }, + { 317, "build_vehicle_truck_frame_2" }, + { 318, "build_vehicle_truck_frame_3" }, + { 319, "build_vehicle_truck_frame_4" }, + { 320, "build_vehicle_truck_frame_5" }, + { 321, "build_vehicle_truck_frame_6" }, + { 322, "build_vehicle_truck_frame_7" }, + { 323, "build_vehicle_truck_frame_8" }, + { 324, "build_vehicle_truck_frame_9" }, + { 325, "build_vehicle_truck_frame_10" }, + { 326, "build_vehicle_truck_frame_11" }, + { 327, "build_vehicle_truck_frame_12" }, + { 328, "build_vehicle_truck_frame_13" }, + { 329, "build_vehicle_truck_frame_14" }, + { 330, "build_vehicle_truck_frame_15" }, + { 331, "build_vehicle_ship_frame_0" }, + { 332, "build_vehicle_ship_frame_1" }, + { 333, "build_vehicle_ship_frame_2" }, + { 334, "build_vehicle_ship_frame_3" }, + { 335, "build_vehicle_ship_frame_4" }, + { 336, "build_vehicle_ship_frame_5" }, + { 337, "build_vehicle_ship_frame_6" }, + { 338, "build_vehicle_ship_frame_7" }, + { 339, "build_vehicle_ship_frame_8" }, + { 340, "build_vehicle_ship_frame_9" }, + { 341, "build_vehicle_ship_frame_10" }, + { 342, "build_vehicle_ship_frame_11" }, + { 343, "build_vehicle_ship_frame_12" }, + { 344, "build_vehicle_ship_frame_13" }, + { 345, "build_vehicle_ship_frame_14" }, + { 346, "build_vehicle_ship_frame_15" }, + { 347, "build_industry_frame_0" }, + { 348, "build_industry_frame_1" }, + { 349, "build_industry_frame_2" }, + { 350, "build_industry_frame_3" }, + { 351, "build_industry_frame_4" }, + { 352, "build_industry_frame_5" }, + { 353, "build_industry_frame_6" }, + { 354, "build_industry_frame_7" }, + { 355, "build_industry_frame_8" }, + { 356, "build_industry_frame_9" }, + { 357, "build_industry_frame_10" }, + { 358, "build_industry_frame_11" }, + { 359, "build_industry_frame_12" }, + { 360, "build_industry_frame_13" }, + { 361, "build_industry_frame_14" }, + { 362, "build_industry_frame_15" }, + { 363, "build_town_frame_0" }, + { 364, "build_town_frame_1" }, + { 365, "build_town_frame_2" }, + { 366, "build_town_frame_3" }, + { 367, "build_town_frame_4" }, + { 368, "build_town_frame_5" }, + { 369, "build_town_frame_6" }, + { 370, "build_town_frame_7" }, + { 371, "build_town_frame_8" }, + { 372, "build_town_frame_9" }, + { 373, "build_town_frame_10" }, + { 374, "build_town_frame_11" }, + { 375, "build_town_frame_12" }, + { 376, "build_town_frame_13" }, + { 377, "build_town_frame_14" }, + { 378, "build_town_frame_15" }, + { 379, "build_buildings_frame_0" }, + { 380, "build_buildings_frame_1" }, + { 381, "build_buildings_frame_2" }, + { 382, "build_buildings_frame_3" }, + { 383, "build_buildings_frame_4" }, + { 384, "build_buildings_frame_5" }, + { 385, "build_buildings_frame_6" }, + { 386, "build_buildings_frame_7" }, + { 387, "build_buildings_frame_8" }, + { 388, "build_buildings_frame_9" }, + { 389, "build_buildings_frame_10" }, + { 390, "build_buildings_frame_11" }, + { 391, "build_buildings_frame_12" }, + { 392, "build_buildings_frame_13" }, + { 393, "build_buildings_frame_14" }, + { 394, "build_buildings_frame_15" }, + { 395, "build_misc_buildings_frame_0" }, + { 396, "build_misc_buildings_frame_1" }, + { 397, "build_misc_buildings_frame_2" }, + { 398, "build_misc_buildings_frame_3" }, + { 399, "build_misc_buildings_frame_4" }, + { 400, "build_misc_buildings_frame_5" }, + { 401, "build_misc_buildings_frame_6" }, + { 402, "build_misc_buildings_frame_7" }, + { 403, "build_misc_buildings_frame_8" }, + { 404, "build_misc_buildings_frame_9" }, + { 405, "build_misc_buildings_frame_10" }, + { 406, "build_misc_buildings_frame_11" }, + { 407, "build_misc_buildings_frame_12" }, + { 408, "build_misc_buildings_frame_13" }, + { 409, "build_misc_buildings_frame_14" }, + { 410, "build_misc_buildings_frame_15" }, + { 411, "build_additional_train" }, + { 412, "build_additional_bus" }, + { 413, "build_additional_truck" }, + { 414, "build_additional_tram" }, + { 415, "build_additional_aircraft" }, + { 416, "build_additional_ship" }, + { 417, "build_headquarters" }, + { 418, "vehicle_train_frame_0" }, + { 419, "vehicle_train_frame_1" }, + { 420, "vehicle_train_frame_2" }, + { 421, "vehicle_train_frame_3" }, + { 422, "vehicle_train_frame_4" }, + { 423, "vehicle_train_frame_5" }, + { 424, "vehicle_train_frame_6" }, + { 425, "vehicle_train_frame_7" }, + { 426, "vehicle_aircraft_frame_0" }, + { 427, "vehicle_aircraft_frame_1" }, + { 428, "vehicle_aircraft_frame_2" }, + { 429, "vehicle_aircraft_frame_3" }, + { 430, "vehicle_aircraft_frame_4" }, + { 431, "vehicle_aircraft_frame_5" }, + { 432, "vehicle_aircraft_frame_6" }, + { 433, "vehicle_aircraft_frame_7" }, + { 434, "vehicle_buses_frame_0" }, + { 435, "vehicle_buses_frame_1" }, + { 436, "vehicle_buses_frame_2" }, + { 437, "vehicle_buses_frame_3" }, + { 438, "vehicle_buses_frame_4" }, + { 439, "vehicle_buses_frame_5" }, + { 440, "vehicle_buses_frame_6" }, + { 441, "vehicle_buses_frame_7" }, + { 442, "vehicle_trams_frame_0" }, + { 443, "vehicle_trams_frame_1" }, + { 444, "vehicle_trams_frame_2" }, + { 445, "vehicle_trams_frame_3" }, + { 446, "vehicle_trams_frame_4" }, + { 447, "vehicle_trams_frame_5" }, + { 448, "vehicle_trams_frame_6" }, + { 449, "vehicle_trams_frame_7" }, + { 450, "vehicle_trucks_frame_0" }, + { 451, "vehicle_trucks_frame_1" }, + { 452, "vehicle_trucks_frame_2" }, + { 453, "vehicle_trucks_frame_3" }, + { 454, "vehicle_trucks_frame_4" }, + { 455, "vehicle_trucks_frame_5" }, + { 456, "vehicle_trucks_frame_6" }, + { 457, "vehicle_trucks_frame_7" }, + { 458, "vehicle_ships_frame_0" }, + { 459, "vehicle_ships_frame_1" }, + { 460, "vehicle_ships_frame_2" }, + { 461, "vehicle_ships_frame_3" }, + { 462, "vehicle_ships_frame_4" }, + { 463, "vehicle_ships_frame_5" }, + { 464, "vehicle_ships_frame_6" }, + { 465, "vehicle_ships_frame_7" }, + { 466, "toolbar_menu_map_north" }, + { 467, "toolbar_menu_map_west" }, + { 468, "toolbar_menu_map_south" }, + { 469, "toolbar_menu_map_east" }, + }; + } + + class CargoObjectNamer : ImageTableNamer + { + public static CargoObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(CargoObject model, int id, [MaybeNullWhen(false)] out string value) + { + value = id == 0 + ? "kInlineSprite" + : $"kStationPlatform{id}"; + + return true; + } + } + + class CompetitorObjectNamer : ImageTableNamer + { + public static CompetitorObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(CompetitorObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "smallNeutral" }, + { 1, "largeNeutral" }, + { 2, "smallHappy" }, + { 3, "largeHappy" }, + { 4, "smallWorried" }, + { 5, "largeWorried" }, + { 6, "smallThinking" }, + { 7, "largeThinking" }, + { 8, "smallDejected" }, + { 9, "largeDejected" }, + { 10, "smallSurprised" }, + { 11, "largeSurprised" }, + { 12, "smallScared" }, + { 13, "largeScared" }, + { 14, "smallAngry" }, + { 15, "largeAngry" }, + { 16, "smallDisgusted" }, + { 17, "largeDisgusted" }, + }; + } + + class HillShapesObjectNamer : ImageTableNamer + { + public static HillShapesObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(HillShapesObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "hill shape 1" }, + { 1, "hill shape 2" }, + { 2, "mountain shape 1" }, + { 3, "mountain shape 2" }, + { 4, "preview image" }, + + }; + } + + class LandObjectNamer : ImageTableNamer + { + public static LandObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(LandObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "flat" }, + { 1, "west corner up" }, + { 2, "south corner up" }, + { 3, "north east slope" }, + { 4, "east corner up" }, + { 5, "west and east corner up" }, + { 6, "north west slope" }, + { 7, "north corner down" }, + { 8, "north corner up" }, + { 9, "south east slope" }, + { 10, "north and south corners up" }, + { 11, "east corner down" }, + { 12, "north west slope" }, + { 13, "south corner down" }, + { 14, "west corner down" }, + { 15, "south slope" }, + { 16, "north slope" }, + { 17, "east slope" }, + { 18, "west slope" } + }; + } + + class StreetLightObjectNamer : ImageTableNamer + { + public static StreetLightObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(StreetLightObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "style0NE" }, + { 1, "style0SE" }, + { 2, "style0SW" }, + { 3, "style0NW" }, + { 4, "style1NE" }, + { 5, "style1SE" }, + { 6, "style1SW" }, + { 7, "style1NW" }, + { 8, "style2NE" }, + { 9, "style2SE" }, + { 10, "style2SW" }, + { 11, "style2NW" }, + }; + } + + class RoadStationObjectNamer : ImageTableNamer + { + public static RoadStationObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(RoadStationObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "preview_image" }, + { 1, "preview_image_glass_overlay" }, + { 2, "North West Back Wall" }, + { 3, "North West Front Platform" }, + { 4, "North West Front Wall/Roof" }, + { 5, "North West Glass Overlay" }, + { 6, "South West Back Wall" }, + { 7, "South West Front Platform" }, + { 8, "South West Front Wall/Roof" }, + { 9, "South West Glass Overlay" }, + { 10, "South East Back Wall" }, + { 11, "South East Front Platform" }, + { 12, "South East Front Wall/Roof" }, + { 13, "South East Glass Overlay" }, + { 14, "North East Back Wall" }, + { 15, "North East Front Platform" }, + { 16, "North East Front Wall/Roof" }, + { 17, "North East Glass Overlay" }, + }; + } + + class TrackStationObjectNamer : ImageTableNamer + { + public static TrackStationObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(TrackStationObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "preview_image" }, + { 1, "preview_image_glass_overlay" }, + { 2, "totalPreviewImages" }, + }; + + // These are relative to ImageOffsets + // ImageOffsets is the imageIds per sequenceIndex (for start/middle/end of the platform) + //namespace Style0 + //{ + // constexpr uint32_t straightBackNE = 0; + // constexpr uint32_t straightFrontNE = 1; + // constexpr uint32_t straightCanopyNE = 2; + // constexpr uint32_t straightCanopyTranslucentNE = 3; + // constexpr uint32_t straightBackSE = 4; + // constexpr uint32_t straightFrontSE = 5; + // constexpr uint32_t straightCanopySE = 6; + // constexpr uint32_t straightCanopyTranslucentSE = 7; + // constexpr uint32_t diagonalNE0 = 8; + // constexpr uint32_t diagonalNE3 = 9; + // constexpr uint32_t diagonalNE1 = 10; + // constexpr uint32_t diagonalCanopyNE1 = 11; + // constexpr uint32_t diagonalCanopyTranslucentNE1 = 12; + // constexpr uint32_t diagonalSE1 = 13; + // constexpr uint32_t diagonalSE2 = 14; + // constexpr uint32_t diagonalSE3 = 15; + // constexpr uint32_t diagonalCanopyTranslucentSE3 = 16; + // constexpr uint32_t totalNumImages = 17; + //} + //namespace Style1 + //{ + // constexpr uint32_t totalNumImages = 8; + //} + } + + class TrackExtraObjectNamer : ImageTableNamer + { + public static TrackExtraObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(TrackExtraObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id - 8, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { -8, "previewImage0" }, + { -7, "previewImage1" }, + { -6, "previewImage2" }, + { -5, "previewImage3" }, + { -4, "previewImage4" }, + { -3, "previewImage5" }, + { -2, "previewImage6" }, + { -1, "previewImage7" }, + { 0, "kStraight0NE" }, + { 1, "kStraight0SE" }, + { 2, "kStraight0SW" }, + { 3, "kStraight0NW" }, + { 4, "kRightCurveSmall0NE" }, + { 5, "kRightCurveSmall1NE" }, + { 6, "kRightCurveSmall2NE" }, + { 7, "kRightCurveSmall3NE" }, + { 8, "kRightCurveSmall0SE" }, + { 9, "kRightCurveSmall1SE" }, + { 10, "kRightCurveSmall2SE" }, + { 11, "kRightCurveSmall3SE" }, + { 12, "kRightCurveSmall0SW" }, + { 13, "kRightCurveSmall1SW" }, + { 14, "kRightCurveSmall2SW" }, + { 15, "kRightCurveSmall3SW" }, + { 16, "kRightCurveSmall0NW" }, + { 17, "kRightCurveSmall1NW" }, + { 18, "kRightCurveSmall2NW" }, + { 19, "kRightCurveSmall3NW" }, + { 20, "kRightCurve0NE" }, + { 21, "kRightCurve1NE" }, + { 22, "kRightCurve2NE" }, + { 23, "kRightCurve3NE" }, + { 24, "kRightCurve4NE" }, + { 25, "kRightCurve0SE" }, + { 26, "kRightCurve1SE" }, + { 27, "kRightCurve2SE" }, + { 28, "kRightCurve3SE" }, + { 29, "kRightCurve4SE" }, + { 30, "kRightCurve0SW" }, + { 31, "kRightCurve1SW" }, + { 32, "kRightCurve2SW" }, + { 33, "kRightCurve3SW" }, + { 34, "kRightCurve4SW" }, + { 35, "kRightCurve0NW" }, + { 36, "kRightCurve1NW" }, + { 37, "kRightCurve2NW" }, + { 38, "kRightCurve3NW" }, + { 39, "kRightCurve4NW" }, + { 40, "kSBendLeft0NE" }, + { 41, "kSBendLeft1NE" }, + { 42, "kSBendLeft2NE" }, + { 43, "kSBendLeft3NE" }, + { 44, "kSBendLeft0SE" }, + { 45, "kSBendLeft1SE" }, + { 46, "kSBendLeft2SE" }, + { 47, "kSBendLeft3SE" }, + { 48, "kSBendLeft3SW" }, + { 49, "kSBendLeft2SW" }, + { 50, "kSBendLeft1SW" }, + { 51, "kSBendLeft0SW" }, + { 52, "kSBendLeft3NW" }, + { 53, "kSBendLeft2NW" }, + { 54, "kSBendLeft1NW" }, + { 55, "kSBendLeft0NW" }, + { 56, "kSBendRight0NE" }, + { 57, "kSBendRight1NE" }, + { 58, "kSBendRight2NE" }, + { 59, "kSBendRight3NE" }, + { 60, "kSBendRight0SE" }, + { 61, "kSBendRight1SE" }, + { 62, "kSBendRight2SE" }, + { 63, "kSBendRight3SE" }, + { 64, "kSBendRight3SW" }, + { 65, "kSBendRight2SW" }, + { 66, "kSBendRight1SW" }, + { 67, "kSBendRight0SW" }, + { 68, "kSBendRight3NW" }, + { 69, "kSBendRight2NW" }, + { 70, "kSBendRight1NW" }, + { 71, "kSBendRight0NW" }, + { 72, "kStraightSlopeUp0NE" }, + { 73, "kStraightSlopeUp1NE" }, + { 74, "kStraightSlopeUp0SE" }, + { 75, "kStraightSlopeUp1SE" }, + { 76, "kStraightSlopeUp0SW" }, + { 77, "kStraightSlopeUp1SW" }, + { 78, "kStraightSlopeUp0NW" }, + { 79, "kStraightSlopeUp1NW" }, + { 80, "kStraightSteepSlopeUp0NE" }, + { 81, "kStraightSteepSlopeUp0SE" }, + { 82, "kStraightSteepSlopeUp0SW" }, + { 83, "kStraightSteepSlopeUp0NW" }, + { 84, "kRightCurveSmallSlopeUp0NE" }, + { 85, "kRightCurveSmallSlopeUp1NE" }, + { 86, "kRightCurveSmallSlopeUp2NE" }, + { 87, "kRightCurveSmallSlopeUp3NE" }, + { 88, "kRightCurveSmallSlopeUp0SE" }, + { 89, "kRightCurveSmallSlopeUp1SE" }, + { 90, "kRightCurveSmallSlopeUp2SE" }, + { 91, "kRightCurveSmallSlopeUp3SE" }, + { 92, "kRightCurveSmallSlopeUp0SW" }, + { 93, "kRightCurveSmallSlopeUp1SW" }, + { 94, "kRightCurveSmallSlopeUp2SW" }, + { 95, "kRightCurveSmallSlopeUp3SW" }, + { 96, "kRightCurveSmallSlopeUp0NW" }, + { 97, "kRightCurveSmallSlopeUp1NW" }, + { 98, "kRightCurveSmallSlopeUp2NW" }, + { 99, "kRightCurveSmallSlopeUp3NW" }, + { 100, "kRightCurveSmallSlopeDown0NE" }, + { 101, "kRightCurveSmallSlopeDown1NE" }, + { 102, "kRightCurveSmallSlopeDown2NE" }, + { 103, "kRightCurveSmallSlopeDown3NE" }, + { 104, "kRightCurveSmallSlopeDown0SE" }, + { 105, "kRightCurveSmallSlopeDown1SE" }, + { 106, "kRightCurveSmallSlopeDown2SE" }, + { 107, "kRightCurveSmallSlopeDown3SE" }, + { 108, "kRightCurveSmallSlopeDown0SW" }, + { 109, "kRightCurveSmallSlopeDown1SW" }, + { 110, "kRightCurveSmallSlopeDown2SW" }, + { 111, "kRightCurveSmallSlopeDown3SW" }, + { 112, "kRightCurveSmallSlopeDown0NW" }, + { 113, "kRightCurveSmallSlopeDown1NW" }, + { 114, "kRightCurveSmallSlopeDown2NW" }, + { 115, "kRightCurveSmallSlopeDown3NW" }, + { 116, "kRightCurveSmallSteepSlopeUp0NE" }, + { 117, "kRightCurveSmallSteepSlopeUp1NE" }, + { 118, "kRightCurveSmallSteepSlopeUp2NE" }, + { 119, "kRightCurveSmallSteepSlopeUp3NE" }, + { 120, "kRightCurveSmallSteepSlopeUp0SE" }, + { 121, "kRightCurveSmallSteepSlopeUp1SE" }, + { 122, "kRightCurveSmallSteepSlopeUp2SE" }, + { 123, "kRightCurveSmallSteepSlopeUp3SE" }, + { 124, "kRightCurveSmallSteepSlopeUp0SW" }, + { 125, "kRightCurveSmallSteepSlopeUp1SW" }, + { 126, "kRightCurveSmallSteepSlopeUp2SW" }, + { 127, "kRightCurveSmallSteepSlopeUp3SW" }, + { 128, "kRightCurveSmallSteepSlopeUp0NW" }, + { 129, "kRightCurveSmallSteepSlopeUp1NW" }, + { 130, "kRightCurveSmallSteepSlopeUp2NW" }, + { 131, "kRightCurveSmallSteepSlopeUp3NW" }, + { 132, "kRightCurveSmallSteepSlopeDown0NE" }, + { 133, "kRightCurveSmallSteepSlopeDown1NE" }, + { 134, "kRightCurveSmallSteepSlopeDown2NE" }, + { 135, "kRightCurveSmallSteepSlopeDown3NE" }, + { 136, "kRightCurveSmallSteepSlopeDown0SE" }, + { 137, "kRightCurveSmallSteepSlopeDown1SE" }, + { 138, "kRightCurveSmallSteepSlopeDown2SE" }, + { 139, "kRightCurveSmallSteepSlopeDown3SE" }, + { 140, "kRightCurveSmallSteepSlopeDown0SW" }, + { 141, "kRightCurveSmallSteepSlopeDown1SW" }, + { 142, "kRightCurveSmallSteepSlopeDown2SW" }, + { 143, "kRightCurveSmallSteepSlopeDown3SW" }, + { 144, "kRightCurveSmallSteepSlopeDown0NW" }, + { 145, "kRightCurveSmallSteepSlopeDown1NW" }, + { 146, "kRightCurveSmallSteepSlopeDown2NW" }, + { 147, "kRightCurveSmallSteepSlopeDown3NW" }, + { 148, "kRightCurveLarge0NE" }, + { 149, "kRightCurveLarge1NE" }, + { 150, "kRightCurveLarge2NE" }, + { 151, "kRightCurveLarge3NE" }, + { 152, "kRightCurveLarge4NE" }, + { 153, "kRightCurveLarge0SE" }, + { 154, "kRightCurveLarge1SE" }, + { 155, "kRightCurveLarge2SE" }, + { 156, "kRightCurveLarge3SE" }, + { 157, "kRightCurveLarge4SE" }, + { 158, "kRightCurveLarge0SW" }, + { 159, "kRightCurveLarge1SW" }, + { 160, "kRightCurveLarge2SW" }, + { 161, "kRightCurveLarge3SW" }, + { 162, "kRightCurveLarge4SW" }, + { 163, "kRightCurveLarge0NW" }, + { 164, "kRightCurveLarge1NW" }, + { 165, "kRightCurveLarge2NW" }, + { 166, "kRightCurveLarge3NW" }, + { 167, "kRightCurveLarge4NW" }, + { 168, "kLeftCurveLarge0NE" }, + { 169, "kLeftCurveLarge1NE" }, + { 170, "kLeftCurveLarge2NE" }, + { 171, "kLeftCurveLarge3NE" }, + { 172, "kLeftCurveLarge4NE" }, + { 173, "kLeftCurveLarge0SE" }, + { 174, "kLeftCurveLarge1SE" }, + { 175, "kLeftCurveLarge2SE" }, + { 176, "kLeftCurveLarge3SE" }, + { 177, "kLeftCurveLarge4SE" }, + { 178, "kLeftCurveLarge0SW" }, + { 179, "kLeftCurveLarge1SW" }, + { 180, "kLeftCurveLarge2SW" }, + { 181, "kLeftCurveLarge3SW" }, + { 182, "kLeftCurveLarge4SW" }, + { 183, "kLeftCurveLarge0NW" }, + { 184, "kLeftCurveLarge1NW" }, + { 185, "kLeftCurveLarge2NW" }, + { 186, "kLeftCurveLarge3NW" }, + { 187, "kLeftCurveLarge4NW" }, + { 188, "kDiagonal0NE" }, + { 189, "kDiagonal2NE" }, + { 190, "kDiagonal1NE" }, + { 191, "kDiagonal3NE" }, + { 192, "kDiagonal0SE" }, + { 193, "kDiagonal2SE" }, + { 194, "kDiagonal1SE" }, + { 195, "kDiagonal3SE" }, + { 196, "kDiagonal0SW" }, + { 197, "kDiagonal2SW" }, + { 198, "kDiagonal1SW" }, + { 199, "kDiagonal3SW" }, + { 200, "kDiagonal0NW" }, + { 201, "kDiagonal2NW" }, + { 202, "kDiagonal1NW" }, + { 203, "kDiagonal3NW" }, + { 204, "kRightCurveVerySmall0NE" }, + { 205, "kRightCurveVerySmall0SE" }, + { 206, "kRightCurveVerySmall0SW" }, + { 207, "kRightCurveVerySmall0NW" }, + }; + } + + class TrackObjectNamer : ImageTableNamer + { + public static TrackObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(TrackObject model, int id, [MaybeNullWhen(false)] out string value) + => ImageIdNameMap.TryGetValue(id, out value); + + static readonly Dictionary ImageIdNameMap = new() + { + { 0, "uiPreviewImage0" }, + { 1, "uiPreviewImage1" }, + { 2, "uiPreviewImage2" }, + { 3, "uiPreviewImage3" }, + { 4, "uiPreviewImage4" }, + { 5, "uiPreviewImage5" }, + { 6, "uiPreviewImage6" }, + { 7, "uiPreviewImage7" }, + { 8, "uiPreviewImage8" }, + { 9, "uiPreviewImage9" }, + { 10, "uiPreviewImage10" }, + { 11, "uiPreviewImage11" }, + { 12, "uiPreviewImage12" }, + { 13, "uiPreviewImage13" }, + { 14, "uiPreviewImage14" }, + { 15, "uiPreviewImage15" }, + { 16, "uiPickupFromTrack" }, + { 17, "uiPlaceOnTrack" }, + // + { 18, "straight0BallastNE" }, + { 19, "straight0BallastSE" }, + { 20, "straight0SleeperNE" }, + { 21, "straight0SleeperSE" }, + { 22, "straight0RailNE" }, + { 23, "straight0RailSE" }, + { 24, "rightCurveSmall0BallastNE" }, + { 25, "rightCurveSmall1BallastNE" }, + { 26, "rightCurveSmall2BallastNE" }, + { 27, "rightCurveSmall3BallastNE" }, + { 28, "rightCurveSmall0BallastSE" }, + { 29, "rightCurveSmall1BallastSE" }, + { 30, "rightCurveSmall2BallastSE" }, + { 31, "rightCurveSmall3BallastSE" }, + { 32, "rightCurveSmall0BallastSW" }, + { 33, "rightCurveSmall1BallastSW" }, + { 34, "rightCurveSmall2BallastSW" }, + { 35, "rightCurveSmall3BallastSW" }, + { 36, "rightCurveSmall0BallastNW" }, + { 37, "rightCurveSmall1BallastNW" }, + { 38, "rightCurveSmall2BallastNW" }, + { 39, "rightCurveSmall3BallastNW" }, + { 40, "rightCurveSmall0SleeperNE" }, + { 41, "rightCurveSmall1SleeperNE" }, + { 42, "rightCurveSmall2SleeperNE" }, + { 43, "rightCurveSmall3SleeperNE" }, + { 44, "rightCurveSmall0SleeperSE" }, + { 45, "rightCurveSmall1SleeperSE" }, + { 46, "rightCurveSmall2SleeperSE" }, + { 47, "rightCurveSmall3SleeperSE" }, + { 48, "rightCurveSmall0SleeperSW" }, + { 49, "rightCurveSmall1SleeperSW" }, + { 50, "rightCurveSmall2SleeperSW" }, + { 51, "rightCurveSmall3SleeperSW" }, + { 52, "rightCurveSmall0SleeperNW" }, + { 53, "rightCurveSmall1SleeperNW" }, + { 54, "rightCurveSmall2SleeperNW" }, + { 55, "rightCurveSmall3SleeperNW" }, + { 56, "rightCurveSmall0RailNE" }, + { 57, "rightCurveSmall1RailNE" }, + { 58, "rightCurveSmall2RailNE" }, + { 59, "rightCurveSmall3RailNE" }, + { 60, "rightCurveSmall0RailSE" }, + { 61, "rightCurveSmall1RailSE" }, + { 62, "rightCurveSmall2RailSE" }, + { 63, "rightCurveSmall3RailSE" }, + { 64, "rightCurveSmall0RailSW" }, + { 65, "rightCurveSmall1RailSW" }, + { 66, "rightCurveSmall2RailSW" }, + { 67, "rightCurveSmall3RailSW" }, + { 68, "rightCurveSmall0RailNW" }, + { 69, "rightCurveSmall1RailNW" }, + { 70, "rightCurveSmall2RailNW" }, + { 71, "rightCurveSmall3RailNW" }, + { 72, "rightCurveSmallSlopeUp0NE" }, + { 73, "rightCurveSmallSlopeUp1NE" }, + { 74, "rightCurveSmallSlopeUp2NE" }, + { 75, "rightCurveSmallSlopeUp3NE" }, + { 76, "rightCurveSmallSlopeUp0SE" }, + { 77, "rightCurveSmallSlopeUp1SE" }, + { 78, "rightCurveSmallSlopeUp2SE" }, + { 79, "rightCurveSmallSlopeUp3SE" }, + { 80, "rightCurveSmallSlopeUp0SW" }, + { 81, "rightCurveSmallSlopeUp1SW" }, + { 82, "rightCurveSmallSlopeUp2SW" }, + { 83, "rightCurveSmallSlopeUp3SW" }, + { 84, "rightCurveSmallSlopeUp0NW" }, + { 85, "rightCurveSmallSlopeUp1NW" }, + { 86, "rightCurveSmallSlopeUp2NW" }, + { 87, "rightCurveSmallSlopeUp3NW" }, + { 88, "rightCurveSmallSlopeDown0NE" }, + { 89, "rightCurveSmallSlopeDown1NE" }, + { 90, "rightCurveSmallSlopeDown2NE" }, + { 91, "rightCurveSmallSlopeDown3NE" }, + { 92, "rightCurveSmallSlopeDown0SE" }, + { 93, "rightCurveSmallSlopeDown1SE" }, + { 94, "rightCurveSmallSlopeDown2SE" }, + { 95, "rightCurveSmallSlopeDown3SE" }, + { 96, "rightCurveSmallSlopeDown0SW" }, + { 97, "rightCurveSmallSlopeDown1SW" }, + { 98, "rightCurveSmallSlopeDown2SW" }, + { 99, "rightCurveSmallSlopeDown3SW" }, + { 100, "rightCurveSmallSlopeDown0NW" }, + { 101, "rightCurveSmallSlopeDown1NW" }, + { 102, "rightCurveSmallSlopeDown2NW" }, + { 103, "rightCurveSmallSlopeDown3NW" }, + { 104, "rightCurveSmallSteepSlopeUp0NE" }, + { 105, "rightCurveSmallSteepSlopeUp1NE" }, + { 106, "rightCurveSmallSteepSlopeUp2NE" }, + { 107, "rightCurveSmallSteepSlopeUp3NE" }, + { 108, "rightCurveSmallSteepSlopeUp0SE" }, + { 109, "rightCurveSmallSteepSlopeUp1SE" }, + { 110, "rightCurveSmallSteepSlopeUp2SE" }, + { 111, "rightCurveSmallSteepSlopeUp3SE" }, + { 112, "rightCurveSmallSteepSlopeUp0SW" }, + { 113, "rightCurveSmallSteepSlopeUp1SW" }, + { 114, "rightCurveSmallSteepSlopeUp2SW" }, + { 115, "rightCurveSmallSteepSlopeUp3SW" }, + { 116, "rightCurveSmallSteepSlopeUp0NW" }, + { 117, "rightCurveSmallSteepSlopeUp1NW" }, + { 118, "rightCurveSmallSteepSlopeUp2NW" }, + { 119, "rightCurveSmallSteepSlopeUp3NW" }, + { 120, "rightCurveSmallSteepSlopeDown0NE" }, + { 121, "rightCurveSmallSteepSlopeDown1NE" }, + { 122, "rightCurveSmallSteepSlopeDown2NE" }, + { 123, "rightCurveSmallSteepSlopeDown3NE" }, + { 124, "rightCurveSmallSteepSlopeDown0SE" }, + { 125, "rightCurveSmallSteepSlopeDown1SE" }, + { 126, "rightCurveSmallSteepSlopeDown2SE" }, + { 127, "rightCurveSmallSteepSlopeDown3SE" }, + { 128, "rightCurveSmallSteepSlopeDown0SW" }, + { 129, "rightCurveSmallSteepSlopeDown1SW" }, + { 130, "rightCurveSmallSteepSlopeDown2SW" }, + { 131, "rightCurveSmallSteepSlopeDown3SW" }, + { 132, "rightCurveSmallSteepSlopeDown0NW" }, + { 133, "rightCurveSmallSteepSlopeDown1NW" }, + { 134, "rightCurveSmallSteepSlopeDown2NW" }, + { 135, "rightCurveSmallSteepSlopeDown3NW" }, + { 136, "rightCurve0BallastNE" }, + { 137, "rightCurve1BallastNE" }, + { 138, "rightCurve2BallastNE" }, + { 139, "rightCurve3BallastNE" }, + { 140, "rightCurve4BallastNE" }, + { 141, "rightCurve0BallastSE" }, + { 142, "rightCurve1BallastSE" }, + { 143, "rightCurve2BallastSE" }, + { 144, "rightCurve3BallastSE" }, + { 145, "rightCurve4BallastSE" }, + { 146, "rightCurve0BallastSW" }, + { 147, "rightCurve1BallastSW" }, + { 148, "rightCurve2BallastSW" }, + { 149, "rightCurve3BallastSW" }, + { 150, "rightCurve4BallastSW" }, + { 151, "rightCurve0BallastNW" }, + { 152, "rightCurve1BallastNW" }, + { 153, "rightCurve2BallastNW" }, + { 154, "rightCurve3BallastNW" }, + { 155, "rightCurve4BallastNW" }, + { 156, "rightCurve0SleeperNE" }, + { 157, "rightCurve1SleeperNE" }, + { 158, "rightCurve2SleeperNE" }, + { 159, "rightCurve3SleeperNE" }, + { 160, "rightCurve4SleeperNE" }, + { 161, "rightCurve0SleeperSE" }, + { 162, "rightCurve1SleeperSE" }, + { 163, "rightCurve2SleeperSE" }, + { 164, "rightCurve3SleeperSE" }, + { 165, "rightCurve4SleeperSE" }, + { 166, "rightCurve0SleeperSW" }, + { 167, "rightCurve1SleeperSW" }, + { 168, "rightCurve2SleeperSW" }, + { 169, "rightCurve3SleeperSW" }, + { 170, "rightCurve4SleeperSW" }, + { 171, "rightCurve0SleeperNW" }, + { 172, "rightCurve1SleeperNW" }, + { 173, "rightCurve2SleeperNW" }, + { 174, "rightCurve3SleeperNW" }, + { 175, "rightCurve4SleeperNW" }, + { 176, "rightCurve0RailNE" }, + { 177, "rightCurve1RailNE" }, + { 178, "rightCurve2RailNE" }, + { 179, "rightCurve3RailNE" }, + { 180, "rightCurve4RailNE" }, + { 181, "rightCurve0RailSE" }, + { 182, "rightCurve1RailSE" }, + { 183, "rightCurve2RailSE" }, + { 184, "rightCurve3RailSE" }, + { 185, "rightCurve4RailSE" }, + { 186, "rightCurve0RailSW" }, + { 187, "rightCurve1RailSW" }, + { 188, "rightCurve2RailSW" }, + { 189, "rightCurve3RailSW" }, + { 190, "rightCurve4RailSW" }, + { 191, "rightCurve0RailNW" }, + { 192, "rightCurve1RailNW" }, + { 193, "rightCurve2RailNW" }, + { 194, "rightCurve3RailNW" }, + { 195, "rightCurve4RailNW" }, + { 196, "straightSlopeUp0NE" }, + { 197, "straightSlopeUp1NE" }, + { 198, "straightSlopeUp0SE" }, + { 199, "straightSlopeUp1SE" }, + { 200, "straightSlopeUp0SW" }, + { 201, "straightSlopeUp1SW" }, + { 202, "straightSlopeUp0NW" }, + { 203, "straightSlopeUp1NW" }, + { 204, "straightSteepSlopeUp0NE" }, + { 205, "straightSteepSlopeUp0SE" }, + { 206, "straightSteepSlopeUp0SW" }, + { 207, "straightSteepSlopeUp0NW" }, + { 208, "rightCurveLarge0BallastNE" }, + { 209, "rightCurveLarge1BallastNE" }, + { 210, "rightCurveLarge2BallastNE" }, + { 211, "rightCurveLarge3BallastNE" }, + { 212, "rightCurveLarge4BallastNE" }, + { 213, "rightCurveLarge0BallastSE" }, + { 214, "rightCurveLarge1BallastSE" }, + { 215, "rightCurveLarge2BallastSE" }, + { 216, "rightCurveLarge3BallastSE" }, + { 217, "rightCurveLarge4BallastSE" }, + { 218, "rightCurveLarge0BallastSW" }, + { 219, "rightCurveLarge1BallastSW" }, + { 220, "rightCurveLarge2BallastSW" }, + { 221, "rightCurveLarge3BallastSW" }, + { 222, "rightCurveLarge4BallastSW" }, + { 223, "rightCurveLarge0BallastNW" }, + { 224, "rightCurveLarge1BallastNW" }, + { 225, "rightCurveLarge2BallastNW" }, + { 226, "rightCurveLarge3BallastNW" }, + { 227, "rightCurveLarge4BallastNW" }, + { 228, "leftCurveLarge0BallastNE" }, + { 229, "leftCurveLarge1BallastNE" }, + { 230, "leftCurveLarge2BallastNE" }, + { 231, "leftCurveLarge3BallastNE" }, + { 232, "leftCurveLarge4BallastNE" }, + { 233, "leftCurveLarge0BallastSE" }, + { 234, "leftCurveLarge1BallastSE" }, + { 235, "leftCurveLarge2BallastSE" }, + { 236, "leftCurveLarge3BallastSE" }, + { 237, "leftCurveLarge4BallastSE" }, + { 238, "leftCurveLarge0BallastSW" }, + { 239, "leftCurveLarge1BallastSW" }, + { 240, "leftCurveLarge2BallastSW" }, + { 241, "leftCurveLarge3BallastSW" }, + { 242, "leftCurveLarge4BallastSW" }, + { 243, "leftCurveLarge0BallastNW" }, + { 244, "leftCurveLarge1BallastNW" }, + { 245, "leftCurveLarge2BallastNW" }, + { 246, "leftCurveLarge3BallastNW" }, + { 247, "leftCurveLarge4BallastNW" }, + { 248, "rightCurveLarge0SleeperNE" }, + { 249, "rightCurveLarge1SleeperNE" }, + { 250, "rightCurveLarge2SleeperNE" }, + { 251, "rightCurveLarge3SleeperNE" }, + { 252, "rightCurveLarge4SleeperNE" }, + { 253, "rightCurveLarge0SleeperSE" }, + { 254, "rightCurveLarge1SleeperSE" }, + { 255, "rightCurveLarge2SleeperSE" }, + { 256, "rightCurveLarge3SleeperSE" }, + { 257, "rightCurveLarge4SleeperSE" }, + { 258, "rightCurveLarge0SleeperSW" }, + { 259, "rightCurveLarge1SleeperSW" }, + { 260, "rightCurveLarge2SleeperSW" }, + { 261, "rightCurveLarge3SleeperSW" }, + { 262, "rightCurveLarge4SleeperSW" }, + { 263, "rightCurveLarge0SleeperNW" }, + { 264, "rightCurveLarge1SleeperNW" }, + { 265, "rightCurveLarge2SleeperNW" }, + { 266, "rightCurveLarge3SleeperNW" }, + { 267, "rightCurveLarge4SleeperNW" }, + { 268, "leftCurveLarge0SleeperNE" }, + { 269, "leftCurveLarge1SleeperNE" }, + { 270, "leftCurveLarge2SleeperNE" }, + { 271, "leftCurveLarge3SleeperNE" }, + { 272, "leftCurveLarge4SleeperNE" }, + { 273, "leftCurveLarge0SleeperSE" }, + { 274, "leftCurveLarge1SleeperSE" }, + { 275, "leftCurveLarge2SleeperSE" }, + { 276, "leftCurveLarge3SleeperSE" }, + { 277, "leftCurveLarge4SleeperSE" }, + { 278, "leftCurveLarge0SleeperSW" }, + { 279, "leftCurveLarge1SleeperSW" }, + { 280, "leftCurveLarge2SleeperSW" }, + { 281, "leftCurveLarge3SleeperSW" }, + { 282, "leftCurveLarge4SleeperSW" }, + { 283, "leftCurveLarge0SleeperNW" }, + { 284, "leftCurveLarge1SleeperNW" }, + { 285, "leftCurveLarge2SleeperNW" }, + { 286, "leftCurveLarge3SleeperNW" }, + { 287, "leftCurveLarge4SleeperNW" }, + { 288, "rightCurveLarge0RailNE" }, + { 289, "rightCurveLarge1RailNE" }, + { 290, "rightCurveLarge2RailNE" }, + { 291, "rightCurveLarge3RailNE" }, + { 292, "rightCurveLarge4RailNE" }, + { 293, "rightCurveLarge0RailSE" }, + { 294, "rightCurveLarge1RailSE" }, + { 295, "rightCurveLarge2RailSE" }, + { 296, "rightCurveLarge3RailSE" }, + { 297, "rightCurveLarge4RailSE" }, + { 298, "rightCurveLarge0RailSW" }, + { 299, "rightCurveLarge1RailSW" }, + { 300, "rightCurveLarge2RailSW" }, + { 301, "rightCurveLarge3RailSW" }, + { 302, "rightCurveLarge4RailSW" }, + { 303, "rightCurveLarge0RailNW" }, + { 304, "rightCurveLarge1RailNW" }, + { 305, "rightCurveLarge2RailNW" }, + { 306, "rightCurveLarge3RailNW" }, + { 307, "rightCurveLarge4RailNW" }, + { 308, "leftCurveLarge0RailNE" }, + { 309, "leftCurveLarge1RailNE" }, + { 310, "leftCurveLarge2RailNE" }, + { 311, "leftCurveLarge3RailNE" }, + { 312, "leftCurveLarge4RailNE" }, + { 313, "leftCurveLarge0RailSE" }, + { 314, "leftCurveLarge1RailSE" }, + { 315, "leftCurveLarge2RailSE" }, + { 316, "leftCurveLarge3RailSE" }, + { 317, "leftCurveLarge4RailSE" }, + { 318, "leftCurveLarge0RailSW" }, + { 319, "leftCurveLarge1RailSW" }, + { 320, "leftCurveLarge2RailSW" }, + { 321, "leftCurveLarge3RailSW" }, + { 322, "leftCurveLarge4RailSW" }, + { 323, "leftCurveLarge0RailNW" }, + { 324, "leftCurveLarge1RailNW" }, + { 325, "leftCurveLarge2RailNW" }, + { 326, "leftCurveLarge3RailNW" }, + { 327, "leftCurveLarge4RailNW" }, + { 328, "diagonal0BallastNE" }, + { 329, "diagonal1BallastSW" }, + { 330, "diagonal1BallastNE" }, + { 331, "diagonal0BallastSW" }, + { 332, "diagonal0BallastSE" }, + { 333, "diagonal1BallastNW" }, + { 334, "diagonal1BallastSE" }, + { 335, "diagonal0BallastNW" }, + { 336, "diagonal0SleeperNE" }, + { 337, "diagonal1SleeperSW" }, + { 338, "diagonal1SleeperNE" }, + { 339, "diagonal0SleeperSW" }, + { 340, "diagonal0SleeperSE" }, + { 341, "diagonal1SleeperNW" }, + { 342, "diagonal1SleeperSE" }, + { 343, "diagonal0SleeperNW" }, + { 344, "diagonal0RailNE" }, + { 345, "diagonal1RailSW" }, + { 346, "diagonal1RailNE" }, + { 347, "diagonal0RailSW" }, + { 348, "diagonal0RailSE" }, + { 349, "diagonal1RailNW" }, + { 350, "diagonal1RailSE" }, + { 351, "diagonal0RailNW" }, + { 352, "sBendLeft0BallastNE" }, + { 353, "sBendLeft1BallastNE" }, + { 354, "sBendLeft1BallastSW" }, + { 355, "sBendLeft0BallastSW" }, + { 356, "sBendLeft0BallastSE" }, + { 357, "sBendLeft1BallastSE" }, + { 358, "sBendLeft1BallastNW" }, + { 359, "sBendLeft0BallastNW" }, + { 360, "sBendRight0BallastNE" }, + { 361, "sBendRight1BallastNE" }, + { 362, "sBendRight1BallastSW" }, + { 363, "sBendRight0BallastSW" }, + { 364, "sBendRight0BallastSE" }, + { 365, "sBendRight1BallastSE" }, + { 366, "sBendRight1BallastNW" }, + { 367, "sBendRight0BallastNW" }, + { 368, "sBendLeft0SleeperNE" }, + { 369, "sBendLeft1SleeperNE" }, + { 370, "sBendLeft1SleeperSW" }, + { 371, "sBendLeft0SleeperSW" }, + { 372, "sBendLeft0SleeperSE" }, + { 373, "sBendLeft1SleeperSE" }, + { 374, "sBendLeft1SleeperNW" }, + { 375, "sBendLeft0SleeperNW" }, + { 376, "sBendRight0SleeperNE" }, + { 377, "sBendRight1SleeperNE" }, + { 378, "sBendRight1SleeperSW" }, + { 379, "sBendRight0SleeperSW" }, + { 380, "sBendRight0SleeperSE" }, + { 381, "sBendRight1SleeperSE" }, + { 382, "sBendRight1SleeperNW" }, + { 383, "sBendRight0SleeperNW" }, + { 384, "sBendLeft0RailNE" }, + { 385, "sBendLeft1RailNE" }, + { 386, "sBendLeft1RailSW" }, + { 387, "sBendLeft0RailSW" }, + { 388, "sBendLeft0RailSE" }, + { 389, "sBendLeft1RailSE" }, + { 390, "sBendLeft1RailNW" }, + { 391, "sBendLeft0RailNW" }, + { 392, "sBendRight0RailNE" }, + { 393, "sBendRight1RailNE" }, + { 394, "sBendRight1RailSW" }, + { 395, "sBendRight0RailSW" }, + { 396, "sBendRight0RailSE" }, + { 397, "sBendRight1RailSE" }, + { 398, "sBendRight1RailNW" }, + { 399, "sBendRight0RailNW" }, + { 400, "rightCurveVerySmall0BallastNE" }, + { 401, "rightCurveVerySmall0BallastSE" }, + { 402, "rightCurveVerySmall0BallastSW" }, + { 403, "rightCurveVerySmall0BallastNW" }, + { 404, "rightCurveVerySmall0SleeperNE" }, + { 405, "rightCurveVerySmall0SleeperSE" }, + { 406, "rightCurveVerySmall0SleeperSW" }, + { 407, "rightCurveVerySmall0SleeperNW" }, + { 408, "rightCurveVerySmall0RailNE" }, + { 409, "rightCurveVerySmall0RailSE" }, + { 410, "rightCurveVerySmall0RailSW" }, + { 411, "rightCurveVerySmall0RailNW" }, + }; + } + + class RoadObjectNamer : ImageTableNamer + { + public static RoadObjectNamer Instance { get; } = new(); + + public override bool TryGetImageName(RoadObject model, int id, [MaybeNullWhen(false)] out string value) + { + if (id is >= 0 and <= 31) + { + value = $"uiPreviewImage{id}"; + return true; + } + + if (id is >= 32 and <= 33) + { + return ImageIdNameMap.TryGetValue(id, out value); + } + + // style dependent + return model.PaintStyle switch + { + 0 => ImageIdNameMap_Style0.TryGetValue(id, out value), + 1 => ImageIdNameMap_Style1.TryGetValue(id, out value), + 2 => ImageIdNameMap_Style2.TryGetValue(id, out value), + _ => throw new NotImplementedException(id.ToString()), + }; + } + + static readonly Dictionary ImageIdNameMap = new() + { + { 32, "uiPickupFromTrack" }, + { 33, "uiPlaceOnTrack" }, + }; + + static readonly Dictionary ImageIdNameMap_Style0 = new() + { + { 34, "kStraight0NE" }, + { 35, "kStraight0SE" }, + { 36, "kRightCurveVerySmall0NE" }, + { 37, "kRightCurveVerySmall0SE" }, + { 38, "kRightCurveVerySmall0SW" }, + { 39, "kRightCurveVerySmall0NW" }, + { 40, "kJunctionLeft0NE" }, + { 41, "kJunctionLeft0SE" }, + { 42, "kJunctionLeft0SW" }, + { 43, "kJunctionLeft0NW" }, + { 44, "kJunctionCrossroads0NE" }, + { 45, "kRightCurveSmall0NE" }, + { 46, "kRightCurveSmall1NE" }, + { 47, "kRightCurveSmall2NE" }, + { 48, "kRightCurveSmall3NE" }, + { 49, "kRightCurveSmall0SE" }, + { 50, "kRightCurveSmall1SE" }, + { 51, "kRightCurveSmall2SE" }, + { 52, "kRightCurveSmall3SE" }, + { 53, "kRightCurveSmall0SW" }, + { 54, "kRightCurveSmall1SW" }, + { 55, "kRightCurveSmall2SW" }, + { 56, "kRightCurveSmall3SW" }, + { 57, "kRightCurveSmall0NW" }, + { 58, "kRightCurveSmall1NW" }, + { 59, "kRightCurveSmall2NW" }, + { 60, "kRightCurveSmall3NW" }, + { 61, "kStraightSlopeUp0NE" }, + { 62, "kStraightSlopeUp1NE" }, + { 63, "kStraightSlopeUp0SE" }, + { 64, "kStraightSlopeUp1SE" }, + { 65, "kStraightSlopeUp0SW" }, + { 66, "kStraightSlopeUp1SW" }, + { 67, "kStraightSlopeUp0NW" }, + { 68, "kStraightSlopeUp1NW" }, + { 69, "kStraightSteepSlopeUp0NE" }, + { 70, "kStraightSteepSlopeUp0SE" }, + { 71, "kStraightSteepSlopeUp0SW" }, + { 72, "kStraightSteepSlopeUp0NW" }, + { 73, "kTurnaround0NE" }, + { 74, "kTurnaround0SE" }, + { 75, "kTurnaround0SW" }, + { 76, "kTurnaround0NW" }, + }; + + static readonly Dictionary ImageIdNameMap_Style1 = new() + { + { 34, "kStraight0BallastNE" }, + { 35, "kStraight0BallastSE" }, + { 36, "kStraight0SleeperNE" }, + { 37, "kStraight0SleeperSE" }, + { 38, "kStraight0RailNE" }, + { 39, "kStraight0RailSE" }, + { 40, "kRightCurveSmall0BallastNE" }, + { 41, "kRightCurveSmall1BallastNE" }, + { 42, "kRightCurveSmall2BallastNE" }, + { 43, "kRightCurveSmall3BallastNE" }, + { 44, "kRightCurveSmall0BallastSE" }, + { 45, "kRightCurveSmall1BallastSE" }, + { 46, "kRightCurveSmall2BallastSE" }, + { 47, "kRightCurveSmall3BallastSE" }, + { 48, "kRightCurveSmall0BallastSW" }, + { 49, "kRightCurveSmall1BallastSW" }, + { 50, "kRightCurveSmall2BallastSW" }, + { 51, "kRightCurveSmall3BallastSW" }, + { 52, "kRightCurveSmall0BallastNW" }, + { 53, "kRightCurveSmall1BallastNW" }, + { 54, "kRightCurveSmall2BallastNW" }, + { 55, "kRightCurveSmall3BallastNW" }, + { 56, "kRightCurveSmall0SleeperNE" }, + { 57, "kRightCurveSmall1SleeperNE" }, + { 58, "kRightCurveSmall2SleeperNE" }, + { 59, "kRightCurveSmall3SleeperNE" }, + { 60, "kRightCurveSmall0SleeperSE" }, + { 61, "kRightCurveSmall1SleeperSE" }, + { 62, "kRightCurveSmall2SleeperSE" }, + { 63, "kRightCurveSmall3SleeperSE" }, + { 64, "kRightCurveSmall0SleeperSW" }, + { 65, "kRightCurveSmall1SleeperSW" }, + { 66, "kRightCurveSmall2SleeperSW" }, + { 67, "kRightCurveSmall3SleeperSW" }, + { 68, "kRightCurveSmall0SleeperNW" }, + { 69, "kRightCurveSmall1SleeperNW" }, + { 70, "kRightCurveSmall2SleeperNW" }, + { 71, "kRightCurveSmall3SleeperNW" }, + { 72, "kRightCurveSmall0RailNE" }, + { 73, "kRightCurveSmall1RailNE" }, + { 74, "kRightCurveSmall2RailNE" }, + { 75, "kRightCurveSmall3RailNE" }, + { 76, "kRightCurveSmall0RailSE" }, + { 77, "kRightCurveSmall1RailSE" }, + { 78, "kRightCurveSmall2RailSE" }, + { 79, "kRightCurveSmall3RailSE" }, + { 80, "kRightCurveSmall0RailSW" }, + { 81, "kRightCurveSmall1RailSW" }, + { 82, "kRightCurveSmall2RailSW" }, + { 83, "kRightCurveSmall3RailSW" }, + { 84, "kRightCurveSmall0RailNW" }, + { 85, "kRightCurveSmall1RailNW" }, + { 86, "kRightCurveSmall2RailNW" }, + { 87, "kRightCurveSmall3RailNW" }, + { 88, "kStraightSlopeUp0BallastNE" }, + { 89, "kStraightSlopeUp1BallastNE" }, + { 90, "kStraightSlopeUp0BallastSE" }, + { 91, "kStraightSlopeUp1BallastSE" }, + { 92, "kStraightSlopeUp0BallastSW" }, + { 93, "kStraightSlopeUp1BallastSW" }, + { 94, "kStraightSlopeUp0BallastNW" }, + { 95, "kStraightSlopeUp1BallastNW" }, + { 96, "kStraightSlopeUp0SleeperNE" }, + { 97, "kStraightSlopeUp1SleeperNE" }, + { 98, "kStraightSlopeUp0SleeperSE" }, + { 99, "kStraightSlopeUp1SleeperSE" }, + { 100, "kStraightSlopeUp0SleeperSW" }, + { 101, "kStraightSlopeUp1SleeperSW" }, + { 102, "kStraightSlopeUp0SleeperNW" }, + { 103, "kStraightSlopeUp1SleeperNW" }, + { 104, "kStraightSlopeUp0RailNE" }, + { 105, "kStraightSlopeUp1RailNE" }, + { 106, "kStraightSlopeUp0RailSE" }, + { 107, "kStraightSlopeUp1RailSE" }, + { 108, "kStraightSlopeUp0RailSW" }, + { 109, "kStraightSlopeUp1RailSW" }, + { 110, "kStraightSlopeUp0RailNW" }, + { 111, "kStraightSlopeUp1RailNW" }, + { 112, "kStraightSteepSlopeUp0BallastNE" }, + { 113, "kStraightSteepSlopeUp0BallastSE" }, + { 114, "kStraightSteepSlopeUp0BallastSW" }, + { 115, "kStraightSteepSlopeUp0BallastNW" }, + { 116, "kStraightSteepSlopeUp0SleeperNE" }, + { 117, "kStraightSteepSlopeUp0SleeperSE" }, + { 118, "kStraightSteepSlopeUp0SleeperSW" }, + { 119, "kStraightSteepSlopeUp0SleeperNW" }, + { 120, "kStraightSteepSlopeUp0RailNE" }, + { 121, "kStraightSteepSlopeUp0RailSE" }, + { 122, "kStraightSteepSlopeUp0RailSW" }, + { 123, "kStraightSteepSlopeUp0RailNW" }, + { 124, "kRightCurveVerySmall0BallastNE" }, + { 125, "kRightCurveVerySmall0BallastSE" }, + { 126, "kRightCurveVerySmall0BallastSW" }, + { 127, "kRightCurveVerySmall0BallastNW" }, + { 128, "kRightCurveVerySmall0SleeperNE" }, + { 129, "kRightCurveVerySmall0SleeperSE" }, + { 130, "kRightCurveVerySmall0SleeperSW" }, + { 131, "kRightCurveVerySmall0SleeperNW" }, + { 132, "kRightCurveVerySmall0RailNE" }, + { 133, "kRightCurveVerySmall0RailSE" }, + { 134, "kRightCurveVerySmall0RailSW" }, + { 135, "kRightCurveVerySmall0RailNW" }, + { 136, "kTurnaround0BallastNE" }, + { 137, "kTurnaround0BallastSE" }, + { 138, "kTurnaround0BallastSW" }, + { 139, "kTurnaround0BallastNW" }, + { 140, "kTurnaround0SleeperNE" }, + { 141, "kTurnaround0SleeperSE" }, + { 142, "kTurnaround0SleeperSW" }, + { 143, "kTurnaround0SleeperNW" }, + { 144, "kTurnaround0RailNE" }, + { 145, "kTurnaround0RailSE" }, + { 146, "kTurnaround0RailSW" }, + { 147, "kTurnaround0RailNW" }, + }; + + static readonly Dictionary ImageIdNameMap_Style2 = new() + { + { 34, "kStraight0NE" }, + { 35, "kStraight0SE" }, + { 36, "kLeftCurveVerySmall0NW" }, + { 37, "kLeftCurveVerySmall0NE" }, + { 38, "kLeftCurveVerySmall0SE" }, + { 39, "kLeftCurveVerySmall0SW" }, + { 40, "kJunctionLeft0NE" }, + { 41, "kJunctionLeft0SE" }, + { 42, "kJunctionLeft0SW" }, + { 43, "kJunctionLeft0NW" }, + { 44, "kJunctionCrossroads0NE" }, + { 45, "kLeftCurveSmall3NW" }, + { 46, "kLeftCurveSmall1NW" }, + { 47, "kLeftCurveSmall2NW" }, + { 48, "kLeftCurveSmall0NW" }, + { 49, "kLeftCurveSmall3NE" }, + { 50, "kLeftCurveSmall1NE" }, + { 51, "kLeftCurveSmall2NE" }, + { 52, "kLeftCurveSmall0NE" }, + { 53, "kLeftCurveSmall3SE" }, + { 54, "kLeftCurveSmall1SE" }, + { 55, "kLeftCurveSmall2SE" }, + { 56, "kLeftCurveSmall0SE" }, + { 57, "kLeftCurveSmall3SW" }, + { 58, "kLeftCurveSmall1SW" }, + { 59, "kLeftCurveSmall2SW" }, + { 60, "kLeftCurveSmall0SW" }, + { 61, "kStraightSlopeUp0NE" }, + { 62, "kStraightSlopeUp1NE" }, + { 63, "kStraightSlopeUp0SE" }, + { 64, "kStraightSlopeUp1SE" }, + { 65, "kStraightSlopeUp0SW" }, + { 66, "kStraightSlopeUp1SW" }, + { 67, "kStraightSlopeUp0NW" }, + { 68, "kStraightSlopeUp1NW" }, + { 69, "kStraightSteepSlopeUp0NE" }, + { 70, "kStraightSteepSlopeUp0SE" }, + { 71, "kStraightSteepSlopeUp0SW" }, + { 72, "kStraightSteepSlopeUp0NW" }, + { 73, "kTurnaround0NE" }, + { 74, "kTurnaround0SE" }, + { 75, "kTurnaround0SW" }, + { 76, "kTurnaround0NW" }, + + { 85, "kStraight0SW" }, + { 86, "kStraight0NW" }, + { 87, "kRightCurveVerySmall0NE" }, + { 88, "kRightCurveVerySmall0SE" }, + { 89, "kRightCurveVerySmall0SW" }, + { 90, "kRightCurveVerySmall0NW" }, + { 91, "kJunctionRight0NE" }, + { 92, "kJunctionRight0SE" }, + { 93, "kJunctionRight0SW" }, + { 94, "kJunctionRight0NW" }, + // Must duplicate kJunctionCrossroads0NE + { 95, "kJunctionCrossroads0NE2" }, + { 96, "kRightCurveSmall0NE" }, + { 97, "kRightCurveSmall1NE" }, + { 98, "kRightCurveSmall2NE" }, + { 99, "kRightCurveSmall3NE" }, + { 100, "kRightCurveSmall0SE" }, + { 101, "kRightCurveSmall1SE" }, + { 102, "kRightCurveSmall2SE" }, + { 103, "kRightCurveSmall3SE" }, + { 104, "kRightCurveSmall0SW" }, + { 105, "kRightCurveSmall1SW" }, + { 106, "kRightCurveSmall2SW" }, + { 107, "kRightCurveSmall3SW" }, + { 108, "kRightCurveSmall0NW" }, + { 109, "kRightCurveSmall1NW" }, + { 110, "kRightCurveSmall2NW" }, + { 111, "kRightCurveSmall3NW" }, + { 112, "kStraightSlopeDown1SW" }, + { 113, "kStraightSlopeDown0SW" }, + { 114, "kStraightSlopeDown1NW" }, + { 115, "kStraightSlopeDown0NW" }, + { 116, "kStraightSlopeDown1NE" }, + { 117, "kStraightSlopeDown0NE" }, + { 118, "kStraightSlopeDown1SE" }, + { 119, "kStraightSlopeDown0SE" }, + { 120, "kStraightSteepSlopeDown0SW" }, + { 121, "kStraightSteepSlopeDown0NW" }, + { 122, "kStraightSteepSlopeDown0NE" }, + { 123, "kStraightSteepSlopeDown0SE" }, + }; + } + + public static void NameImages(ILocoStruct obj, ObjectType objectType, List imageList) + { + IImageTableNameProvider imageNamer = objectType switch + { + ObjectType.InterfaceSkin => InterfaceSkinObjectNamer.Instance, + ObjectType.Sound => DefaultImageTableNameProvider.Instance, + ObjectType.Currency => DefaultImageTableNameProvider.Instance, + ObjectType.Steam => DefaultImageTableNameProvider.Instance, + ObjectType.CliffEdge => CliffEdgeObjectNamer.Instance, + ObjectType.Water => WaterObjectNamer.Instance, + ObjectType.Land => LandObjectNamer.Instance, + ObjectType.TownNames => DefaultImageTableNameProvider.Instance, + ObjectType.Cargo => CargoObjectNamer.Instance, + ObjectType.Wall => WallObjectNamer.Instance, + ObjectType.TrackSignal => TrackSignalObjectNamer.Instance, + ObjectType.LevelCrossing => DefaultImageTableNameProvider.Instance, + ObjectType.StreetLight => StreetLightObjectNamer.Instance, + ObjectType.Tunnel => DefaultImageTableNameProvider.Instance, + ObjectType.Bridge => DefaultImageTableNameProvider.Instance, + ObjectType.TrackStation => TrackSignalObjectNamer.Instance, + ObjectType.TrackExtra => TrackExtraObjectNamer.Instance, + ObjectType.Track => TrackObjectNamer.Instance, + ObjectType.RoadStation => RoadStationObjectNamer.Instance, + ObjectType.RoadExtra => DefaultImageTableNameProvider.Instance, + ObjectType.Road => RoadObjectNamer.Instance, + ObjectType.Airport => DefaultImageTableNameProvider.Instance, + ObjectType.Dock => DefaultImageTableNameProvider.Instance, + ObjectType.Vehicle => DefaultImageTableNameProvider.Instance, + ObjectType.Tree => DefaultImageTableNameProvider.Instance, + ObjectType.Snow => DefaultImageTableNameProvider.Instance, + ObjectType.Climate => DefaultImageTableNameProvider.Instance, + ObjectType.HillShapes => HillShapesObjectNamer.Instance, + ObjectType.Building => DefaultImageTableNameProvider.Instance, + ObjectType.Scaffolding => DefaultImageTableNameProvider.Instance, + ObjectType.Industry => DefaultImageTableNameProvider.Instance, + ObjectType.Region => DefaultImageTableNameProvider.Instance, + ObjectType.Competitor => CompetitorObjectNamer.Instance, + ObjectType.ScenarioText => DefaultImageTableNameProvider.Instance, + _ => DefaultImageTableNameProvider.Instance, + }; + + for (var i = 0; i < imageList.Count; ++i) + { + imageList[i].Name = imageNamer.TryGetImageName(obj, i, out var name) + ? name! + : DefaultImageTableNameProvider.GetImageName(i); + } + } +} + +public static class ImageTableGrouper +{ + public static ImageTable CreateImageTable(ILocoStruct obj, ObjectType objectType, List imageList) + { + var originalCount = imageList.Count; + + ImageTableNamer.NameImages(obj, objectType, imageList); + + var imageTable = new ImageTable(); + + switch (objectType) + { + case ObjectType.InterfaceSkin: + CreateInterfaceGroups(imageList, imageTable); + break; + case ObjectType.Sound: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Currency: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Steam: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.CliffEdge: + CreateCliffEdgeGroups(imageList, imageTable); + break; + case ObjectType.Water: + CreateWaterGroups(imageList, imageTable); + break; + case ObjectType.Land: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.TownNames: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Cargo: + CreateCargoGroups(imageList, imageTable); + break; + case ObjectType.Wall: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.TrackSignal: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.LevelCrossing: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.StreetLight: + CreateStreetLightGroups(imageList, imageTable); + break; + case ObjectType.Tunnel: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Bridge: + CreateBridgeGroups(imageList, imageTable); + break; + case ObjectType.TrackStation: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.TrackExtra: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Track: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.RoadStation: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.RoadExtra: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Road: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Airport: + CreateAirportGroups(imageList, imageTable); + break; + case ObjectType.Dock: + CreateDockGroups(imageList, imageTable); + break; + case ObjectType.Vehicle: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Tree: + CreateTreeGroups(imageList, imageTable); + break; + case ObjectType.Snow: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Climate: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.HillShapes: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Building: + CreateBuildingGroups(imageList, imageTable); + break; + case ObjectType.Industry: + CreateBuildingGroups(imageList, imageTable); + break; + case ObjectType.Region: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Competitor: + CreateCompetitorGroups(imageList, imageTable); + break; + case ObjectType.ScenarioText: + imageTable.Groups.Add(("", imageList.ToList())); + break; + case ObjectType.Scaffolding: + CreateScaffoldingGroups(imageList, imageTable); + break; + default: + break; + } + + Debug.Assert(imageTable.GraphicsElements.Count == originalCount, "Image grouping lost or gained images"); + + return imageTable; + } + + private static void CreateAirportGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("preview", imageList[0..1])); + + imageTable.Groups.AddRange(imageList + .Skip(1) + .Chunk(4) + .Select((x, i) => ($"Part {i}", x.ToList())) + .ToList()); + } + + private static void CreateBridgeGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("preview", imageList[0..1])); + imageTable.Groups.Add(("base plates", imageList[1..6])); + imageTable.Groups.Add(("unk", imageList[6..12])); + imageTable.Groups.Add(("", imageList[12..])); + } + + private static void CreateBuildingGroups(List imageList, ImageTable imageTable) + => imageTable.Groups = imageList + .Chunk(4) + .Select((x, i) => ($"Part {i}", x.ToList())) + .ToList(); + + private static void CreateCargoGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("preview", imageList[0..1])); + imageTable.Groups.Add(("station variations", imageList[1..])); + } + + private static void CreateCliffEdgeGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("left west", imageList[0..16])); + imageTable.Groups.Add(("right east", imageList[16..32])); + imageTable.Groups.Add(("right west", imageList[32..48])); + imageTable.Groups.Add(("left east", imageList[48..64])); + imageTable.Groups.Add(("far-side slopes", imageList[64..])); + } + + private static void CreateCompetitorGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("neutral", imageList[0..2])); + imageTable.Groups.Add(("happy", imageList[2..4])); + imageTable.Groups.Add(("worried", imageList[4..6])); + imageTable.Groups.Add(("thinking", imageList[6..8])); + imageTable.Groups.Add(("dejected", imageList[8..10])); + imageTable.Groups.Add(("surprised", imageList[10..12])); + imageTable.Groups.Add(("scared", imageList[12..14])); + imageTable.Groups.Add(("angry", imageList[14..16])); + imageTable.Groups.Add(("disgusted", imageList[16..18])); + } + + private static void CreateDockGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("preview", [imageList[0]])); + + imageTable.Groups.AddRange(imageList + .Skip(1) + .Chunk(4) + .Select((x, i) => ($"Part {i}", x.ToList())) + .ToList()); + } + + private static void CreateInterfaceGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("preview", imageList[0..1])); + imageTable.Groups.Add(("toolbar", imageList[1..31])); + imageTable.Groups.Add(("build-vehicle", imageList[31..43])); + imageTable.Groups.Add(("toolbar", imageList[43..49])); + imageTable.Groups.Add(("paint", imageList[49..57])); + imageTable.Groups.Add(("population", imageList[57..65])); + imageTable.Groups.Add(("performance-index", imageList[65..73])); + imageTable.Groups.Add(("cargo-units", imageList[73..81])); + imageTable.Groups.Add(("cargo-distance", imageList[81..89])); + imageTable.Groups.Add(("production", imageList[89..97])); + imageTable.Groups.Add(("wrench", imageList[97..113])); + imageTable.Groups.Add(("finances", imageList[113..129])); + imageTable.Groups.Add(("cup", imageList[129..145])); + imageTable.Groups.Add(("ratings", imageList[145..161])); + imageTable.Groups.Add(("transported", imageList[161..168])); + imageTable.Groups.Add(("cogs", imageList[168..172])); + imageTable.Groups.Add(("toolbar", imageList[172..203])); + imageTable.Groups.Add(("tab-train", imageList[203..211])); + imageTable.Groups.Add(("tab-aircraft", imageList[211..219])); + imageTable.Groups.Add(("tab-bus", imageList[219..227])); + imageTable.Groups.Add(("tab-tram", imageList[227..235])); + imageTable.Groups.Add(("tab-truck", imageList[235..243])); + imageTable.Groups.Add(("tab-ship", imageList[243..251])); + imageTable.Groups.Add(("build-train", imageList[251..267])); + imageTable.Groups.Add(("build-aircraft", imageList[267..283])); + imageTable.Groups.Add(("build-bus", imageList[283..299])); + imageTable.Groups.Add(("build-tram", imageList[299..315])); + imageTable.Groups.Add(("build-truck", imageList[315..331])); + imageTable.Groups.Add(("build-ship", imageList[331..347])); + imageTable.Groups.Add(("build-industry", imageList[347..363])); + imageTable.Groups.Add(("build-town", imageList[363..379])); + imageTable.Groups.Add(("build-buildings", imageList[379..395])); + imageTable.Groups.Add(("build-misc-buildings", imageList[395..411])); + imageTable.Groups.Add(("build-extra", imageList[411..418])); + imageTable.Groups.Add(("train", imageList[418..426])); + imageTable.Groups.Add(("aircraft", imageList[426..434])); + imageTable.Groups.Add(("bus", imageList[434..442])); + imageTable.Groups.Add(("tram", imageList[442..450])); + imageTable.Groups.Add(("truck", imageList[450..458])); + imageTable.Groups.Add(("ship", imageList[458..466])); + imageTable.Groups.Add(("toolbar-map", imageList[466..470])); + } + + private static void CreateScaffoldingGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("type 0", imageList[0..10])); + imageTable.Groups.Add(("type 1", imageList[10..24])); + imageTable.Groups.Add(("type 2", imageList[24..36])); + } + + private static void CreateStreetLightGroups(List imageList, ImageTable imageTable) + => imageTable.Groups.AddRange(imageList + .Chunk(4) + .Select((x, i) => ($"Year group {i}", x.ToList())) + .ToList()); + + private static void CreateTreeGroups(List imageList, ImageTable imageTable) + => imageTable.Groups.AddRange(imageList + .Chunk(4) + .Select((x, i) => ($"Variation {i}", x.ToList())) + .ToList()); + + private static void CreateWaterGroups(List imageList, ImageTable imageTable) + { + imageTable.Groups.Add(("zoom 1", imageList[0..10])); + imageTable.Groups.Add(("zoom 2", imageList[10..20])); + imageTable.Groups.Add(("zoom 3", imageList[20..30])); + imageTable.Groups.Add(("zoom 4", imageList[30..40])); + imageTable.Groups.Add(("palettes", imageList[40..42])); + imageTable.Groups.Add(("icon-animation", imageList[42..58])); + imageTable.Groups.Add(("icon-interaction", imageList[58..60])); + imageTable.Groups.Add(("animation", imageList[60..76])); + } +} diff --git a/Definitions/ObjectModels/LocoObject.cs b/Definitions/ObjectModels/LocoObject.cs index 39ffd19d..2bcfc8d5 100644 --- a/Definitions/ObjectModels/LocoObject.cs +++ b/Definitions/ObjectModels/LocoObject.cs @@ -2,26 +2,31 @@ namespace Definitions.ObjectModels; -public class LocoObject : IHasGraphicsElements +public class LocoObject { - public LocoObject(ObjectType objectType, ILocoStruct obj, StringTable stringTable, List graphicsElements) + public LocoObject(ObjectType objectType, ILocoStruct obj, StringTable stringTable, ImageTable? imageTable = null) { ObjectType = objectType; Object = obj; StringTable = stringTable; - GraphicsElements = graphicsElements; - } - - public LocoObject(ObjectType objectType, ILocoStruct obj, StringTable stringTable) - { - ObjectType = objectType; - Object = obj; - StringTable = stringTable; - GraphicsElements = []; + ImageTable = imageTable; } public ObjectType ObjectType { get; init; } public ILocoStruct Object { get; set; } public StringTable StringTable { get; set; } - public List GraphicsElements { get; set; } + + public ImageTable? ImageTable { get; set; } } + +//public class LocoObjectWithGraphics : LocoObject +//{ +// public LocoObjectWithGraphics(ObjectType objectType, ILocoStruct obj, StringTable stringTable, ImageTable imageTable) +// : base(objectType, obj, stringTable) +// { +// ImageTable = imageTable; +// } + +// public ImageTable ImageTable { get; set; } +//} + diff --git a/Definitions/ObjectModels/Objects/Airport/AirportBuilding.cs b/Definitions/ObjectModels/Objects/Airport/AirportBuilding.cs index 5293e40f..95cf5f0c 100644 --- a/Definitions/ObjectModels/Objects/Airport/AirportBuilding.cs +++ b/Definitions/ObjectModels/Objects/Airport/AirportBuilding.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Airport; @@ -10,5 +11,6 @@ public class AirportBuilding : ILocoStruct public int8_t X { get; set; } public int8_t Y { get; set; } - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Airport/AirportObject.cs b/Definitions/ObjectModels/Objects/Airport/AirportObject.cs index 4418d6a5..ebb60698 100644 --- a/Definitions/ObjectModels/Objects/Airport/AirportObject.cs +++ b/Definitions/ObjectModels/Objects/Airport/AirportObject.cs @@ -1,8 +1,9 @@ -using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Airport; -public class AirportObject : ILocoStruct +public class AirportObject : ILocoStruct, IHasBuildingComponents { public int16_t BuildCostFactor { get; set; } public int16_t SellCostFactor { get; set; } @@ -17,27 +18,34 @@ public class AirportObject : ILocoStruct public uint16_t DesignedYear { get; set; } public uint16_t ObsoleteYear { get; set; } - public List BuildingHeights { get; set; } = []; - public List BuildingAnimations { get; set; } = []; - public List> BuildingVariations { get; set; } = []; + public BuildingComponentsModel BuildingComponents { get; set; } = new(); public List BuildingPositions { get; set; } = []; public List MovementNodes { get; set; } = []; public List MovementEdges { get; set; } = []; public uint8_t[] var_B6 { get; set; } = []; - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { + var bcValidationContext = new ValidationContext(BuildingComponents); + foreach (var result in BuildingComponents.Validate(bcValidationContext)) + { + yield return result; + } + if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32.", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult($"-{nameof(SellCostFactor)} must be less than or equal to {nameof(BuildCostFactor)}.", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } - return BuildCostFactor > 0; + if (BuildCostFactor <= 0) + { + yield return new ValidationResult($"{nameof(BuildCostFactor)} must be greater than 0.", [nameof(BuildCostFactor)]); + } } } diff --git a/Definitions/ObjectModels/Objects/Airport/MovementEdge.cs b/Definitions/ObjectModels/Objects/Airport/MovementEdge.cs index 5190db85..e18aba29 100644 --- a/Definitions/ObjectModels/Objects/Airport/MovementEdge.cs +++ b/Definitions/ObjectModels/Objects/Airport/MovementEdge.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Airport; @@ -12,5 +13,6 @@ public class MovementEdge : ILocoStruct public uint32_t MustBeClearEdges { get; set; } // Which edges must be clear to use the transition edge. should probably be some kind of flags? public uint32_t AtLeastOneClearEdges { get; set; } // Which edges must have at least one clear to use transition edge. should probably be some kind of flags? - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Airport/MovementNode.cs b/Definitions/ObjectModels/Objects/Airport/MovementNode.cs index f0f44fec..0a93fea6 100644 --- a/Definitions/ObjectModels/Objects/Airport/MovementNode.cs +++ b/Definitions/ObjectModels/Objects/Airport/MovementNode.cs @@ -1,5 +1,6 @@ using Definitions.ObjectModels.Types; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Airport; @@ -9,5 +10,6 @@ public class MovementNode : ILocoStruct public Pos3 Position { get; set; } public AirportMovementNodeFlags Flags { get; set; } - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Bridge/BridgeObject.cs b/Definitions/ObjectModels/Objects/Bridge/BridgeObject.cs index 010155f3..732b7175 100644 --- a/Definitions/ObjectModels/Objects/Bridge/BridgeObject.cs +++ b/Definitions/ObjectModels/Objects/Bridge/BridgeObject.cs @@ -1,4 +1,6 @@ using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; namespace Definitions.ObjectModels.Objects.Bridge; @@ -22,48 +24,46 @@ public class BridgeObject : ILocoStruct public List CompatibleTrackObjects { get; set; } = []; public List CompatibleRoadObjects { get; set; } = []; - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32 inclusive.", [nameof(CostIndex)]); } if (-SellCostFactor > BaseCostFactor) { - return false; + yield return new ValidationResult($"The negative of {nameof(SellCostFactor)} must be less than or equal to {nameof(BaseCostFactor)}.", [nameof(SellCostFactor), nameof(BaseCostFactor)]); } if (BaseCostFactor <= 0) { - return false; + yield return new ValidationResult($"{nameof(BaseCostFactor)} must be positive.", [nameof(BaseCostFactor)]); } if (HeightCostFactor < 0) { - return false; + yield return new ValidationResult($"{nameof(HeightCostFactor)} must be non-negative.", [nameof(HeightCostFactor)]); } if (DeckDepth is not 16 and not 32) { - return false; + yield return new ValidationResult($"{nameof(DeckDepth)} must be either 16 or 32.", [nameof(DeckDepth)]); } if (SpanLength is not 1 and not 2 and not 4) { - return false; + yield return new ValidationResult($"{nameof(SpanLength)} must be either 1, 2, or 4.", [nameof(SpanLength)]); } - //if (CompatibleTrackObjectCount > 7) - //{ - // return false; - //} - - //if (CompatibleRoadObjectCount > 7) - //{ - // return false; - //} + if (CompatibleTrackObjects.Count > 7) + { + yield return new ValidationResult($"{nameof(CompatibleTrackObjects)} must contain at most 7 entries.", [nameof(CompatibleTrackObjects)]); + } - return true; + if (CompatibleRoadObjects.Count > 7) + { + yield return new ValidationResult($"{nameof(CompatibleRoadObjects)} must contain at most 7 entries.", [nameof(CompatibleRoadObjects)]); + } } } diff --git a/Definitions/ObjectModels/Objects/Building/BuildingObject.cs b/Definitions/ObjectModels/Objects/Building/BuildingObject.cs index f7ed07bb..65725e66 100644 --- a/Definitions/ObjectModels/Objects/Building/BuildingObject.cs +++ b/Definitions/ObjectModels/Objects/Building/BuildingObject.cs @@ -1,16 +1,10 @@ +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Building; -public enum CardinalDirection : uint8_t -{ - South, - West, - North, - East, -} - -public class BuildingObject : ILocoStruct, IImageTableNameProvider +public class BuildingObject : ILocoStruct, IHasBuildingComponents { public uint16_t DesignedYear { get; set; } public uint16_t ObsoleteYear { get; set; } @@ -31,9 +25,7 @@ public class BuildingObject : ILocoStruct, IImageTableNameProvider public uint8_t var_AC { get; set; } - public List BuildingHeights { get; set; } = []; - public List BuildingAnimations { get; set; } = []; - public List> BuildingVariations { get; set; } = []; + public BuildingComponentsModel BuildingComponents { get; set; } = new(); public List ProducedQuantity { get; set; } = []; public List ProducedCargo { get; set; } = []; @@ -41,19 +33,17 @@ public class BuildingObject : ILocoStruct, IImageTableNameProvider public List ElevatorHeightSequences { get; set; } = []; - public bool Validate() - => ProducedQuantity.Count == 2 - && BuildingHeights.Count is not 0 and not > 63 - && BuildingAnimations.Count is not 0 and not > 63 - && BuildingHeights.Count == BuildingAnimations.Count - && BuildingVariations.Count is not 0 and <= 31; - - public bool TryGetImageName(int id, out string? value) + public IEnumerable Validate(ValidationContext validationContext) { - var direction = (CardinalDirection)(id % 4); - var level = id / 4; - value = $"{direction} | Level {level}"; - return true; + var bcValidationContext = new ValidationContext(BuildingComponents); + foreach (var result in BuildingComponents.Validate(bcValidationContext)) + { + yield return result; + } + + if (ProducedQuantity.Count != 2) + { + yield return new ValidationResult($"{nameof(ProducedQuantity)} must have exactly 2 entries.", [nameof(ProducedQuantity)]); + } } - } diff --git a/Definitions/ObjectModels/Objects/Building/CardinalDirection.cs b/Definitions/ObjectModels/Objects/Building/CardinalDirection.cs new file mode 100644 index 00000000..d8fd9777 --- /dev/null +++ b/Definitions/ObjectModels/Objects/Building/CardinalDirection.cs @@ -0,0 +1,9 @@ +namespace Definitions.ObjectModels.Objects.Building; + +public enum CardinalDirection : uint8_t +{ + South, + West, + North, + East, +} diff --git a/Definitions/ObjectModels/Objects/Cargo/CargoObject.cs b/Definitions/ObjectModels/Objects/Cargo/CargoObject.cs index e5f7aee2..c3ac4839 100644 --- a/Definitions/ObjectModels/Objects/Cargo/CargoObject.cs +++ b/Definitions/ObjectModels/Objects/Cargo/CargoObject.cs @@ -1,6 +1,9 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + namespace Definitions.ObjectModels.Objects.Cargo; -public class CargoObject : ILocoStruct, IImageTableNameProvider +public class CargoObject : ILocoStruct { public uint16_t CargoTransferTime { get; set; } public CargoCategory CargoCategory { get; set; } @@ -16,16 +19,16 @@ public class CargoObject : ILocoStruct, IImageTableNameProvider public uint8_t UnitSize { get; set; } public uint16_t var_02 { get; set; } - public bool Validate() - => var_02 <= 3840 - && CargoTransferTime != 0; - - public bool TryGetImageName(int id, out string? value) + public IEnumerable Validate(ValidationContext validationContext) { - value = id == 0 - ? "kInlineSprite" - : $"kStationPlatform{id}"; + if (var_02 > 3840) + { + yield return new ValidationResult($"{nameof(var_02)} must be less than or equal to 3840."); + } - return true; + if (CargoTransferTime == 0) + { + yield return new ValidationResult($"{nameof(CargoTransferTime)} must be non-zero."); + } } } diff --git a/Definitions/ObjectModels/Objects/CliffEdge/CliffEdgeObject.cs b/Definitions/ObjectModels/Objects/CliffEdge/CliffEdgeObject.cs index 16f2e052..b01b66ee 100644 --- a/Definitions/ObjectModels/Objects/CliffEdge/CliffEdgeObject.cs +++ b/Definitions/ObjectModels/Objects/CliffEdge/CliffEdgeObject.cs @@ -1,36 +1,9 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.CliffEdge; -public class CliffEdgeObject : ILocoStruct, IImageTableNameProvider +public class CliffEdgeObject : ILocoStruct { - public bool Validate() => true; - - public bool TryGetImageName(int id, out string? value) - { - if (id is >= 0 and <= 63) - { - var direction = id / 16 % 2 == 0 ? "west" : "east"; - var side = id is >= 16 and <= 47 ? "right" : "left"; - var level = id % 16; - value = $"south {direction} {side} {level}"; - return true; - } - - if (id is >= 64 and <= 69) - { - return ImageIdNameMap.TryGetValue(id, out value); - } - - value = null; - return false; - } - - public static readonly Dictionary ImageIdNameMap = new() - { - { 64, "north west slope 1" }, - { 65, "north west slope 2" }, - { 66, "north west slope 3" }, - { 67, "north east slope 1" }, - { 68, "north east slope 2" }, - { 69, "north east slope 3" }, - }; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Climate/ClimateObject.cs b/Definitions/ObjectModels/Objects/Climate/ClimateObject.cs index f1ede564..4d251c71 100644 --- a/Definitions/ObjectModels/Objects/Climate/ClimateObject.cs +++ b/Definitions/ObjectModels/Objects/Climate/ClimateObject.cs @@ -1,3 +1,5 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.Climate; public class ClimateObject : ILocoStruct @@ -10,7 +12,16 @@ public class ClimateObject : ILocoStruct public uint8_t WinterSnowLine { get; set; } public uint8_t SummerSnowLine { get; set; } - public bool Validate() - => WinterSnowLine <= SummerSnowLine - && FirstSeason < 4; + public IEnumerable Validate(ValidationContext validationContext) + { + if (WinterSnowLine > SummerSnowLine) + { + yield return new ValidationResult("WinterSnowLine must be less than or equal to SummerSnowLine", [nameof(WinterSnowLine), nameof(SummerSnowLine)]); + } + + if (FirstSeason >= 4) + { + yield return new ValidationResult("FirstSeason must be less than 4", [nameof(FirstSeason)]); + } + } } diff --git a/Definitions/ObjectModels/Objects/Common/BuildingComponentsModel.cs b/Definitions/ObjectModels/Objects/Common/BuildingComponentsModel.cs new file mode 100644 index 00000000..eba18d1b --- /dev/null +++ b/Definitions/ObjectModels/Objects/Common/BuildingComponentsModel.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.DataAnnotations; + +namespace Definitions.ObjectModels.Objects.Common; + +public interface IHasBuildingComponents +{ + BuildingComponentsModel BuildingComponents { get; set; } +} + +public class BuildingComponentsModel : ILocoStruct +{ + public List BuildingHeights { get; set; } = []; + public List BuildingAnimations { get; set; } = []; + public List> BuildingVariations { get; set; } = []; + + public IEnumerable Validate(ValidationContext validationContext) + { + if (BuildingHeights.Count is not 0 and not > 63) + { + yield return new ValidationResult($"{nameof(BuildingHeights)} must contain between 1 and 63 entries.", [nameof(BuildingHeights)]); + } + + if (BuildingAnimations.Count is not 0 and not > 63) + { + yield return new ValidationResult($"{nameof(BuildingAnimations)} must contain between 1 and 63 entries.", [nameof(BuildingAnimations)]); + } + + if (BuildingHeights.Count == BuildingAnimations.Count) + { + yield return new ValidationResult($"{nameof(BuildingHeights)} and {nameof(BuildingAnimations)} must contain the same number of entries.", [nameof(BuildingHeights), nameof(BuildingAnimations)]); + } + + if (BuildingVariations.Count is not 0 and <= 31) + { + yield return new ValidationResult($"{nameof(BuildingVariations)} must contain between 1 and 31 entries.", [nameof(BuildingVariations)]); + } + } +} diff --git a/Definitions/ObjectModels/Objects/Building/BuildingPartAnimation.cs b/Definitions/ObjectModels/Objects/Common/BuildingPartAnimation.cs similarity index 90% rename from Definitions/ObjectModels/Objects/Building/BuildingPartAnimation.cs rename to Definitions/ObjectModels/Objects/Common/BuildingPartAnimation.cs index ae78f983..5f4e6a1b 100644 --- a/Definitions/ObjectModels/Objects/Building/BuildingPartAnimation.cs +++ b/Definitions/ObjectModels/Objects/Common/BuildingPartAnimation.cs @@ -1,6 +1,6 @@ using System.ComponentModel; -namespace Definitions.ObjectModels.Objects.Building; +namespace Definitions.ObjectModels.Objects.Common; [TypeConverter(typeof(ExpandableObjectConverter))] public class BuildingPartAnimation diff --git a/Definitions/ObjectModels/Objects/Competitor/CompetitorObject.cs b/Definitions/ObjectModels/Objects/Competitor/CompetitorObject.cs index c9239abd..d0573c5f 100644 --- a/Definitions/ObjectModels/Objects/Competitor/CompetitorObject.cs +++ b/Definitions/ObjectModels/Objects/Competitor/CompetitorObject.cs @@ -1,6 +1,7 @@ +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Competitor; -public class CompetitorObject : ILocoStruct, IImageTableNameProvider +public class CompetitorObject : ILocoStruct { public NamePrefixFlags AvailableNamePrefixes { get; set; } // bitset public PlaystyleFlags AvailablePlaystyles { get; set; } // bitset @@ -10,48 +11,26 @@ public class CompetitorObject : ILocoStruct, IImageTableNameProvider public uint8_t Competitiveness { get; set; } public uint8_t var_37 { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (!Emotions.HasFlag(EmotionFlags.Neutral)) { - return false; + yield return new ValidationResult($"{nameof(Emotions)} must include {nameof(EmotionFlags.Neutral)}.", [nameof(Emotions)]); } if (Intelligence is < 1 or > 9) { - return false; + yield return new ValidationResult($"{nameof(Intelligence)} must be between 1 and 9 inclusive.", [nameof(Intelligence)]); } if (Aggressiveness is < 1 or > 9) { - return false; + yield return new ValidationResult($"{nameof(Aggressiveness)} must be between 1 and 9 inclusive.", [nameof(Aggressiveness)]); } - return Competitiveness is >= 1 and <= 9; + if (Competitiveness is < 1 or > 9) + { + yield return new ValidationResult($"{nameof(Competitiveness)} must be between 1 and 9 inclusive.", [nameof(Competitiveness)]); + } } - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static Dictionary ImageIdNameMap = new() - { - { 0, "smallNeutral" }, - { 1, "largeNeutral" }, - { 2, "smallHappy" }, - { 3, "largeHappy" }, - { 4, "smallWorried" }, - { 5, "largeWorried" }, - { 6, "smallThinking" }, - { 7, "largeThinking" }, - { 8, "smallDejected" }, - { 9, "largeDejected" }, - { 10, "smallSurprised" }, - { 11, "largeSurprised" }, - { 12, "smallScared" }, - { 13, "largeScared" }, - { 14, "smallAngry" }, - { 15, "largeAngry" }, - { 16, "smallDisgusted" }, - { 17, "largeDisgusted" }, - }; } diff --git a/Definitions/ObjectModels/Objects/Currency/CurrencyObject.cs b/Definitions/ObjectModels/Objects/Currency/CurrencyObject.cs index 6b303d13..c565326e 100644 --- a/Definitions/ObjectModels/Objects/Currency/CurrencyObject.cs +++ b/Definitions/ObjectModels/Objects/Currency/CurrencyObject.cs @@ -1,3 +1,5 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.Currency; public class CurrencyObject : ILocoStruct @@ -5,18 +7,16 @@ public class CurrencyObject : ILocoStruct public uint8_t Separator { get; set; } public uint8_t Factor { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (Separator > 4) { - return false; + yield return new ValidationResult("Separator must be between 0 and 4", [nameof(Separator)]); } if (Factor > 3) { - return false; + yield return new ValidationResult("Factor must be between 0 and 3", [nameof(Factor)]); } - - return true; } } diff --git a/Definitions/ObjectModels/Objects/Dock/DockObject.cs b/Definitions/ObjectModels/Objects/Dock/DockObject.cs index e7fbd68a..2f5ce690 100644 --- a/Definitions/ObjectModels/Objects/Dock/DockObject.cs +++ b/Definitions/ObjectModels/Objects/Dock/DockObject.cs @@ -1,9 +1,10 @@ -using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Dock; -public class DockObject : ILocoStruct +public class DockObject : ILocoStruct, IHasBuildingComponents { public int16_t BuildCostFactor { get; set; } public int16_t SellCostFactor { get; set; } @@ -14,22 +15,29 @@ public class DockObject : ILocoStruct public uint16_t ObsoleteYear { get; set; } public Pos2 BoatPosition { get; set; } - public List BuildingHeights { get; set; } = []; - public List BuildingAnimations { get; set; } = []; - public List> BuildingVariations { get; set; } = []; + public BuildingComponentsModel BuildingComponents { get; set; } = new(); - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { + var bcValidationContext = new ValidationContext(BuildingComponents); + foreach (var result in BuildingComponents.Validate(bcValidationContext)) + { + yield return result; + } + if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32 (inclusive).", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult($"{nameof(SellCostFactor)} must be between 0 and -{BuildCostFactor} (inclusive).", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } - return BuildCostFactor > 0; + if (BuildCostFactor <= 0) + { + yield return new ValidationResult($"{nameof(BuildCostFactor)} must be greater than 0.", [nameof(BuildCostFactor)]); + } } } diff --git a/Definitions/ObjectModels/Objects/HillShapes/HillShapesObject.cs b/Definitions/ObjectModels/Objects/HillShapes/HillShapesObject.cs index babd34e3..ed9dfd79 100644 --- a/Definitions/ObjectModels/Objects/HillShapes/HillShapesObject.cs +++ b/Definitions/ObjectModels/Objects/HillShapes/HillShapesObject.cs @@ -1,23 +1,13 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.HillShape; -public class HillShapesObject : ILocoStruct, IImageTableNameProvider +public class HillShapesObject : ILocoStruct { public uint8_t HillHeightMapCount { get; set; } public uint8_t MountainHeightMapCount { get; set; } public bool IsHeightMap { get; set; } - public bool Validate() => true; - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static Dictionary ImageIdNameMap = new() - { - { 0, "hill shape 1" }, - { 1, "hill shape 2" }, - { 2, "mountain shape 1" }, - { 3, "mountain shape 2" }, - { 4, "preview image" }, - - }; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Industry/IndustryObject.cs b/Definitions/ObjectModels/Objects/Industry/IndustryObject.cs index 9d9ff731..3a667db4 100644 --- a/Definitions/ObjectModels/Objects/Industry/IndustryObject.cs +++ b/Definitions/ObjectModels/Objects/Industry/IndustryObject.cs @@ -1,9 +1,10 @@ -using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Industry; -public class IndustryObject : ILocoStruct +public class IndustryObject : ILocoStruct, IHasBuildingComponents { public uint32_t FarmImagesPerGrowthStage { get; set; } public uint8_t MinNumBuildings { get; set; } @@ -34,72 +35,64 @@ public class IndustryObject : ILocoStruct public ObjectModelHeader? BuildingWall { get; set; } // Wall types that can be built around this industry public ObjectModelHeader? BuildingWallEntrance { get; set; } // Wall types that can be built around this industry - public List BuildingHeights { get; set; } = []; - public List BuildingAnimations { get; set; } = []; - public List> BuildingVariations { get; set; } = []; + public BuildingComponentsModel BuildingComponents { get; set; } = new(); public List> AnimationSequences { get; set; } = []; // Access with getAnimationSequence helper method public List UnkBuildingData { get; set; } = []; public List var_38 { get; set; } = []; // Access with getUnk38 helper method - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { - if (BuildingHeights.Count == 0 || BuildingAnimations.Count == 0) + var bcValidationContext = new ValidationContext(BuildingComponents); + foreach (var result in BuildingComponents.Validate(bcValidationContext)) { - return false; - } - - if (BuildingVariations.Count is 0 or > 31) - { - return false; + yield return result; } if (MaxNumBuildings < MinNumBuildings) { - return false; + yield return new ValidationResult("MaxNumBuildings must be greater than or equal to MinNumBuildings", [nameof(MaxNumBuildings), nameof(MinNumBuildings)]); } if (TotalOfTypeInScenario is 0 or > 32) { - return false; + yield return new ValidationResult("TotalOfTypeInScenario must be between 1 and 32", [nameof(TotalOfTypeInScenario)]); } // 230/256 = ~90% if (-SellCostFactor > BuildCostFactor * 230 / 256) { - return false; + yield return new ValidationResult("SellCostFactor must be at least -90% of BuildCostFactor", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } if (var_E8 > 8) { - return false; + yield return new ValidationResult("var_E8 must be between 0 and 8", [nameof(var_E8)]); } - switch (FarmTileNumImageAngles) + if (FarmTileNumImageAngles is not 1 or 2 or 4) { - case 1: - case 2: - case 4: - break; - default: - return false; + yield return new ValidationResult("FarmTileNumImageAngles must be 1, 2, or 4", [nameof(FarmTileNumImageAngles)]); } if (FarmGrowthStageWithNoProduction is not 0xFF and > 7) { - return false; + yield return new ValidationResult("FarmGrowthStageWithNoProduction must be between 0 and 7, or 0xFF", [nameof(FarmGrowthStageWithNoProduction)]); } if (FarmNumStagesOfGrowth > 8) { - return false; + yield return new ValidationResult("FarmNumStagesOfGrowth must be between 1 and 8", [nameof(FarmNumStagesOfGrowth)]); } if (InitialProductionRate[0].Min > 100) { - return false; + yield return new ValidationResult("InitialProductionRate[0].Min must be less than or equal to 100", [nameof(InitialProductionRate)]); } - return InitialProductionRate[1].Min <= 100; + if (InitialProductionRate[1].Min > 100) + { + yield return new ValidationResult("InitialProductionRate[1].Min must be less than or equal to 100", [nameof(InitialProductionRate)]); + } } } diff --git a/Definitions/ObjectModels/Objects/InterfaceSkin/InterfaceSkinObject.cs b/Definitions/ObjectModels/Objects/InterfaceSkin/InterfaceSkinObject.cs index 9471858c..2077751b 100644 --- a/Definitions/ObjectModels/Objects/InterfaceSkin/InterfaceSkinObject.cs +++ b/Definitions/ObjectModels/Objects/InterfaceSkin/InterfaceSkinObject.cs @@ -1,8 +1,10 @@ using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; namespace Definitions.ObjectModels.Objects.InterfaceSkin; -public class InterfaceSkinObject : ILocoStruct, IImageTableNameProvider +public class InterfaceSkinObject : ILocoStruct { public Colour MapTooltipObjectColour { get; set; } public Colour MapTooltipCargoColour { get; set; } @@ -23,482 +25,6 @@ public class InterfaceSkinObject : ILocoStruct, IImageTableNameProvider public Colour PlayerInfoToolbarColour { get; set; } public Colour TimeToolbarColour { get; set; } - public bool Validate() => true; - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static Dictionary ImageIdNameMap = new() - { - { 0, "preview_image" }, - { 1, "toolbar_pause" }, - { 2, "toolbar_pause_hover" }, - { 3, "toolbar_loadsave" }, - { 4, "toolbar_loadsave_hover" }, - { 5, "toolbar_zoom" }, - { 6, "toolbar_zoom_hover" }, - { 7, "toolbar_rotate" }, - { 8, "toolbar_rotate_hover" }, - { 9, "toolbar_terraform" }, - { 10, "toolbar_terraform_hover" }, - { 11, "toolbar_audio_active" }, - { 12, "toolbar_audio_active_hover" }, - { 13, "toolbar_audio_inactive" }, - { 14, "toolbar_audio_inactive_hover" }, - { 15, "toolbar_view" }, - { 16, "toolbar_view_hover" }, - { 17, "toolbar_towns" }, - { 18, "toolbar_towns_hover" }, - { 19, "toolbar_empty_opaque" }, - { 20, "toolbar_empty_opaque_hover" }, - { 21, "toolbar_empty_transparent" }, - { 22, "toolbar_empty_transparent_hover" }, - { 23, "toolbar_industries" }, - { 24, "toolbar_industries_hover" }, - { 25, "toolbar_airports" }, - { 26, "toolbar_airports_hover" }, - { 27, "toolbar_ports" }, - { 28, "toolbar_ports_hover" }, - { 29, "toolbar_cogwheels" }, - { 30, "toolbar_cogwheels_hover" }, - { 31, "toolbar_build_vehicle_train" }, - { 32, "toolbar_build_vehicle_train_hover" }, - { 33, "toolbar_build_vehicle_bus" }, - { 34, "toolbar_build_vehicle_bus_hover" }, - { 35, "toolbar_build_vehicle_truck" }, - { 36, "toolbar_build_vehicle_truck_hover" }, - { 37, "toolbar_build_vehicle_tram" }, - { 38, "toolbar_build_vehicle_tram_hover" }, - { 39, "toolbar_build_vehicle_airplane" }, - { 40, "toolbar_build_vehicle_airplane_hover" }, - { 41, "toolbar_build_vehicle_boat" }, - { 42, "toolbar_build_vehicle_boat_hover" }, - { 43, "toolbar_stations" }, - { 44, "toolbar_stations_hover" }, - { 45, "tab_awards" }, - { 46, "toolbar_menu_airport" }, - { 47, "toolbar_menu_ship_port" }, - { 48, "tab_cargo_ratings" }, - { 49, "tab_colour_scheme_frame0" }, - { 50, "tab_colour_scheme_frame1" }, - { 51, "tab_colour_scheme_frame2" }, - { 52, "tab_colour_scheme_frame3" }, - { 53, "tab_colour_scheme_frame4" }, - { 54, "tab_colour_scheme_frame5" }, - { 55, "tab_colour_scheme_frame6" }, - { 56, "tab_colour_scheme_frame7" }, - { 57, "tab_population_frame0" }, - { 58, "tab_population_frame1" }, - { 59, "tab_population_frame2" }, - { 60, "tab_population_frame3" }, - { 61, "tab_population_frame4" }, - { 62, "tab_population_frame5" }, - { 63, "tab_population_frame6" }, - { 64, "tab_population_frame7" }, - { 65, "tab_performance_index_frame0" }, - { 66, "tab_performance_index_frame1" }, - { 67, "tab_performance_index_frame2" }, - { 68, "tab_performance_index_frame3" }, - { 69, "tab_performance_index_frame4" }, - { 70, "tab_performance_index_frame5" }, - { 71, "tab_performance_index_frame6" }, - { 72, "tab_performance_index_frame7" }, - { 73, "tab_cargo_units_frame0" }, - { 74, "tab_cargo_units_frame1" }, - { 75, "tab_cargo_units_frame2" }, - { 76, "tab_cargo_units_frame3" }, - { 77, "tab_cargo_units_frame4" }, - { 78, "tab_cargo_units_frame5" }, - { 79, "tab_cargo_units_frame6" }, - { 80, "tab_cargo_units_frame7" }, - { 81, "tab_cargo_distance_frame0" }, - { 82, "tab_cargo_distance_frame1" }, - { 83, "tab_cargo_distance_frame2" }, - { 84, "tab_cargo_distance_frame3" }, - { 85, "tab_cargo_distance_frame4" }, - { 86, "tab_cargo_distance_frame5" }, - { 87, "tab_cargo_distance_frame6" }, - { 88, "tab_cargo_distance_frame7" }, - { 89, "tab_production_frame0" }, - { 90, "tab_production_frame1" }, - { 91, "tab_production_frame2" }, - { 92, "tab_production_frame3" }, - { 93, "tab_production_frame4" }, - { 94, "tab_production_frame5" }, - { 95, "tab_production_frame6" }, - { 96, "tab_production_frame7" }, - { 97, "tab_wrench_frame0" }, - { 98, "tab_wrench_frame1" }, - { 99, "tab_wrench_frame2" }, - { 100, "tab_wrench_frame3" }, - { 101, "tab_wrench_frame4" }, - { 102, "tab_wrench_frame5" }, - { 103, "tab_wrench_frame6" }, - { 104, "tab_wrench_frame7" }, - { 105, "tab_wrench_frame8" }, - { 106, "tab_wrench_frame9" }, - { 107, "tab_wrench_frame10" }, - { 108, "tab_wrench_frame11" }, - { 109, "tab_wrench_frame12" }, - { 110, "tab_wrench_frame13" }, - { 111, "tab_wrench_frame14" }, - { 112, "tab_wrench_frame15" }, - { 113, "tab_finances_frame0" }, - { 114, "tab_finances_frame1" }, - { 115, "tab_finances_frame2" }, - { 116, "tab_finances_frame3" }, - { 117, "tab_finances_frame4" }, - { 118, "tab_finances_frame5" }, - { 119, "tab_finances_frame6" }, - { 120, "tab_finances_frame7" }, - { 121, "tab_finances_frame8" }, - { 122, "tab_finances_frame9" }, - { 123, "tab_finances_frame10" }, - { 124, "tab_finances_frame11" }, - { 125, "tab_finances_frame12" }, - { 126, "tab_finances_frame13" }, - { 127, "tab_finances_frame14" }, - { 128, "tab_finances_frame15" }, - { 129, "tab_cup_frame0" }, - { 130, "tab_cup_frame1" }, - { 131, "tab_cup_frame2" }, - { 132, "tab_cup_frame3" }, - { 133, "tab_cup_frame4" }, - { 134, "tab_cup_frame5" }, - { 135, "tab_cup_frame6" }, - { 136, "tab_cup_frame7" }, - { 137, "tab_cup_frame8" }, - { 138, "tab_cup_frame9" }, - { 139, "tab_cup_frame10" }, - { 140, "tab_cup_frame11" }, - { 141, "tab_cup_frame12" }, - { 142, "tab_cup_frame13" }, - { 143, "tab_cup_frame14" }, - { 144, "tab_cup_frame15" }, - { 145, "tab_ratings_frame0" }, - { 146, "tab_ratings_frame1" }, - { 147, "tab_ratings_frame2" }, - { 148, "tab_ratings_frame3" }, - { 149, "tab_ratings_frame4" }, - { 150, "tab_ratings_frame5" }, - { 151, "tab_ratings_frame6" }, - { 152, "tab_ratings_frame7" }, - { 153, "tab_ratings_frame8" }, - { 154, "tab_ratings_frame9" }, - { 155, "tab_ratings_frame10" }, - { 156, "tab_ratings_frame11" }, - { 157, "tab_ratings_frame12" }, - { 158, "tab_ratings_frame13" }, - { 159, "tab_ratings_frame14" }, - { 160, "tab_ratings_frame15" }, - { 161, "tab_transported_frame0" }, - { 162, "tab_transported_frame1" }, - { 163, "tab_transported_frame2" }, - { 164, "tab_transported_frame3" }, - { 165, "tab_transported_frame4" }, - { 166, "tab_transported_frame5" }, - { 167, "tab_transported_frame6" }, - { 168, "tab_cogs_frame0" }, - { 169, "tab_cogs_frame1" }, - { 170, "tab_cogs_frame2" }, - { 171, "tab_cogs_frame3" }, - { 172, "tab_scenario_details" }, - { 173, "tab_company" }, - { 174, "tab_companies" }, - { 175, "toolbar_menu_zoom_in" }, - { 176, "toolbar_menu_zoom_out" }, - { 177, "toolbar_menu_rotate_clockwise" }, - { 178, "toolbar_menu_rotate_anti_clockwise" }, - { 179, "toolbar_menu_plant_trees" }, - { 180, "toolbar_menu_bulldozer" }, - { 181, "tab_company_details" }, - { 182, "all_stations" }, - { 183, "rail_stations" }, - { 184, "road_stations" }, - { 185, "airports" }, - { 186, "ship_ports" }, - { 187, "toolbar_menu_build_walls" }, - { 188, "phone" }, - { 189, "toolbar_menu_towns" }, - { 190, "toolbar_menu_stations" }, - { 191, "toolbar_menu_industries" }, - { 192, "tab_routes_frame_0" }, - { 193, "tab_routes_frame_1" }, - { 194, "tab_routes_frame_2" }, - { 195, "tab_routes_frame_3" }, - { 196, "tab_messages" }, - { 197, "tab_message_settings" }, - { 198, "tab_cargo_delivered_frame0" }, - { 199, "tab_cargo_delivered_frame1" }, - { 200, "tab_cargo_delivered_frame2" }, - { 201, "tab_cargo_delivered_frame3" }, - { 202, "tab_cargo_payment_rates" }, - { 203, "tab_vehicle_train_frame0" }, - { 204, "tab_vehicle_train_frame1" }, - { 205, "tab_vehicle_train_frame2" }, - { 206, "tab_vehicle_train_frame3" }, - { 207, "tab_vehicle_train_frame4" }, - { 208, "tab_vehicle_train_frame5" }, - { 209, "tab_vehicle_train_frame6" }, - { 210, "tab_vehicle_train_frame7" }, - { 211, "tab_vehicle_aircraft_frame0" }, - { 212, "tab_vehicle_aircraft_frame1" }, - { 213, "tab_vehicle_aircraft_frame2" }, - { 214, "tab_vehicle_aircraft_frame3" }, - { 215, "tab_vehicle_aircraft_frame4" }, - { 216, "tab_vehicle_aircraft_frame5" }, - { 217, "tab_vehicle_aircraft_frame6" }, - { 218, "tab_vehicle_aircraft_frame7" }, - { 219, "tab_vehicle_bus_frame0" }, - { 220, "tab_vehicle_bus_frame1" }, - { 221, "tab_vehicle_bus_frame2" }, - { 222, "tab_vehicle_bus_frame3" }, - { 223, "tab_vehicle_bus_frame4" }, - { 224, "tab_vehicle_bus_frame5" }, - { 225, "tab_vehicle_bus_frame6" }, - { 226, "tab_vehicle_bus_frame7" }, - { 227, "tab_vehicle_tram_frame0" }, - { 228, "tab_vehicle_tram_frame1" }, - { 229, "tab_vehicle_tram_frame2" }, - { 230, "tab_vehicle_tram_frame3" }, - { 231, "tab_vehicle_tram_frame4" }, - { 232, "tab_vehicle_tram_frame5" }, - { 233, "tab_vehicle_tram_frame6" }, - { 234, "tab_vehicle_tram_frame7" }, - { 235, "tab_vehicle_truck_frame0" }, - { 236, "tab_vehicle_truck_frame1" }, - { 237, "tab_vehicle_truck_frame2" }, - { 238, "tab_vehicle_truck_frame3" }, - { 239, "tab_vehicle_truck_frame4" }, - { 240, "tab_vehicle_truck_frame5" }, - { 241, "tab_vehicle_truck_frame6" }, - { 242, "tab_vehicle_truck_frame7" }, - { 243, "tab_vehicle_ship_frame0" }, - { 244, "tab_vehicle_ship_frame1" }, - { 245, "tab_vehicle_ship_frame2" }, - { 246, "tab_vehicle_ship_frame3" }, - { 247, "tab_vehicle_ship_frame4" }, - { 248, "tab_vehicle_ship_frame5" }, - { 249, "tab_vehicle_ship_frame6" }, - { 250, "tab_vehicle_ship_frame7" }, - { 251, "build_vehicle_train_frame_0" }, - { 252, "build_vehicle_train_frame_1" }, - { 253, "build_vehicle_train_frame_2" }, - { 254, "build_vehicle_train_frame_3" }, - { 255, "build_vehicle_train_frame_4" }, - { 256, "build_vehicle_train_frame_5" }, - { 257, "build_vehicle_train_frame_6" }, - { 258, "build_vehicle_train_frame_7" }, - { 259, "build_vehicle_train_frame_8" }, - { 260, "build_vehicle_train_frame_9" }, - { 261, "build_vehicle_train_frame_10" }, - { 262, "build_vehicle_train_frame_11" }, - { 263, "build_vehicle_train_frame_12" }, - { 264, "build_vehicle_train_frame_13" }, - { 265, "build_vehicle_train_frame_14" }, - { 266, "build_vehicle_train_frame_15" }, - { 267, "build_vehicle_aircraft_frame_0" }, - { 268, "build_vehicle_aircraft_frame_1" }, - { 269, "build_vehicle_aircraft_frame_2" }, - { 270, "build_vehicle_aircraft_frame_3" }, - { 271, "build_vehicle_aircraft_frame_4" }, - { 272, "build_vehicle_aircraft_frame_5" }, - { 273, "build_vehicle_aircraft_frame_6" }, - { 274, "build_vehicle_aircraft_frame_7" }, - { 275, "build_vehicle_aircraft_frame_8" }, - { 276, "build_vehicle_aircraft_frame_9" }, - { 277, "build_vehicle_aircraft_frame_10" }, - { 278, "build_vehicle_aircraft_frame_11" }, - { 279, "build_vehicle_aircraft_frame_12" }, - { 280, "build_vehicle_aircraft_frame_13" }, - { 281, "build_vehicle_aircraft_frame_14" }, - { 282, "build_vehicle_aircraft_frame_15" }, - { 283, "build_vehicle_bus_frame_0" }, - { 284, "build_vehicle_bus_frame_1" }, - { 285, "build_vehicle_bus_frame_2" }, - { 286, "build_vehicle_bus_frame_3" }, - { 287, "build_vehicle_bus_frame_4" }, - { 288, "build_vehicle_bus_frame_5" }, - { 289, "build_vehicle_bus_frame_6" }, - { 290, "build_vehicle_bus_frame_7" }, - { 291, "build_vehicle_bus_frame_8" }, - { 292, "build_vehicle_bus_frame_9" }, - { 293, "build_vehicle_bus_frame_10" }, - { 294, "build_vehicle_bus_frame_11" }, - { 295, "build_vehicle_bus_frame_12" }, - { 296, "build_vehicle_bus_frame_13" }, - { 297, "build_vehicle_bus_frame_14" }, - { 298, "build_vehicle_bus_frame_15" }, - { 299, "build_vehicle_tram_frame_0" }, - { 300, "build_vehicle_tram_frame_1" }, - { 301, "build_vehicle_tram_frame_2" }, - { 302, "build_vehicle_tram_frame_3" }, - { 303, "build_vehicle_tram_frame_4" }, - { 304, "build_vehicle_tram_frame_5" }, - { 305, "build_vehicle_tram_frame_6" }, - { 306, "build_vehicle_tram_frame_7" }, - { 307, "build_vehicle_tram_frame_8" }, - { 308, "build_vehicle_tram_frame_9" }, - { 309, "build_vehicle_tram_frame_10" }, - { 310, "build_vehicle_tram_frame_11" }, - { 311, "build_vehicle_tram_frame_12" }, - { 312, "build_vehicle_tram_frame_13" }, - { 313, "build_vehicle_tram_frame_14" }, - { 314, "build_vehicle_tram_frame_15" }, - { 315, "build_vehicle_truck_frame_0" }, - { 316, "build_vehicle_truck_frame_1" }, - { 317, "build_vehicle_truck_frame_2" }, - { 318, "build_vehicle_truck_frame_3" }, - { 319, "build_vehicle_truck_frame_4" }, - { 320, "build_vehicle_truck_frame_5" }, - { 321, "build_vehicle_truck_frame_6" }, - { 322, "build_vehicle_truck_frame_7" }, - { 323, "build_vehicle_truck_frame_8" }, - { 324, "build_vehicle_truck_frame_9" }, - { 325, "build_vehicle_truck_frame_10" }, - { 326, "build_vehicle_truck_frame_11" }, - { 327, "build_vehicle_truck_frame_12" }, - { 328, "build_vehicle_truck_frame_13" }, - { 329, "build_vehicle_truck_frame_14" }, - { 330, "build_vehicle_truck_frame_15" }, - { 331, "build_vehicle_ship_frame_0" }, - { 332, "build_vehicle_ship_frame_1" }, - { 333, "build_vehicle_ship_frame_2" }, - { 334, "build_vehicle_ship_frame_3" }, - { 335, "build_vehicle_ship_frame_4" }, - { 336, "build_vehicle_ship_frame_5" }, - { 337, "build_vehicle_ship_frame_6" }, - { 338, "build_vehicle_ship_frame_7" }, - { 339, "build_vehicle_ship_frame_8" }, - { 340, "build_vehicle_ship_frame_9" }, - { 341, "build_vehicle_ship_frame_10" }, - { 342, "build_vehicle_ship_frame_11" }, - { 343, "build_vehicle_ship_frame_12" }, - { 344, "build_vehicle_ship_frame_13" }, - { 345, "build_vehicle_ship_frame_14" }, - { 346, "build_vehicle_ship_frame_15" }, - { 347, "build_industry_frame_0" }, - { 348, "build_industry_frame_1" }, - { 349, "build_industry_frame_2" }, - { 350, "build_industry_frame_3" }, - { 351, "build_industry_frame_4" }, - { 352, "build_industry_frame_5" }, - { 353, "build_industry_frame_6" }, - { 354, "build_industry_frame_7" }, - { 355, "build_industry_frame_8" }, - { 356, "build_industry_frame_9" }, - { 357, "build_industry_frame_10" }, - { 358, "build_industry_frame_11" }, - { 359, "build_industry_frame_12" }, - { 360, "build_industry_frame_13" }, - { 361, "build_industry_frame_14" }, - { 362, "build_industry_frame_15" }, - { 363, "build_town_frame_0" }, - { 364, "build_town_frame_1" }, - { 365, "build_town_frame_2" }, - { 366, "build_town_frame_3" }, - { 367, "build_town_frame_4" }, - { 368, "build_town_frame_5" }, - { 369, "build_town_frame_6" }, - { 370, "build_town_frame_7" }, - { 371, "build_town_frame_8" }, - { 372, "build_town_frame_9" }, - { 373, "build_town_frame_10" }, - { 374, "build_town_frame_11" }, - { 375, "build_town_frame_12" }, - { 376, "build_town_frame_13" }, - { 377, "build_town_frame_14" }, - { 378, "build_town_frame_15" }, - { 379, "build_buildings_frame_0" }, - { 380, "build_buildings_frame_1" }, - { 381, "build_buildings_frame_2" }, - { 382, "build_buildings_frame_3" }, - { 383, "build_buildings_frame_4" }, - { 384, "build_buildings_frame_5" }, - { 385, "build_buildings_frame_6" }, - { 386, "build_buildings_frame_7" }, - { 387, "build_buildings_frame_8" }, - { 388, "build_buildings_frame_9" }, - { 389, "build_buildings_frame_10" }, - { 390, "build_buildings_frame_11" }, - { 391, "build_buildings_frame_12" }, - { 392, "build_buildings_frame_13" }, - { 393, "build_buildings_frame_14" }, - { 394, "build_buildings_frame_15" }, - { 395, "build_misc_buildings_frame_0" }, - { 396, "build_misc_buildings_frame_1" }, - { 397, "build_misc_buildings_frame_2" }, - { 398, "build_misc_buildings_frame_3" }, - { 399, "build_misc_buildings_frame_4" }, - { 400, "build_misc_buildings_frame_5" }, - { 401, "build_misc_buildings_frame_6" }, - { 402, "build_misc_buildings_frame_7" }, - { 403, "build_misc_buildings_frame_8" }, - { 404, "build_misc_buildings_frame_9" }, - { 405, "build_misc_buildings_frame_10" }, - { 406, "build_misc_buildings_frame_11" }, - { 407, "build_misc_buildings_frame_12" }, - { 408, "build_misc_buildings_frame_13" }, - { 409, "build_misc_buildings_frame_14" }, - { 410, "build_misc_buildings_frame_15" }, - { 411, "build_additional_train" }, - { 412, "build_additional_bus" }, - { 413, "build_additional_truck" }, - { 414, "build_additional_tram" }, - { 415, "build_additional_aircraft" }, - { 416, "build_additional_ship" }, - { 417, "build_headquarters" }, - { 418, "vehicle_train_frame_0" }, - { 419, "vehicle_train_frame_1" }, - { 420, "vehicle_train_frame_2" }, - { 421, "vehicle_train_frame_3" }, - { 422, "vehicle_train_frame_4" }, - { 423, "vehicle_train_frame_5" }, - { 424, "vehicle_train_frame_6" }, - { 425, "vehicle_train_frame_7" }, - { 426, "vehicle_aircraft_frame_0" }, - { 427, "vehicle_aircraft_frame_1" }, - { 428, "vehicle_aircraft_frame_2" }, - { 429, "vehicle_aircraft_frame_3" }, - { 430, "vehicle_aircraft_frame_4" }, - { 431, "vehicle_aircraft_frame_5" }, - { 432, "vehicle_aircraft_frame_6" }, - { 433, "vehicle_aircraft_frame_7" }, - { 434, "vehicle_buses_frame_0" }, - { 435, "vehicle_buses_frame_1" }, - { 436, "vehicle_buses_frame_2" }, - { 437, "vehicle_buses_frame_3" }, - { 438, "vehicle_buses_frame_4" }, - { 439, "vehicle_buses_frame_5" }, - { 440, "vehicle_buses_frame_6" }, - { 441, "vehicle_buses_frame_7" }, - { 442, "vehicle_trams_frame_0" }, - { 443, "vehicle_trams_frame_1" }, - { 444, "vehicle_trams_frame_2" }, - { 445, "vehicle_trams_frame_3" }, - { 446, "vehicle_trams_frame_4" }, - { 447, "vehicle_trams_frame_5" }, - { 448, "vehicle_trams_frame_6" }, - { 449, "vehicle_trams_frame_7" }, - { 450, "vehicle_trucks_frame_0" }, - { 451, "vehicle_trucks_frame_1" }, - { 452, "vehicle_trucks_frame_2" }, - { 453, "vehicle_trucks_frame_3" }, - { 454, "vehicle_trucks_frame_4" }, - { 455, "vehicle_trucks_frame_5" }, - { 456, "vehicle_trucks_frame_6" }, - { 457, "vehicle_trucks_frame_7" }, - { 458, "vehicle_ships_frame_0" }, - { 459, "vehicle_ships_frame_1" }, - { 460, "vehicle_ships_frame_2" }, - { 461, "vehicle_ships_frame_3" }, - { 462, "vehicle_ships_frame_4" }, - { 463, "vehicle_ships_frame_5" }, - { 464, "vehicle_ships_frame_6" }, - { 465, "vehicle_ships_frame_7" }, - { 466, "toolbar_menu_map_north" }, - { 467, "toolbar_menu_map_west" }, - { 468, "toolbar_menu_map_south" }, - { 469, "toolbar_menu_map_east" }, - }; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Land/LandObject.cs b/Definitions/ObjectModels/Objects/Land/LandObject.cs index 33e3d4a2..d542f19d 100644 --- a/Definitions/ObjectModels/Objects/Land/LandObject.cs +++ b/Definitions/ObjectModels/Objects/Land/LandObject.cs @@ -1,8 +1,9 @@ using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Land; -public class LandObject : ILocoStruct, IImageTableNameProvider +public class LandObject : ILocoStruct { public uint8_t CostIndex { get; set; } public uint8_t NumGrowthStages { get; set; } @@ -17,54 +18,31 @@ public class LandObject : ILocoStruct, IImageTableNameProvider public ObjectModelHeader CliffEdgeHeader { get; set; } public ObjectModelHeader? UnkObjectHeader { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be in the range 0-32.", [nameof(CostIndex)]); } if (CostFactor <= 0) { - return false; + yield return new ValidationResult($"{nameof(CostFactor)} must be positive.", [nameof(CostFactor)]); } if (NumGrowthStages < 1) { - return false; + yield return new ValidationResult($"{nameof(NumGrowthStages)} must be at least 1.", [nameof(NumGrowthStages)]); } if (NumGrowthStages > 8) { - return false; + yield return new ValidationResult($"{nameof(NumGrowthStages)} must be at most 8.", [nameof(NumGrowthStages)]); } - return NumImageAngles is 1 or 2 or 4; + if (NumImageAngles is not 1 or 2 or 4) + { + yield return new ValidationResult($"{nameof(NumImageAngles)} must be 1, 2, or 4.", [nameof(NumImageAngles)]); + } } - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static readonly Dictionary ImageIdNameMap = new() - { - { 0, "flat" }, - { 1, "west corner up" }, - { 2, "south corner up" }, - { 3, "north east slope" }, - { 4, "east corner up" }, - { 5, "west and east corner up" }, - { 6, "north west slope" }, - { 7, "north corner down" }, - { 8, "north corner up" }, - { 9, "south east slope" }, - { 10, "north and south corners up" }, - { 11, "east corner down" }, - { 12, "north west slope" }, - { 13, "south corner down" }, - { 14, "west corner down" }, - { 15, "south slope" }, - { 16, "north slope" }, - { 17, "east slope" }, - { 18, "west slope" } - }; } diff --git a/Definitions/ObjectModels/Objects/LevelCrossing/LevelCrossingObject.cs b/Definitions/ObjectModels/Objects/LevelCrossing/LevelCrossingObject.cs index 66cf30d8..d94316d7 100644 --- a/Definitions/ObjectModels/Objects/LevelCrossing/LevelCrossingObject.cs +++ b/Definitions/ObjectModels/Objects/LevelCrossing/LevelCrossingObject.cs @@ -1,3 +1,5 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.LevelCrossing; public class LevelCrossingObject : ILocoStruct @@ -11,22 +13,21 @@ public class LevelCrossingObject : ILocoStruct public uint8_t var_0A { get; set; } // something like IdleAnimationFrames or something public uint16_t DesignedYear { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (-SellCostFactor > CostFactor) { - return false; + yield return new ValidationResult("SellCostFactor must not be greater than CostFactor", [nameof(SellCostFactor), nameof(CostFactor)]); } if (CostFactor <= 0) { - return false; + yield return new ValidationResult("CostFactor must be positive", [nameof(CostFactor)]); } - return ClosingFrames switch + if (ClosingFrames is not 1 or 2 or 4 or 8 or 16 or 32) { - 1 or 2 or 4 or 8 or 16 or 32 => true, - _ => false, - }; + yield return new ValidationResult("ClosingFrames must be a power of two between 1 and 32 (inclusive)", [nameof(ClosingFrames)]); + } } } diff --git a/Definitions/ObjectModels/Objects/Region/RegionObject.cs b/Definitions/ObjectModels/Objects/Region/RegionObject.cs index 6919a447..159ba960 100644 --- a/Definitions/ObjectModels/Objects/Region/RegionObject.cs +++ b/Definitions/ObjectModels/Objects/Region/RegionObject.cs @@ -1,4 +1,5 @@ using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Region; @@ -10,6 +11,6 @@ public class RegionObject : ILocoStruct public List DependentObjects { get; set; } = []; public List CargoInfluenceTownFilter { get; set; } = []; - public bool Validate() - => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Road/RoadObject.cs b/Definitions/ObjectModels/Objects/Road/RoadObject.cs index 86f63754..a16c4e14 100644 --- a/Definitions/ObjectModels/Objects/Road/RoadObject.cs +++ b/Definitions/ObjectModels/Objects/Road/RoadObject.cs @@ -1,8 +1,9 @@ using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Road; -public class RoadObject : ILocoStruct, IImageTableNameProvider +public class RoadObject : ILocoStruct { public RoadTraitFlags RoadPieces { get; set; } public int16_t BuildCostFactor { get; set; } @@ -21,353 +22,41 @@ public class RoadObject : ILocoStruct, IImageTableNameProvider public List Bridges { get; set; } = []; public List Stations { get; set; } = []; - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { - // check missing in vanilla if (CostIndex >= 32) { - return false; + yield return new ValidationResult("CostIndex must be less than 32", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult("SellCostFactor must not be less than negative BuildCostFactor", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } if (BuildCostFactor <= 0) { - return false; + yield return new ValidationResult("BuildCostFactor must be greater than 0", [nameof(BuildCostFactor)]); } if (TunnelCostFactor <= 0) { - return false; + yield return new ValidationResult("TunnelCostFactor must be greater than 0", [nameof(TunnelCostFactor)]); } if (Bridges.Count > 7) { - return false; + yield return new ValidationResult("Bridges.Count must be 7 or less", [nameof(Bridges)]); } if (RoadMods.Count > 2) { - return false; + yield return new ValidationResult("RoadMods.Count must be 2 or less", [nameof(RoadMods)]); } - if (Flags.HasFlag(RoadObjectFlags.unk_03)) + if (Flags.HasFlag(RoadObjectFlags.unk_03) && RoadMods.Count != 0) { - return RoadMods.Count == 0; + yield return new ValidationResult("If unk_03 flag is set, RoadMods.Count must be 0", [nameof(Flags), nameof(RoadMods)]); } - - return true; } - - public bool TryGetImageName(int id, out string? value) - { - if (id is >= 0 and <= 33) - { - return ImageIdNameMap.TryGetValue(id, out value); - } - - // style dependent - return PaintStyle switch - { - 0 => ImageIdNameMap_Style0.TryGetValue(id, out value), - 1 => ImageIdNameMap_Style1.TryGetValue(id, out value), - 2 => ImageIdNameMap_Style2.TryGetValue(id, out value), - _ => throw new NotImplementedException(id.ToString()), - }; - } - - public static Dictionary ImageIdNameMap = new() - { - { 0, "uiPreviewImage0" }, - { 1, "uiPreviewImage1" }, - { 2, "uiPreviewImage2" }, - { 3, "uiPreviewImage3" }, - { 4, "uiPreviewImage4" }, - { 5, "uiPreviewImage5" }, - { 6, "uiPreviewImage6" }, - { 7, "uiPreviewImage7" }, - { 8, "uiPreviewImage8" }, - { 9, "uiPreviewImage9" }, - { 10, "uiPreviewImage10" }, - { 11, "uiPreviewImage11" }, - { 12, "uiPreviewImage12" }, - { 13, "uiPreviewImage13" }, - { 14, "uiPreviewImage14" }, - { 15, "uiPreviewImage15" }, - { 16, "uiPreviewImage0" }, - { 17, "uiPreviewImage1" }, - { 18, "uiPreviewImage2" }, - { 19, "uiPreviewImage3" }, - { 20, "uiPreviewImage4" }, - { 21, "uiPreviewImage5" }, - { 22, "uiPreviewImage6" }, - { 23, "uiPreviewImage7" }, - { 24, "uiPreviewImage8" }, - { 25, "uiPreviewImage9" }, - { 26, "uiPreviewImage10" }, - { 27, "uiPreviewImage11" }, - { 28, "uiPreviewImage12" }, - { 29, "uiPreviewImage13" }, - { 30, "uiPreviewImage14" }, - { 31, "uiPreviewImage15" }, - - { 32, "uiPickupFromTrack" }, - { 33, "uiPlaceOnTrack" }, - }; - - public static Dictionary ImageIdNameMap_Style0 = new() - { - { 34, "kStraight0NE" }, - { 35, "kStraight0SE" }, - { 36, "kRightCurveVerySmall0NE" }, - { 37, "kRightCurveVerySmall0SE" }, - { 38, "kRightCurveVerySmall0SW" }, - { 39, "kRightCurveVerySmall0NW" }, - { 40, "kJunctionLeft0NE" }, - { 41, "kJunctionLeft0SE" }, - { 42, "kJunctionLeft0SW" }, - { 43, "kJunctionLeft0NW" }, - { 44, "kJunctionCrossroads0NE" }, - { 45, "kRightCurveSmall0NE" }, - { 46, "kRightCurveSmall1NE" }, - { 47, "kRightCurveSmall2NE" }, - { 48, "kRightCurveSmall3NE" }, - { 49, "kRightCurveSmall0SE" }, - { 50, "kRightCurveSmall1SE" }, - { 51, "kRightCurveSmall2SE" }, - { 52, "kRightCurveSmall3SE" }, - { 53, "kRightCurveSmall0SW" }, - { 54, "kRightCurveSmall1SW" }, - { 55, "kRightCurveSmall2SW" }, - { 56, "kRightCurveSmall3SW" }, - { 57, "kRightCurveSmall0NW" }, - { 58, "kRightCurveSmall1NW" }, - { 59, "kRightCurveSmall2NW" }, - { 60, "kRightCurveSmall3NW" }, - { 61, "kStraightSlopeUp0NE" }, - { 62, "kStraightSlopeUp1NE" }, - { 63, "kStraightSlopeUp0SE" }, - { 64, "kStraightSlopeUp1SE" }, - { 65, "kStraightSlopeUp0SW" }, - { 66, "kStraightSlopeUp1SW" }, - { 67, "kStraightSlopeUp0NW" }, - { 68, "kStraightSlopeUp1NW" }, - { 69, "kStraightSteepSlopeUp0NE" }, - { 70, "kStraightSteepSlopeUp0SE" }, - { 71, "kStraightSteepSlopeUp0SW" }, - { 72, "kStraightSteepSlopeUp0NW" }, - { 73, "kTurnaround0NE" }, - { 74, "kTurnaround0SE" }, - { 75, "kTurnaround0SW" }, - { 76, "kTurnaround0NW" }, - }; - - public static Dictionary ImageIdNameMap_Style1 = new() - { - { 34, "kStraight0BallastNE" }, - { 35, "kStraight0BallastSE" }, - { 36, "kStraight0SleeperNE" }, - { 37, "kStraight0SleeperSE" }, - { 38, "kStraight0RailNE" }, - { 39, "kStraight0RailSE" }, - { 40, "kRightCurveSmall0BallastNE" }, - { 41, "kRightCurveSmall1BallastNE" }, - { 42, "kRightCurveSmall2BallastNE" }, - { 43, "kRightCurveSmall3BallastNE" }, - { 44, "kRightCurveSmall0BallastSE" }, - { 45, "kRightCurveSmall1BallastSE" }, - { 46, "kRightCurveSmall2BallastSE" }, - { 47, "kRightCurveSmall3BallastSE" }, - { 48, "kRightCurveSmall0BallastSW" }, - { 49, "kRightCurveSmall1BallastSW" }, - { 50, "kRightCurveSmall2BallastSW" }, - { 51, "kRightCurveSmall3BallastSW" }, - { 52, "kRightCurveSmall0BallastNW" }, - { 53, "kRightCurveSmall1BallastNW" }, - { 54, "kRightCurveSmall2BallastNW" }, - { 55, "kRightCurveSmall3BallastNW" }, - { 56, "kRightCurveSmall0SleeperNE" }, - { 57, "kRightCurveSmall1SleeperNE" }, - { 58, "kRightCurveSmall2SleeperNE" }, - { 59, "kRightCurveSmall3SleeperNE" }, - { 60, "kRightCurveSmall0SleeperSE" }, - { 61, "kRightCurveSmall1SleeperSE" }, - { 62, "kRightCurveSmall2SleeperSE" }, - { 63, "kRightCurveSmall3SleeperSE" }, - { 64, "kRightCurveSmall0SleeperSW" }, - { 65, "kRightCurveSmall1SleeperSW" }, - { 66, "kRightCurveSmall2SleeperSW" }, - { 67, "kRightCurveSmall3SleeperSW" }, - { 68, "kRightCurveSmall0SleeperNW" }, - { 69, "kRightCurveSmall1SleeperNW" }, - { 70, "kRightCurveSmall2SleeperNW" }, - { 71, "kRightCurveSmall3SleeperNW" }, - { 72, "kRightCurveSmall0RailNE" }, - { 73, "kRightCurveSmall1RailNE" }, - { 74, "kRightCurveSmall2RailNE" }, - { 75, "kRightCurveSmall3RailNE" }, - { 76, "kRightCurveSmall0RailSE" }, - { 77, "kRightCurveSmall1RailSE" }, - { 78, "kRightCurveSmall2RailSE" }, - { 79, "kRightCurveSmall3RailSE" }, - { 80, "kRightCurveSmall0RailSW" }, - { 81, "kRightCurveSmall1RailSW" }, - { 82, "kRightCurveSmall2RailSW" }, - { 83, "kRightCurveSmall3RailSW" }, - { 84, "kRightCurveSmall0RailNW" }, - { 85, "kRightCurveSmall1RailNW" }, - { 86, "kRightCurveSmall2RailNW" }, - { 87, "kRightCurveSmall3RailNW" }, - { 88, "kStraightSlopeUp0BallastNE" }, - { 89, "kStraightSlopeUp1BallastNE" }, - { 90, "kStraightSlopeUp0BallastSE" }, - { 91, "kStraightSlopeUp1BallastSE" }, - { 92, "kStraightSlopeUp0BallastSW" }, - { 93, "kStraightSlopeUp1BallastSW" }, - { 94, "kStraightSlopeUp0BallastNW" }, - { 95, "kStraightSlopeUp1BallastNW" }, - { 96, "kStraightSlopeUp0SleeperNE" }, - { 97, "kStraightSlopeUp1SleeperNE" }, - { 98, "kStraightSlopeUp0SleeperSE" }, - { 99, "kStraightSlopeUp1SleeperSE" }, - { 100, "kStraightSlopeUp0SleeperSW" }, - { 101, "kStraightSlopeUp1SleeperSW" }, - { 102, "kStraightSlopeUp0SleeperNW" }, - { 103, "kStraightSlopeUp1SleeperNW" }, - { 104, "kStraightSlopeUp0RailNE" }, - { 105, "kStraightSlopeUp1RailNE" }, - { 106, "kStraightSlopeUp0RailSE" }, - { 107, "kStraightSlopeUp1RailSE" }, - { 108, "kStraightSlopeUp0RailSW" }, - { 109, "kStraightSlopeUp1RailSW" }, - { 110, "kStraightSlopeUp0RailNW" }, - { 111, "kStraightSlopeUp1RailNW" }, - { 112, "kStraightSteepSlopeUp0BallastNE" }, - { 113, "kStraightSteepSlopeUp0BallastSE" }, - { 114, "kStraightSteepSlopeUp0BallastSW" }, - { 115, "kStraightSteepSlopeUp0BallastNW" }, - { 116, "kStraightSteepSlopeUp0SleeperNE" }, - { 117, "kStraightSteepSlopeUp0SleeperSE" }, - { 118, "kStraightSteepSlopeUp0SleeperSW" }, - { 119, "kStraightSteepSlopeUp0SleeperNW" }, - { 120, "kStraightSteepSlopeUp0RailNE" }, - { 121, "kStraightSteepSlopeUp0RailSE" }, - { 122, "kStraightSteepSlopeUp0RailSW" }, - { 123, "kStraightSteepSlopeUp0RailNW" }, - { 124, "kRightCurveVerySmall0BallastNE" }, - { 125, "kRightCurveVerySmall0BallastSE" }, - { 126, "kRightCurveVerySmall0BallastSW" }, - { 127, "kRightCurveVerySmall0BallastNW" }, - { 128, "kRightCurveVerySmall0SleeperNE" }, - { 129, "kRightCurveVerySmall0SleeperSE" }, - { 130, "kRightCurveVerySmall0SleeperSW" }, - { 131, "kRightCurveVerySmall0SleeperNW" }, - { 132, "kRightCurveVerySmall0RailNE" }, - { 133, "kRightCurveVerySmall0RailSE" }, - { 134, "kRightCurveVerySmall0RailSW" }, - { 135, "kRightCurveVerySmall0RailNW" }, - { 136, "kTurnaround0BallastNE" }, - { 137, "kTurnaround0BallastSE" }, - { 138, "kTurnaround0BallastSW" }, - { 139, "kTurnaround0BallastNW" }, - { 140, "kTurnaround0SleeperNE" }, - { 141, "kTurnaround0SleeperSE" }, - { 142, "kTurnaround0SleeperSW" }, - { 143, "kTurnaround0SleeperNW" }, - { 144, "kTurnaround0RailNE" }, - { 145, "kTurnaround0RailSE" }, - { 146, "kTurnaround0RailSW" }, - { 147, "kTurnaround0RailNW" }, - }; - - public static Dictionary ImageIdNameMap_Style2 = new() - { - { 34, "kStraight0NE" }, - { 35, "kStraight0SE" }, - { 36, "kLeftCurveVerySmall0NW" }, - { 37, "kLeftCurveVerySmall0NE" }, - { 38, "kLeftCurveVerySmall0SE" }, - { 39, "kLeftCurveVerySmall0SW" }, - { 40, "kJunctionLeft0NE" }, - { 41, "kJunctionLeft0SE" }, - { 42, "kJunctionLeft0SW" }, - { 43, "kJunctionLeft0NW" }, - { 44, "kJunctionCrossroads0NE" }, - { 45, "kLeftCurveSmall3NW" }, - { 46, "kLeftCurveSmall1NW" }, - { 47, "kLeftCurveSmall2NW" }, - { 48, "kLeftCurveSmall0NW" }, - { 49, "kLeftCurveSmall3NE" }, - { 50, "kLeftCurveSmall1NE" }, - { 51, "kLeftCurveSmall2NE" }, - { 52, "kLeftCurveSmall0NE" }, - { 53, "kLeftCurveSmall3SE" }, - { 54, "kLeftCurveSmall1SE" }, - { 55, "kLeftCurveSmall2SE" }, - { 56, "kLeftCurveSmall0SE" }, - { 57, "kLeftCurveSmall3SW" }, - { 58, "kLeftCurveSmall1SW" }, - { 59, "kLeftCurveSmall2SW" }, - { 60, "kLeftCurveSmall0SW" }, - { 61, "kStraightSlopeUp0NE" }, - { 62, "kStraightSlopeUp1NE" }, - { 63, "kStraightSlopeUp0SE" }, - { 64, "kStraightSlopeUp1SE" }, - { 65, "kStraightSlopeUp0SW" }, - { 66, "kStraightSlopeUp1SW" }, - { 67, "kStraightSlopeUp0NW" }, - { 68, "kStraightSlopeUp1NW" }, - { 69, "kStraightSteepSlopeUp0NE" }, - { 70, "kStraightSteepSlopeUp0SE" }, - { 71, "kStraightSteepSlopeUp0SW" }, - { 72, "kStraightSteepSlopeUp0NW" }, - { 73, "kTurnaround0NE" }, - { 74, "kTurnaround0SE" }, - { 75, "kTurnaround0SW" }, - { 76, "kTurnaround0NW" }, - - { 85, "kStraight0SW" }, - { 86, "kStraight0NW" }, - { 87, "kRightCurveVerySmall0NE" }, - { 88, "kRightCurveVerySmall0SE" }, - { 89, "kRightCurveVerySmall0SW" }, - { 90, "kRightCurveVerySmall0NW" }, - { 91, "kJunctionRight0NE" }, - { 92, "kJunctionRight0SE" }, - { 93, "kJunctionRight0SW" }, - { 94, "kJunctionRight0NW" }, - // Must duplicate kJunctionCrossroads0NE - { 95, "kJunctionCrossroads0NE2" }, - { 96, "kRightCurveSmall0NE" }, - { 97, "kRightCurveSmall1NE" }, - { 98, "kRightCurveSmall2NE" }, - { 99, "kRightCurveSmall3NE" }, - { 100, "kRightCurveSmall0SE" }, - { 101, "kRightCurveSmall1SE" }, - { 102, "kRightCurveSmall2SE" }, - { 103, "kRightCurveSmall3SE" }, - { 104, "kRightCurveSmall0SW" }, - { 105, "kRightCurveSmall1SW" }, - { 106, "kRightCurveSmall2SW" }, - { 107, "kRightCurveSmall3SW" }, - { 108, "kRightCurveSmall0NW" }, - { 109, "kRightCurveSmall1NW" }, - { 110, "kRightCurveSmall2NW" }, - { 111, "kRightCurveSmall3NW" }, - { 112, "kStraightSlopeDown1SW" }, - { 113, "kStraightSlopeDown0SW" }, - { 114, "kStraightSlopeDown1NW" }, - { 115, "kStraightSlopeDown0NW" }, - { 116, "kStraightSlopeDown1NE" }, - { 117, "kStraightSlopeDown0NE" }, - { 118, "kStraightSlopeDown1SE" }, - { 119, "kStraightSlopeDown0SE" }, - { 120, "kStraightSteepSlopeDown0SW" }, - { 121, "kStraightSteepSlopeDown0NW" }, - { 122, "kStraightSteepSlopeDown0NE" }, - { 123, "kStraightSteepSlopeDown0SE" }, - }; } diff --git a/Definitions/ObjectModels/Objects/RoadExtra/RoadExtraObject.cs b/Definitions/ObjectModels/Objects/RoadExtra/RoadExtraObject.cs index e0537de3..95ea453c 100644 --- a/Definitions/ObjectModels/Objects/RoadExtra/RoadExtraObject.cs +++ b/Definitions/ObjectModels/Objects/RoadExtra/RoadExtraObject.cs @@ -1,4 +1,5 @@ using Definitions.ObjectModels.Objects.Road; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.RoadExtra; @@ -10,24 +11,28 @@ public class RoadExtraObject : ILocoStruct public int16_t BuildCostFactor { get; set; } public int16_t SellCostFactor { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (PaintStyle >= 2) { - return false; + yield return new ValidationResult("PaintStyle must be 0 or 1", [nameof(PaintStyle)]); } // This check missing from vanilla if (CostIndex >= 32) { - return false; + yield return new ValidationResult("CostIndex must be less than 32", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult("SellCostFactor must be greater than or equal to -BuildCostFactor", [nameof(SellCostFactor), nameof(BuildCostFactor)]); + } - return BuildCostFactor > 0; + if (BuildCostFactor <= 0) + { + yield return new ValidationResult("BuildCostFactor must be greater than 0", [nameof(BuildCostFactor)]); + } } } diff --git a/Definitions/ObjectModels/Objects/RoadStation/RoadStationObject.cs b/Definitions/ObjectModels/Objects/RoadStation/RoadStationObject.cs index d05da515..9633dd8c 100644 --- a/Definitions/ObjectModels/Objects/RoadStation/RoadStationObject.cs +++ b/Definitions/ObjectModels/Objects/RoadStation/RoadStationObject.cs @@ -1,10 +1,11 @@ using Definitions.ObjectModels.Objects.Road; using Definitions.ObjectModels.Objects.Shared; using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.RoadStation; -public class RoadStationObject : ILocoStruct, IImageTableNameProvider +public class RoadStationObject : ILocoStruct { public uint8_t PaintStyle { get; set; } public uint8_t Height { get; set; } @@ -23,63 +24,36 @@ public class RoadStationObject : ILocoStruct, IImageTableNameProvider //public uint8_t[][][] CargoOffsetBytes { get; set; } public CargoOffset[][][] CargoOffsets { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (CostIndex >= 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 31 inclusive.", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult($"The negative of {nameof(SellCostFactor)} must be less than or equal to {nameof(BuildCostFactor)}.", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } if (BuildCostFactor <= 0) { - return false; + yield return new ValidationResult($"{nameof(BuildCostFactor)} must be positive.", [nameof(BuildCostFactor)]); } if (PaintStyle >= 1) { - return false; + yield return new ValidationResult($"{nameof(PaintStyle)} must be 0.", [nameof(PaintStyle)]); } if (CompatibleRoadObjects.Count > 7) { - return false; + yield return new ValidationResult($"{nameof(CompatibleRoadObjects)} must have at most 7 entries.", [nameof(CompatibleRoadObjects)]); } if (Flags.HasFlag(RoadStationObjectFlags.Passenger) && Flags.HasFlag(RoadStationObjectFlags.Freight)) { - return false; + yield return new ValidationResult($"Only one of {nameof(RoadStationObjectFlags.Passenger)} or {nameof(RoadStationObjectFlags.Freight)} can be set.", [nameof(Flags)]); } - - return true; } - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static Dictionary ImageIdNameMap = new() - { - { 0, "preview_image" }, - { 1, "preview_image_glass_overlay" }, - { 2, "North West Back Wall" }, - { 3, "North West Front Platform" }, - { 4, "North West Front Wall/Roof" }, - { 5, "North West Glass Overlay" }, - { 6, "South West Back Wall" }, - { 7, "South West Front Platform" }, - { 8, "South West Front Wall/Roof" }, - { 9, "South West Glass Overlay" }, - { 10, "South East Back Wall" }, - { 11, "South East Front Platform" }, - { 12, "South East Front Wall/Roof" }, - { 13, "South East Glass Overlay" }, - { 14, "North East Back Wall" }, - { 15, "North East Front Platform" }, - { 16, "North East Front Wall/Roof" }, - { 17, "North East Glass Overlay" }, - }; } diff --git a/Definitions/ObjectModels/Objects/Scaffolding/ScaffoldingObject.cs b/Definitions/ObjectModels/Objects/Scaffolding/ScaffoldingObject.cs index bf282e94..528b5b71 100644 --- a/Definitions/ObjectModels/Objects/Scaffolding/ScaffoldingObject.cs +++ b/Definitions/ObjectModels/Objects/Scaffolding/ScaffoldingObject.cs @@ -1,51 +1,12 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + namespace Definitions.ObjectModels.Objects.Scaffolding; public class ScaffoldingObject : ILocoStruct { public List SegmentHeights { get; set; } = []; public List RoofHeights { get; set; } = []; - public bool Validate() => true; - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static Dictionary ImageIdNameMap = new() - { - { 0, "type0 1x1 SegmentBack" }, - { 1, "type0 1x1 SegmentFront" }, - { 2, "type0 1x1 RoofNE" }, - { 3, "type0 1x1 RoofSE" }, - { 4, "type0 1x1 RoofSW" }, - { 5, "type0 1x1 RoofNW" }, - { 6, "type0 2x2 SegmentBack" }, - { 7, "type0 2x2 SegmentFront" }, - { 8, "type0 2x2 RoofNE" }, - { 9, "type0 2x2 RoofSE" }, - { 10, "type0 2x2 RoofSW" }, - { 11, "type0 2x2 RoofNW" }, - { 12, "type1 1x1 SegmentBack" }, - { 13, "type1 1x1 SegmentFront" }, - { 14, "type1 1x1 RoofNE" }, - { 15, "type1 1x1 RoofSE" }, - { 16, "type1 1x1 RoofSW" }, - { 17, "type1 1x1 RoofNW" }, - { 18, "type1 2x2 SegmentBack" }, - { 19, "type1 2x2 SegmentFront" }, - { 20, "type1 2x2 RoofNE" }, - { 21, "type1 2x2 RoofSE" }, - { 22, "type1 2x2 RoofSW" }, - { 23, "type1 2x2 RoofNW" }, - { 24, "type2 1x1 SegmentBack" }, - { 25, "type2 1x1 SegmentFront" }, - { 26, "type2 1x1 RoofNE" }, - { 27, "type2 1x1 RoofSE" }, - { 28, "type2 1x1 RoofSW" }, - { 29, "type2 1x1 RoofNW" }, - { 30, "type2 2x2 SegmentBack" }, - { 31, "type2 2x2 SegmentFront" }, - { 32, "type2 2x2 RoofNE" }, - { 33, "type2 2x2 RoofSE" }, - { 34, "type2 2x2 RoofSW" }, - { 35, "type2 2x2 RoofNW" }, - }; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/ScenarioText/ScenarioTextObject.cs b/Definitions/ObjectModels/Objects/ScenarioText/ScenarioTextObject.cs index 74a89a09..06c5c8d3 100644 --- a/Definitions/ObjectModels/Objects/ScenarioText/ScenarioTextObject.cs +++ b/Definitions/ObjectModels/Objects/ScenarioText/ScenarioTextObject.cs @@ -1,5 +1,9 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + namespace Definitions.ObjectModels.Objects.ScenarioText; public class ScenarioTextObject : ILocoStruct { - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Snow/SnowObject.cs b/Definitions/ObjectModels/Objects/Snow/SnowObject.cs index b9e6023f..64b6db1d 100644 --- a/Definitions/ObjectModels/Objects/Snow/SnowObject.cs +++ b/Definitions/ObjectModels/Objects/Snow/SnowObject.cs @@ -1,20 +1,10 @@ -namespace Definitions.ObjectModels.Objects.Snow; -public class SnowObject : ILocoStruct, IImageTableNameProvider -{ - public bool Validate() => true; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); +namespace Definitions.ObjectModels.Objects.Snow; - public static Dictionary ImageIdNameMap = new() - { - { 0, "surfaceEighthZoom" }, - { 10, "outlineEighthZoom" }, - { 19, "surfaceQuarterZoom" }, - { 38, "outlineQuarterZoom" }, - { 57, "surfaceHalfZoom" }, - { 76, "outlineHalfZoom" }, - { 95, "surfaceFullZoom" }, - { 114, "outlineFullZoom" }, - }; +public class SnowObject : ILocoStruct +{ + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Sound/SoundObject.cs b/Definitions/ObjectModels/Objects/Sound/SoundObject.cs index 69776203..ded371dc 100644 --- a/Definitions/ObjectModels/Objects/Sound/SoundObject.cs +++ b/Definitions/ObjectModels/Objects/Sound/SoundObject.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Sound; @@ -13,6 +14,11 @@ public class SoundObject : ILocoStruct public uint32_t NumUnkStructs { get; set; } [Browsable(false)] public byte[] UnkData { get; set; } - public bool Validate() - => SoundObjectData?.Offset >= 0; + public IEnumerable Validate(ValidationContext validationContext) + { + if (SoundObjectData?.Offset < -1) // todo: move validation into SoundObjectData + { + yield return new ValidationResult($"{nameof(SoundObjectData.Offset)} must be -1 or non-negative.", [nameof(SoundObjectData), nameof(SoundObjectData.Offset)]); + } + } } diff --git a/Definitions/ObjectModels/Objects/Steam/SteamObject.cs b/Definitions/ObjectModels/Objects/Steam/SteamObject.cs index a1ae6023..5686d987 100644 --- a/Definitions/ObjectModels/Objects/Steam/SteamObject.cs +++ b/Definitions/ObjectModels/Objects/Steam/SteamObject.cs @@ -1,4 +1,5 @@ using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Steam; @@ -15,6 +16,6 @@ public class SteamObject : ILocoStruct public List FrameInfoType1 { get; set; } = []; public List SoundEffects { get; set; } = []; - public bool Validate() - => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/StreetLight/StreetLightObject.cs b/Definitions/ObjectModels/Objects/StreetLight/StreetLightObject.cs index aced3133..d5fa87d4 100644 --- a/Definitions/ObjectModels/Objects/StreetLight/StreetLightObject.cs +++ b/Definitions/ObjectModels/Objects/StreetLight/StreetLightObject.cs @@ -1,27 +1,10 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.Streetlight; -public class StreetLightObject : ILocoStruct, IImageTableNameProvider +public class StreetLightObject : ILocoStruct { public List DesignedYears { get; set; } = []; - public bool Validate() - => true; - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static Dictionary ImageIdNameMap = new() - { - { 0, "style0NE" }, - { 1, "style0SE" }, - { 2, "style0SW" }, - { 3, "style0NW" }, - { 4, "style1NE" }, - { 5, "style1SE" }, - { 6, "style1SW" }, - { 7, "style1NW" }, - { 8, "style2NE" }, - { 9, "style2SE" }, - { 10, "style2SW" }, - { 11, "style2NW" }, - }; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/TownNames/TownNamesObject.cs b/Definitions/ObjectModels/Objects/TownNames/TownNamesObject.cs index 05f29ecf..eebf7299 100644 --- a/Definitions/ObjectModels/Objects/TownNames/TownNamesObject.cs +++ b/Definitions/ObjectModels/Objects/TownNames/TownNamesObject.cs @@ -1,8 +1,11 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.TownNames; public class TownNamesObject : ILocoStruct { public List Categories { get; set; } = []; - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Track/TrackObject.cs b/Definitions/ObjectModels/Objects/Track/TrackObject.cs index 9ebbbb14..877b553d 100644 --- a/Definitions/ObjectModels/Objects/Track/TrackObject.cs +++ b/Definitions/ObjectModels/Objects/Track/TrackObject.cs @@ -1,8 +1,9 @@ using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Track; -public class TrackObject : ILocoStruct, IImageTableNameProvider +public class TrackObject : ILocoStruct { public TrackTraitFlags TrackPieces { get; set; } public TrackTraitFlags StationTrackPieces { get; set; } @@ -22,466 +23,47 @@ public class TrackObject : ILocoStruct, IImageTableNameProvider public List Bridges { get; set; } = []; public List Stations { get; set; } = []; - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (var_06 >= 3) { - return false; + yield return new ValidationResult($"{nameof(var_06)} must be 0, 1, or 2.", [nameof(var_06)]); } // vanilla missed this check if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32 inclusive.", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult($"The negative of {nameof(SellCostFactor)} must be less than or equal to {nameof(BuildCostFactor)}.", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } if (BuildCostFactor <= 0) { - return false; + yield return new ValidationResult($"{nameof(BuildCostFactor)} must be positive.", [nameof(BuildCostFactor)]); } if (TunnelCostFactor <= 0) { - return false; + yield return new ValidationResult($"{nameof(TunnelCostFactor)} must be positive.", [nameof(TunnelCostFactor)]); } - if (TrackPieces.HasFlag(TrackTraitFlags.Diagonal | TrackTraitFlags.LargeCurve) - && TrackPieces.HasFlag(TrackTraitFlags.OneSided | TrackTraitFlags.VerySmallCurve)) + if (TrackPieces.HasFlag(TrackTraitFlags.Diagonal | TrackTraitFlags.LargeCurve) && TrackPieces.HasFlag(TrackTraitFlags.OneSided | TrackTraitFlags.VerySmallCurve)) { - return false; + yield return new ValidationResult($"{nameof(TrackPieces)} cannot include both {TrackTraitFlags.Diagonal} or {TrackTraitFlags.LargeCurve} and {TrackTraitFlags.OneSided} or {TrackTraitFlags.VerySmallCurve}.", [nameof(TrackPieces)]); } if (Bridges.Count > 7) { - return false; + yield return new ValidationResult($"{nameof(Bridges)} can contain at most 7 entries.", [nameof(Bridges)]); } - return Stations.Count <= 7; + if (Stations.Count > 7) + { + yield return new ValidationResult($"{nameof(Stations)} can contain at most 7 entries.", [nameof(Stations)]); + } } - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - // taken from OpenLoco TrackObject.h - public static Dictionary ImageIdNameMap = new() - { - { 0, "uiPreviewImage0" }, - { 1, "uiPreviewImage1" }, - { 2, "uiPreviewImage2" }, - { 3, "uiPreviewImage3" }, - { 4, "uiPreviewImage4" }, - { 5, "uiPreviewImage5" }, - { 6, "uiPreviewImage6" }, - { 7, "uiPreviewImage7" }, - { 8, "uiPreviewImage8" }, - { 9, "uiPreviewImage9" }, - { 10, "uiPreviewImage10" }, - { 11, "uiPreviewImage11" }, - { 12, "uiPreviewImage12" }, - { 13, "uiPreviewImage13" }, - { 14, "uiPreviewImage14" }, - { 15, "uiPreviewImage15" }, - { 16, "uiPickupFromTrack" }, - { 17, "uiPlaceOnTrack" }, - // - { 18, "straight0BallastNE" }, - { 19, "straight0BallastSE" }, - { 20, "straight0SleeperNE" }, - { 21, "straight0SleeperSE" }, - { 22, "straight0RailNE" }, - { 23, "straight0RailSE" }, - { 24, "rightCurveSmall0BallastNE" }, - { 25, "rightCurveSmall1BallastNE" }, - { 26, "rightCurveSmall2BallastNE" }, - { 27, "rightCurveSmall3BallastNE" }, - { 28, "rightCurveSmall0BallastSE" }, - { 29, "rightCurveSmall1BallastSE" }, - { 30, "rightCurveSmall2BallastSE" }, - { 31, "rightCurveSmall3BallastSE" }, - { 32, "rightCurveSmall0BallastSW" }, - { 33, "rightCurveSmall1BallastSW" }, - { 34, "rightCurveSmall2BallastSW" }, - { 35, "rightCurveSmall3BallastSW" }, - { 36, "rightCurveSmall0BallastNW" }, - { 37, "rightCurveSmall1BallastNW" }, - { 38, "rightCurveSmall2BallastNW" }, - { 39, "rightCurveSmall3BallastNW" }, - { 40, "rightCurveSmall0SleeperNE" }, - { 41, "rightCurveSmall1SleeperNE" }, - { 42, "rightCurveSmall2SleeperNE" }, - { 43, "rightCurveSmall3SleeperNE" }, - { 44, "rightCurveSmall0SleeperSE" }, - { 45, "rightCurveSmall1SleeperSE" }, - { 46, "rightCurveSmall2SleeperSE" }, - { 47, "rightCurveSmall3SleeperSE" }, - { 48, "rightCurveSmall0SleeperSW" }, - { 49, "rightCurveSmall1SleeperSW" }, - { 50, "rightCurveSmall2SleeperSW" }, - { 51, "rightCurveSmall3SleeperSW" }, - { 52, "rightCurveSmall0SleeperNW" }, - { 53, "rightCurveSmall1SleeperNW" }, - { 54, "rightCurveSmall2SleeperNW" }, - { 55, "rightCurveSmall3SleeperNW" }, - { 56, "rightCurveSmall0RailNE" }, - { 57, "rightCurveSmall1RailNE" }, - { 58, "rightCurveSmall2RailNE" }, - { 59, "rightCurveSmall3RailNE" }, - { 60, "rightCurveSmall0RailSE" }, - { 61, "rightCurveSmall1RailSE" }, - { 62, "rightCurveSmall2RailSE" }, - { 63, "rightCurveSmall3RailSE" }, - { 64, "rightCurveSmall0RailSW" }, - { 65, "rightCurveSmall1RailSW" }, - { 66, "rightCurveSmall2RailSW" }, - { 67, "rightCurveSmall3RailSW" }, - { 68, "rightCurveSmall0RailNW" }, - { 69, "rightCurveSmall1RailNW" }, - { 70, "rightCurveSmall2RailNW" }, - { 71, "rightCurveSmall3RailNW" }, - { 72, "rightCurveSmallSlopeUp0NE" }, - { 73, "rightCurveSmallSlopeUp1NE" }, - { 74, "rightCurveSmallSlopeUp2NE" }, - { 75, "rightCurveSmallSlopeUp3NE" }, - { 76, "rightCurveSmallSlopeUp0SE" }, - { 77, "rightCurveSmallSlopeUp1SE" }, - { 78, "rightCurveSmallSlopeUp2SE" }, - { 79, "rightCurveSmallSlopeUp3SE" }, - { 80, "rightCurveSmallSlopeUp0SW" }, - { 81, "rightCurveSmallSlopeUp1SW" }, - { 82, "rightCurveSmallSlopeUp2SW" }, - { 83, "rightCurveSmallSlopeUp3SW" }, - { 84, "rightCurveSmallSlopeUp0NW" }, - { 85, "rightCurveSmallSlopeUp1NW" }, - { 86, "rightCurveSmallSlopeUp2NW" }, - { 87, "rightCurveSmallSlopeUp3NW" }, - { 88, "rightCurveSmallSlopeDown0NE" }, - { 89, "rightCurveSmallSlopeDown1NE" }, - { 90, "rightCurveSmallSlopeDown2NE" }, - { 91, "rightCurveSmallSlopeDown3NE" }, - { 92, "rightCurveSmallSlopeDown0SE" }, - { 93, "rightCurveSmallSlopeDown1SE" }, - { 94, "rightCurveSmallSlopeDown2SE" }, - { 95, "rightCurveSmallSlopeDown3SE" }, - { 96, "rightCurveSmallSlopeDown0SW" }, - { 97, "rightCurveSmallSlopeDown1SW" }, - { 98, "rightCurveSmallSlopeDown2SW" }, - { 99, "rightCurveSmallSlopeDown3SW" }, - { 100, "rightCurveSmallSlopeDown0NW" }, - { 101, "rightCurveSmallSlopeDown1NW" }, - { 102, "rightCurveSmallSlopeDown2NW" }, - { 103, "rightCurveSmallSlopeDown3NW" }, - { 104, "rightCurveSmallSteepSlopeUp0NE" }, - { 105, "rightCurveSmallSteepSlopeUp1NE" }, - { 106, "rightCurveSmallSteepSlopeUp2NE" }, - { 107, "rightCurveSmallSteepSlopeUp3NE" }, - { 108, "rightCurveSmallSteepSlopeUp0SE" }, - { 109, "rightCurveSmallSteepSlopeUp1SE" }, - { 110, "rightCurveSmallSteepSlopeUp2SE" }, - { 111, "rightCurveSmallSteepSlopeUp3SE" }, - { 112, "rightCurveSmallSteepSlopeUp0SW" }, - { 113, "rightCurveSmallSteepSlopeUp1SW" }, - { 114, "rightCurveSmallSteepSlopeUp2SW" }, - { 115, "rightCurveSmallSteepSlopeUp3SW" }, - { 116, "rightCurveSmallSteepSlopeUp0NW" }, - { 117, "rightCurveSmallSteepSlopeUp1NW" }, - { 118, "rightCurveSmallSteepSlopeUp2NW" }, - { 119, "rightCurveSmallSteepSlopeUp3NW" }, - { 120, "rightCurveSmallSteepSlopeDown0NE" }, - { 121, "rightCurveSmallSteepSlopeDown1NE" }, - { 122, "rightCurveSmallSteepSlopeDown2NE" }, - { 123, "rightCurveSmallSteepSlopeDown3NE" }, - { 124, "rightCurveSmallSteepSlopeDown0SE" }, - { 125, "rightCurveSmallSteepSlopeDown1SE" }, - { 126, "rightCurveSmallSteepSlopeDown2SE" }, - { 127, "rightCurveSmallSteepSlopeDown3SE" }, - { 128, "rightCurveSmallSteepSlopeDown0SW" }, - { 129, "rightCurveSmallSteepSlopeDown1SW" }, - { 130, "rightCurveSmallSteepSlopeDown2SW" }, - { 131, "rightCurveSmallSteepSlopeDown3SW" }, - { 132, "rightCurveSmallSteepSlopeDown0NW" }, - { 133, "rightCurveSmallSteepSlopeDown1NW" }, - { 134, "rightCurveSmallSteepSlopeDown2NW" }, - { 135, "rightCurveSmallSteepSlopeDown3NW" }, - { 136, "rightCurve0BallastNE" }, - { 137, "rightCurve1BallastNE" }, - { 138, "rightCurve2BallastNE" }, - { 139, "rightCurve3BallastNE" }, - { 140, "rightCurve4BallastNE" }, - { 141, "rightCurve0BallastSE" }, - { 142, "rightCurve1BallastSE" }, - { 143, "rightCurve2BallastSE" }, - { 144, "rightCurve3BallastSE" }, - { 145, "rightCurve4BallastSE" }, - { 146, "rightCurve0BallastSW" }, - { 147, "rightCurve1BallastSW" }, - { 148, "rightCurve2BallastSW" }, - { 149, "rightCurve3BallastSW" }, - { 150, "rightCurve4BallastSW" }, - { 151, "rightCurve0BallastNW" }, - { 152, "rightCurve1BallastNW" }, - { 153, "rightCurve2BallastNW" }, - { 154, "rightCurve3BallastNW" }, - { 155, "rightCurve4BallastNW" }, - { 156, "rightCurve0SleeperNE" }, - { 157, "rightCurve1SleeperNE" }, - { 158, "rightCurve2SleeperNE" }, - { 159, "rightCurve3SleeperNE" }, - { 160, "rightCurve4SleeperNE" }, - { 161, "rightCurve0SleeperSE" }, - { 162, "rightCurve1SleeperSE" }, - { 163, "rightCurve2SleeperSE" }, - { 164, "rightCurve3SleeperSE" }, - { 165, "rightCurve4SleeperSE" }, - { 166, "rightCurve0SleeperSW" }, - { 167, "rightCurve1SleeperSW" }, - { 168, "rightCurve2SleeperSW" }, - { 169, "rightCurve3SleeperSW" }, - { 170, "rightCurve4SleeperSW" }, - { 171, "rightCurve0SleeperNW" }, - { 172, "rightCurve1SleeperNW" }, - { 173, "rightCurve2SleeperNW" }, - { 174, "rightCurve3SleeperNW" }, - { 175, "rightCurve4SleeperNW" }, - { 176, "rightCurve0RailNE" }, - { 177, "rightCurve1RailNE" }, - { 178, "rightCurve2RailNE" }, - { 179, "rightCurve3RailNE" }, - { 180, "rightCurve4RailNE" }, - { 181, "rightCurve0RailSE" }, - { 182, "rightCurve1RailSE" }, - { 183, "rightCurve2RailSE" }, - { 184, "rightCurve3RailSE" }, - { 185, "rightCurve4RailSE" }, - { 186, "rightCurve0RailSW" }, - { 187, "rightCurve1RailSW" }, - { 188, "rightCurve2RailSW" }, - { 189, "rightCurve3RailSW" }, - { 190, "rightCurve4RailSW" }, - { 191, "rightCurve0RailNW" }, - { 192, "rightCurve1RailNW" }, - { 193, "rightCurve2RailNW" }, - { 194, "rightCurve3RailNW" }, - { 195, "rightCurve4RailNW" }, - { 196, "straightSlopeUp0NE" }, - { 197, "straightSlopeUp1NE" }, - { 198, "straightSlopeUp0SE" }, - { 199, "straightSlopeUp1SE" }, - { 200, "straightSlopeUp0SW" }, - { 201, "straightSlopeUp1SW" }, - { 202, "straightSlopeUp0NW" }, - { 203, "straightSlopeUp1NW" }, - { 204, "straightSteepSlopeUp0NE" }, - { 205, "straightSteepSlopeUp0SE" }, - { 206, "straightSteepSlopeUp0SW" }, - { 207, "straightSteepSlopeUp0NW" }, - { 208, "rightCurveLarge0BallastNE" }, - { 209, "rightCurveLarge1BallastNE" }, - { 210, "rightCurveLarge2BallastNE" }, - { 211, "rightCurveLarge3BallastNE" }, - { 212, "rightCurveLarge4BallastNE" }, - { 213, "rightCurveLarge0BallastSE" }, - { 214, "rightCurveLarge1BallastSE" }, - { 215, "rightCurveLarge2BallastSE" }, - { 216, "rightCurveLarge3BallastSE" }, - { 217, "rightCurveLarge4BallastSE" }, - { 218, "rightCurveLarge0BallastSW" }, - { 219, "rightCurveLarge1BallastSW" }, - { 220, "rightCurveLarge2BallastSW" }, - { 221, "rightCurveLarge3BallastSW" }, - { 222, "rightCurveLarge4BallastSW" }, - { 223, "rightCurveLarge0BallastNW" }, - { 224, "rightCurveLarge1BallastNW" }, - { 225, "rightCurveLarge2BallastNW" }, - { 226, "rightCurveLarge3BallastNW" }, - { 227, "rightCurveLarge4BallastNW" }, - { 228, "leftCurveLarge0BallastNE" }, - { 229, "leftCurveLarge1BallastNE" }, - { 230, "leftCurveLarge2BallastNE" }, - { 231, "leftCurveLarge3BallastNE" }, - { 232, "leftCurveLarge4BallastNE" }, - { 233, "leftCurveLarge0BallastSE" }, - { 234, "leftCurveLarge1BallastSE" }, - { 235, "leftCurveLarge2BallastSE" }, - { 236, "leftCurveLarge3BallastSE" }, - { 237, "leftCurveLarge4BallastSE" }, - { 238, "leftCurveLarge0BallastSW" }, - { 239, "leftCurveLarge1BallastSW" }, - { 240, "leftCurveLarge2BallastSW" }, - { 241, "leftCurveLarge3BallastSW" }, - { 242, "leftCurveLarge4BallastSW" }, - { 243, "leftCurveLarge0BallastNW" }, - { 244, "leftCurveLarge1BallastNW" }, - { 245, "leftCurveLarge2BallastNW" }, - { 246, "leftCurveLarge3BallastNW" }, - { 247, "leftCurveLarge4BallastNW" }, - { 248, "rightCurveLarge0SleeperNE" }, - { 249, "rightCurveLarge1SleeperNE" }, - { 250, "rightCurveLarge2SleeperNE" }, - { 251, "rightCurveLarge3SleeperNE" }, - { 252, "rightCurveLarge4SleeperNE" }, - { 253, "rightCurveLarge0SleeperSE" }, - { 254, "rightCurveLarge1SleeperSE" }, - { 255, "rightCurveLarge2SleeperSE" }, - { 256, "rightCurveLarge3SleeperSE" }, - { 257, "rightCurveLarge4SleeperSE" }, - { 258, "rightCurveLarge0SleeperSW" }, - { 259, "rightCurveLarge1SleeperSW" }, - { 260, "rightCurveLarge2SleeperSW" }, - { 261, "rightCurveLarge3SleeperSW" }, - { 262, "rightCurveLarge4SleeperSW" }, - { 263, "rightCurveLarge0SleeperNW" }, - { 264, "rightCurveLarge1SleeperNW" }, - { 265, "rightCurveLarge2SleeperNW" }, - { 266, "rightCurveLarge3SleeperNW" }, - { 267, "rightCurveLarge4SleeperNW" }, - { 268, "leftCurveLarge0SleeperNE" }, - { 269, "leftCurveLarge1SleeperNE" }, - { 270, "leftCurveLarge2SleeperNE" }, - { 271, "leftCurveLarge3SleeperNE" }, - { 272, "leftCurveLarge4SleeperNE" }, - { 273, "leftCurveLarge0SleeperSE" }, - { 274, "leftCurveLarge1SleeperSE" }, - { 275, "leftCurveLarge2SleeperSE" }, - { 276, "leftCurveLarge3SleeperSE" }, - { 277, "leftCurveLarge4SleeperSE" }, - { 278, "leftCurveLarge0SleeperSW" }, - { 279, "leftCurveLarge1SleeperSW" }, - { 280, "leftCurveLarge2SleeperSW" }, - { 281, "leftCurveLarge3SleeperSW" }, - { 282, "leftCurveLarge4SleeperSW" }, - { 283, "leftCurveLarge0SleeperNW" }, - { 284, "leftCurveLarge1SleeperNW" }, - { 285, "leftCurveLarge2SleeperNW" }, - { 286, "leftCurveLarge3SleeperNW" }, - { 287, "leftCurveLarge4SleeperNW" }, - { 288, "rightCurveLarge0RailNE" }, - { 289, "rightCurveLarge1RailNE" }, - { 290, "rightCurveLarge2RailNE" }, - { 291, "rightCurveLarge3RailNE" }, - { 292, "rightCurveLarge4RailNE" }, - { 293, "rightCurveLarge0RailSE" }, - { 294, "rightCurveLarge1RailSE" }, - { 295, "rightCurveLarge2RailSE" }, - { 296, "rightCurveLarge3RailSE" }, - { 297, "rightCurveLarge4RailSE" }, - { 298, "rightCurveLarge0RailSW" }, - { 299, "rightCurveLarge1RailSW" }, - { 300, "rightCurveLarge2RailSW" }, - { 301, "rightCurveLarge3RailSW" }, - { 302, "rightCurveLarge4RailSW" }, - { 303, "rightCurveLarge0RailNW" }, - { 304, "rightCurveLarge1RailNW" }, - { 305, "rightCurveLarge2RailNW" }, - { 306, "rightCurveLarge3RailNW" }, - { 307, "rightCurveLarge4RailNW" }, - { 308, "leftCurveLarge0RailNE" }, - { 309, "leftCurveLarge1RailNE" }, - { 310, "leftCurveLarge2RailNE" }, - { 311, "leftCurveLarge3RailNE" }, - { 312, "leftCurveLarge4RailNE" }, - { 313, "leftCurveLarge0RailSE" }, - { 314, "leftCurveLarge1RailSE" }, - { 315, "leftCurveLarge2RailSE" }, - { 316, "leftCurveLarge3RailSE" }, - { 317, "leftCurveLarge4RailSE" }, - { 318, "leftCurveLarge0RailSW" }, - { 319, "leftCurveLarge1RailSW" }, - { 320, "leftCurveLarge2RailSW" }, - { 321, "leftCurveLarge3RailSW" }, - { 322, "leftCurveLarge4RailSW" }, - { 323, "leftCurveLarge0RailNW" }, - { 324, "leftCurveLarge1RailNW" }, - { 325, "leftCurveLarge2RailNW" }, - { 326, "leftCurveLarge3RailNW" }, - { 327, "leftCurveLarge4RailNW" }, - { 328, "diagonal0BallastNE" }, - { 329, "diagonal1BallastSW" }, - { 330, "diagonal1BallastNE" }, - { 331, "diagonal0BallastSW" }, - { 332, "diagonal0BallastSE" }, - { 333, "diagonal1BallastNW" }, - { 334, "diagonal1BallastSE" }, - { 335, "diagonal0BallastNW" }, - { 336, "diagonal0SleeperNE" }, - { 337, "diagonal1SleeperSW" }, - { 338, "diagonal1SleeperNE" }, - { 339, "diagonal0SleeperSW" }, - { 340, "diagonal0SleeperSE" }, - { 341, "diagonal1SleeperNW" }, - { 342, "diagonal1SleeperSE" }, - { 343, "diagonal0SleeperNW" }, - { 344, "diagonal0RailNE" }, - { 345, "diagonal1RailSW" }, - { 346, "diagonal1RailNE" }, - { 347, "diagonal0RailSW" }, - { 348, "diagonal0RailSE" }, - { 349, "diagonal1RailNW" }, - { 350, "diagonal1RailSE" }, - { 351, "diagonal0RailNW" }, - { 352, "sBendLeft0BallastNE" }, - { 353, "sBendLeft1BallastNE" }, - { 354, "sBendLeft1BallastSW" }, - { 355, "sBendLeft0BallastSW" }, - { 356, "sBendLeft0BallastSE" }, - { 357, "sBendLeft1BallastSE" }, - { 358, "sBendLeft1BallastNW" }, - { 359, "sBendLeft0BallastNW" }, - { 360, "sBendRight0BallastNE" }, - { 361, "sBendRight1BallastNE" }, - { 362, "sBendRight1BallastSW" }, - { 363, "sBendRight0BallastSW" }, - { 364, "sBendRight0BallastSE" }, - { 365, "sBendRight1BallastSE" }, - { 366, "sBendRight1BallastNW" }, - { 367, "sBendRight0BallastNW" }, - { 368, "sBendLeft0SleeperNE" }, - { 369, "sBendLeft1SleeperNE" }, - { 370, "sBendLeft1SleeperSW" }, - { 371, "sBendLeft0SleeperSW" }, - { 372, "sBendLeft0SleeperSE" }, - { 373, "sBendLeft1SleeperSE" }, - { 374, "sBendLeft1SleeperNW" }, - { 375, "sBendLeft0SleeperNW" }, - { 376, "sBendRight0SleeperNE" }, - { 377, "sBendRight1SleeperNE" }, - { 378, "sBendRight1SleeperSW" }, - { 379, "sBendRight0SleeperSW" }, - { 380, "sBendRight0SleeperSE" }, - { 381, "sBendRight1SleeperSE" }, - { 382, "sBendRight1SleeperNW" }, - { 383, "sBendRight0SleeperNW" }, - { 384, "sBendLeft0RailNE" }, - { 385, "sBendLeft1RailNE" }, - { 386, "sBendLeft1RailSW" }, - { 387, "sBendLeft0RailSW" }, - { 388, "sBendLeft0RailSE" }, - { 389, "sBendLeft1RailSE" }, - { 390, "sBendLeft1RailNW" }, - { 391, "sBendLeft0RailNW" }, - { 392, "sBendRight0RailNE" }, - { 393, "sBendRight1RailNE" }, - { 394, "sBendRight1RailSW" }, - { 395, "sBendRight0RailSW" }, - { 396, "sBendRight0RailSE" }, - { 397, "sBendRight1RailSE" }, - { 398, "sBendRight1RailNW" }, - { 399, "sBendRight0RailNW" }, - { 400, "rightCurveVerySmall0BallastNE" }, - { 401, "rightCurveVerySmall0BallastSE" }, - { 402, "rightCurveVerySmall0BallastSW" }, - { 403, "rightCurveVerySmall0BallastNW" }, - { 404, "rightCurveVerySmall0SleeperNE" }, - { 405, "rightCurveVerySmall0SleeperSE" }, - { 406, "rightCurveVerySmall0SleeperSW" }, - { 407, "rightCurveVerySmall0SleeperNW" }, - { 408, "rightCurveVerySmall0RailNE" }, - { 409, "rightCurveVerySmall0RailSE" }, - { 410, "rightCurveVerySmall0RailSW" }, - { 411, "rightCurveVerySmall0RailNW" }, - }; } diff --git a/Definitions/ObjectModels/Objects/TrackExtra/TrackExtraObject.cs b/Definitions/ObjectModels/Objects/TrackExtra/TrackExtraObject.cs index 1c22a6f6..132c2413 100644 --- a/Definitions/ObjectModels/Objects/TrackExtra/TrackExtraObject.cs +++ b/Definitions/ObjectModels/Objects/TrackExtra/TrackExtraObject.cs @@ -1,8 +1,9 @@ using Definitions.ObjectModels.Objects.Track; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.TrackExtra; -public class TrackExtraObject : ILocoStruct, IImageTableNameProvider +public class TrackExtraObject : ILocoStruct { public TrackTraitFlags TrackPieces { get; set; } public uint8_t PaintStyle { get; set; } @@ -10,248 +11,27 @@ public class TrackExtraObject : ILocoStruct, IImageTableNameProvider public int16_t BuildCostFactor { get; set; } public int16_t SellCostFactor { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (PaintStyle >= 2) { - return false; + yield return new ValidationResult($"{nameof(PaintStyle)} must be either 0 (normal) or 1 (fancy).", [nameof(PaintStyle)]); } // This check missing from vanilla if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32 inclusive.", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult($"The negative of {nameof(SellCostFactor)} must be less than or equal to {nameof(BuildCostFactor)}.", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } - return BuildCostFactor > 0; + if (BuildCostFactor > 0) + { + yield return new ValidationResult($"{nameof(BuildCostFactor)} must be greater than 0.", [nameof(BuildCostFactor)]); + } } - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id - 8, out value); - - // taken from OpenLoco TrackExtraObject.h - public static Dictionary ImageIdNameMap = new() - { - { -8, "previewImage0" }, - { -7, "previewImage1" }, - { -6, "previewImage2" }, - { -5, "previewImage3" }, - { -4, "previewImage4" }, - { -3, "previewImage5" }, - { -2, "previewImage6" }, - { -1, "previewImage7" }, - { 0, "kStraight0NE" }, - { 1, "kStraight0SE" }, - { 2, "kStraight0SW" }, - { 3, "kStraight0NW" }, - { 4, "kRightCurveSmall0NE" }, - { 5, "kRightCurveSmall1NE" }, - { 6, "kRightCurveSmall2NE" }, - { 7, "kRightCurveSmall3NE" }, - { 8, "kRightCurveSmall0SE" }, - { 9, "kRightCurveSmall1SE" }, - { 10, "kRightCurveSmall2SE" }, - { 11, "kRightCurveSmall3SE" }, - { 12, "kRightCurveSmall0SW" }, - { 13, "kRightCurveSmall1SW" }, - { 14, "kRightCurveSmall2SW" }, - { 15, "kRightCurveSmall3SW" }, - { 16, "kRightCurveSmall0NW" }, - { 17, "kRightCurveSmall1NW" }, - { 18, "kRightCurveSmall2NW" }, - { 19, "kRightCurveSmall3NW" }, - { 20, "kRightCurve0NE" }, - { 21, "kRightCurve1NE" }, - { 22, "kRightCurve2NE" }, - { 23, "kRightCurve3NE" }, - { 24, "kRightCurve4NE" }, - { 25, "kRightCurve0SE" }, - { 26, "kRightCurve1SE" }, - { 27, "kRightCurve2SE" }, - { 28, "kRightCurve3SE" }, - { 29, "kRightCurve4SE" }, - { 30, "kRightCurve0SW" }, - { 31, "kRightCurve1SW" }, - { 32, "kRightCurve2SW" }, - { 33, "kRightCurve3SW" }, - { 34, "kRightCurve4SW" }, - { 35, "kRightCurve0NW" }, - { 36, "kRightCurve1NW" }, - { 37, "kRightCurve2NW" }, - { 38, "kRightCurve3NW" }, - { 39, "kRightCurve4NW" }, - { 40, "kSBendLeft0NE" }, - { 41, "kSBendLeft1NE" }, - { 42, "kSBendLeft2NE" }, - { 43, "kSBendLeft3NE" }, - { 44, "kSBendLeft0SE" }, - { 45, "kSBendLeft1SE" }, - { 46, "kSBendLeft2SE" }, - { 47, "kSBendLeft3SE" }, - { 48, "kSBendLeft3SW" }, - { 49, "kSBendLeft2SW" }, - { 50, "kSBendLeft1SW" }, - { 51, "kSBendLeft0SW" }, - { 52, "kSBendLeft3NW" }, - { 53, "kSBendLeft2NW" }, - { 54, "kSBendLeft1NW" }, - { 55, "kSBendLeft0NW" }, - { 56, "kSBendRight0NE" }, - { 57, "kSBendRight1NE" }, - { 58, "kSBendRight2NE" }, - { 59, "kSBendRight3NE" }, - { 60, "kSBendRight0SE" }, - { 61, "kSBendRight1SE" }, - { 62, "kSBendRight2SE" }, - { 63, "kSBendRight3SE" }, - { 64, "kSBendRight3SW" }, - { 65, "kSBendRight2SW" }, - { 66, "kSBendRight1SW" }, - { 67, "kSBendRight0SW" }, - { 68, "kSBendRight3NW" }, - { 69, "kSBendRight2NW" }, - { 70, "kSBendRight1NW" }, - { 71, "kSBendRight0NW" }, - { 72, "kStraightSlopeUp0NE" }, - { 73, "kStraightSlopeUp1NE" }, - { 74, "kStraightSlopeUp0SE" }, - { 75, "kStraightSlopeUp1SE" }, - { 76, "kStraightSlopeUp0SW" }, - { 77, "kStraightSlopeUp1SW" }, - { 78, "kStraightSlopeUp0NW" }, - { 79, "kStraightSlopeUp1NW" }, - { 80, "kStraightSteepSlopeUp0NE" }, - { 81, "kStraightSteepSlopeUp0SE" }, - { 82, "kStraightSteepSlopeUp0SW" }, - { 83, "kStraightSteepSlopeUp0NW" }, - { 84, "kRightCurveSmallSlopeUp0NE" }, - { 85, "kRightCurveSmallSlopeUp1NE" }, - { 86, "kRightCurveSmallSlopeUp2NE" }, - { 87, "kRightCurveSmallSlopeUp3NE" }, - { 88, "kRightCurveSmallSlopeUp0SE" }, - { 89, "kRightCurveSmallSlopeUp1SE" }, - { 90, "kRightCurveSmallSlopeUp2SE" }, - { 91, "kRightCurveSmallSlopeUp3SE" }, - { 92, "kRightCurveSmallSlopeUp0SW" }, - { 93, "kRightCurveSmallSlopeUp1SW" }, - { 94, "kRightCurveSmallSlopeUp2SW" }, - { 95, "kRightCurveSmallSlopeUp3SW" }, - { 96, "kRightCurveSmallSlopeUp0NW" }, - { 97, "kRightCurveSmallSlopeUp1NW" }, - { 98, "kRightCurveSmallSlopeUp2NW" }, - { 99, "kRightCurveSmallSlopeUp3NW" }, - { 100, "kRightCurveSmallSlopeDown0NE" }, - { 101, "kRightCurveSmallSlopeDown1NE" }, - { 102, "kRightCurveSmallSlopeDown2NE" }, - { 103, "kRightCurveSmallSlopeDown3NE" }, - { 104, "kRightCurveSmallSlopeDown0SE" }, - { 105, "kRightCurveSmallSlopeDown1SE" }, - { 106, "kRightCurveSmallSlopeDown2SE" }, - { 107, "kRightCurveSmallSlopeDown3SE" }, - { 108, "kRightCurveSmallSlopeDown0SW" }, - { 109, "kRightCurveSmallSlopeDown1SW" }, - { 110, "kRightCurveSmallSlopeDown2SW" }, - { 111, "kRightCurveSmallSlopeDown3SW" }, - { 112, "kRightCurveSmallSlopeDown0NW" }, - { 113, "kRightCurveSmallSlopeDown1NW" }, - { 114, "kRightCurveSmallSlopeDown2NW" }, - { 115, "kRightCurveSmallSlopeDown3NW" }, - { 116, "kRightCurveSmallSteepSlopeUp0NE" }, - { 117, "kRightCurveSmallSteepSlopeUp1NE" }, - { 118, "kRightCurveSmallSteepSlopeUp2NE" }, - { 119, "kRightCurveSmallSteepSlopeUp3NE" }, - { 120, "kRightCurveSmallSteepSlopeUp0SE" }, - { 121, "kRightCurveSmallSteepSlopeUp1SE" }, - { 122, "kRightCurveSmallSteepSlopeUp2SE" }, - { 123, "kRightCurveSmallSteepSlopeUp3SE" }, - { 124, "kRightCurveSmallSteepSlopeUp0SW" }, - { 125, "kRightCurveSmallSteepSlopeUp1SW" }, - { 126, "kRightCurveSmallSteepSlopeUp2SW" }, - { 127, "kRightCurveSmallSteepSlopeUp3SW" }, - { 128, "kRightCurveSmallSteepSlopeUp0NW" }, - { 129, "kRightCurveSmallSteepSlopeUp1NW" }, - { 130, "kRightCurveSmallSteepSlopeUp2NW" }, - { 131, "kRightCurveSmallSteepSlopeUp3NW" }, - { 132, "kRightCurveSmallSteepSlopeDown0NE" }, - { 133, "kRightCurveSmallSteepSlopeDown1NE" }, - { 134, "kRightCurveSmallSteepSlopeDown2NE" }, - { 135, "kRightCurveSmallSteepSlopeDown3NE" }, - { 136, "kRightCurveSmallSteepSlopeDown0SE" }, - { 137, "kRightCurveSmallSteepSlopeDown1SE" }, - { 138, "kRightCurveSmallSteepSlopeDown2SE" }, - { 139, "kRightCurveSmallSteepSlopeDown3SE" }, - { 140, "kRightCurveSmallSteepSlopeDown0SW" }, - { 141, "kRightCurveSmallSteepSlopeDown1SW" }, - { 142, "kRightCurveSmallSteepSlopeDown2SW" }, - { 143, "kRightCurveSmallSteepSlopeDown3SW" }, - { 144, "kRightCurveSmallSteepSlopeDown0NW" }, - { 145, "kRightCurveSmallSteepSlopeDown1NW" }, - { 146, "kRightCurveSmallSteepSlopeDown2NW" }, - { 147, "kRightCurveSmallSteepSlopeDown3NW" }, - { 148, "kRightCurveLarge0NE" }, - { 149, "kRightCurveLarge1NE" }, - { 150, "kRightCurveLarge2NE" }, - { 151, "kRightCurveLarge3NE" }, - { 152, "kRightCurveLarge4NE" }, - { 153, "kRightCurveLarge0SE" }, - { 154, "kRightCurveLarge1SE" }, - { 155, "kRightCurveLarge2SE" }, - { 156, "kRightCurveLarge3SE" }, - { 157, "kRightCurveLarge4SE" }, - { 158, "kRightCurveLarge0SW" }, - { 159, "kRightCurveLarge1SW" }, - { 160, "kRightCurveLarge2SW" }, - { 161, "kRightCurveLarge3SW" }, - { 162, "kRightCurveLarge4SW" }, - { 163, "kRightCurveLarge0NW" }, - { 164, "kRightCurveLarge1NW" }, - { 165, "kRightCurveLarge2NW" }, - { 166, "kRightCurveLarge3NW" }, - { 167, "kRightCurveLarge4NW" }, - { 168, "kLeftCurveLarge0NE" }, - { 169, "kLeftCurveLarge1NE" }, - { 170, "kLeftCurveLarge2NE" }, - { 171, "kLeftCurveLarge3NE" }, - { 172, "kLeftCurveLarge4NE" }, - { 173, "kLeftCurveLarge0SE" }, - { 174, "kLeftCurveLarge1SE" }, - { 175, "kLeftCurveLarge2SE" }, - { 176, "kLeftCurveLarge3SE" }, - { 177, "kLeftCurveLarge4SE" }, - { 178, "kLeftCurveLarge0SW" }, - { 179, "kLeftCurveLarge1SW" }, - { 180, "kLeftCurveLarge2SW" }, - { 181, "kLeftCurveLarge3SW" }, - { 182, "kLeftCurveLarge4SW" }, - { 183, "kLeftCurveLarge0NW" }, - { 184, "kLeftCurveLarge1NW" }, - { 185, "kLeftCurveLarge2NW" }, - { 186, "kLeftCurveLarge3NW" }, - { 187, "kLeftCurveLarge4NW" }, - { 188, "kDiagonal0NE" }, - { 189, "kDiagonal2NE" }, - { 190, "kDiagonal1NE" }, - { 191, "kDiagonal3NE" }, - { 192, "kDiagonal0SE" }, - { 193, "kDiagonal2SE" }, - { 194, "kDiagonal1SE" }, - { 195, "kDiagonal3SE" }, - { 196, "kDiagonal0SW" }, - { 197, "kDiagonal2SW" }, - { 198, "kDiagonal1SW" }, - { 199, "kDiagonal3SW" }, - { 200, "kDiagonal0NW" }, - { 201, "kDiagonal2NW" }, - { 202, "kDiagonal1NW" }, - { 203, "kDiagonal3NW" }, - { 204, "kRightCurveVerySmall0NE" }, - { 205, "kRightCurveVerySmall0SE" }, - { 206, "kRightCurveVerySmall0SW" }, - { 207, "kRightCurveVerySmall0NW" }, - }; } diff --git a/Definitions/ObjectModels/Objects/TrackSignal/TrackSignalObject.cs b/Definitions/ObjectModels/Objects/TrackSignal/TrackSignalObject.cs index 7d4d9607..b18a4aa3 100644 --- a/Definitions/ObjectModels/Objects/TrackSignal/TrackSignalObject.cs +++ b/Definitions/ObjectModels/Objects/TrackSignal/TrackSignalObject.cs @@ -1,8 +1,9 @@ using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.TrackSignal; -public class TrackSignalObject : ILocoStruct, IImageTableNameProvider +public class TrackSignalObject : ILocoStruct { public TrackSignalObjectFlags Flags { get; set; } public uint8_t AnimationSpeed { get; set; } @@ -15,57 +16,32 @@ public class TrackSignalObject : ILocoStruct, IImageTableNameProvider public uint16_t ObsoleteYear { get; set; } public List CompatibleTrackObjects { get; set; } = []; - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { - // animationSpeed must be 1 less than a power of 2 (its a mask) - switch (AnimationSpeed) + if (AnimationSpeed is not 0 and not 1 and not 3 and not 7 and not 15) { - case 0: - case 1: - case 3: - case 7: - case 15: - break; - default: - return false; + // animationSpeed must be 1 less than a power of 2 (its a mask) + yield return new ValidationResult($"{nameof(AnimationSpeed)} must be 0, 1, 3, 7, or 15.", [nameof(AnimationSpeed)]); } - switch (NumFrames) + if (NumFrames is not 4 or 7 or 10) { - case 4: - case 7: - case 10: - break; - default: - return false; + yield return new ValidationResult($"{nameof(NumFrames)} must be 4, 7, or 10.", [nameof(NumFrames)]); } if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32 inclusive.", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult($"The negative of {nameof(SellCostFactor)} must be less than or equal to {nameof(BuildCostFactor)}.", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } if (CompatibleTrackObjects.Count > 7) { - return false; + yield return new ValidationResult($"{nameof(CompatibleTrackObjects)} must have at most 7 entries.", [nameof(CompatibleTrackObjects)]); } - - return true; } - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static Dictionary ImageIdNameMap = new() - { - { 80, "redLights" }, - { 88, "redLights2" }, - { 96, "greenLights" }, - { 104, "greenLights2" }, - }; } diff --git a/Definitions/ObjectModels/Objects/TrackStation/TrackStationObject.cs b/Definitions/ObjectModels/Objects/TrackStation/TrackStationObject.cs index 2b232334..42b5a57f 100644 --- a/Definitions/ObjectModels/Objects/TrackStation/TrackStationObject.cs +++ b/Definitions/ObjectModels/Objects/TrackStation/TrackStationObject.cs @@ -1,10 +1,11 @@ using Definitions.ObjectModels.Objects.Shared; using Definitions.ObjectModels.Objects.Track; using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.TrackStation; -public class TrackStationObject : ILocoStruct, IImageTableNameProvider +public class TrackStationObject : ILocoStruct { public uint8_t PaintStyle { get; set; } public uint8_t Height { get; set; } @@ -24,66 +25,31 @@ public class TrackStationObject : ILocoStruct, IImageTableNameProvider public uint8_t[][] var_6E { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (CostIndex >= 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 31 inclusive.", [nameof(CostIndex)]); } if (-SellCostFactor > BuildCostFactor) { - return false; + yield return new ValidationResult($"The negative of {nameof(SellCostFactor)} must be less than or equal to {nameof(BuildCostFactor)}.", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } if (BuildCostFactor <= 0) { - return false; + yield return new ValidationResult($"{nameof(BuildCostFactor)} must be positive.", [nameof(BuildCostFactor)]); } if (PaintStyle >= 1) { - return false; + yield return new ValidationResult($"{nameof(PaintStyle)} must be 0.", [nameof(PaintStyle)]); } - return true; // CompatibleTrackObjects.Count <= TrackStationObjectLoader.Constants.MaxNumCompatible; + if (CompatibleTrackObjects.Count > 7 /*TrackStationObjectLoader.Constants.MaxNumCompatible*/) + { + yield return new ValidationResult($"{nameof(CompatibleTrackObjects)} must have at most 7 entries.", [nameof(CompatibleTrackObjects)]); + } } - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static readonly Dictionary ImageIdNameMap = new() - { - { 0, "preview_image" }, - { 1, "preview_image_glass_overlay" }, - { 2, "totalPreviewImages" }, - }; - - // These are relative to ImageOffsets - // ImageOffsets is the imageIds per sequenceIndex (for start/middle/end of the platform) - //namespace Style0 - //{ - // constexpr uint32_t straightBackNE = 0; - // constexpr uint32_t straightFrontNE = 1; - // constexpr uint32_t straightCanopyNE = 2; - // constexpr uint32_t straightCanopyTranslucentNE = 3; - // constexpr uint32_t straightBackSE = 4; - // constexpr uint32_t straightFrontSE = 5; - // constexpr uint32_t straightCanopySE = 6; - // constexpr uint32_t straightCanopyTranslucentSE = 7; - // constexpr uint32_t diagonalNE0 = 8; - // constexpr uint32_t diagonalNE3 = 9; - // constexpr uint32_t diagonalNE1 = 10; - // constexpr uint32_t diagonalCanopyNE1 = 11; - // constexpr uint32_t diagonalCanopyTranslucentNE1 = 12; - // constexpr uint32_t diagonalSE1 = 13; - // constexpr uint32_t diagonalSE2 = 14; - // constexpr uint32_t diagonalSE3 = 15; - // constexpr uint32_t diagonalCanopyTranslucentSE3 = 16; - // constexpr uint32_t totalNumImages = 17; - //} - //namespace Style1 - //{ - // constexpr uint32_t totalNumImages = 8; - //} } diff --git a/Definitions/ObjectModels/Objects/Tree/TreeObject.cs b/Definitions/ObjectModels/Objects/Tree/TreeObject.cs index 87c44232..568e04d5 100644 --- a/Definitions/ObjectModels/Objects/Tree/TreeObject.cs +++ b/Definitions/ObjectModels/Objects/Tree/TreeObject.cs @@ -1,3 +1,5 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.Tree; public class TreeObject : ILocoStruct @@ -20,39 +22,37 @@ public class TreeObject : ILocoStruct public int16_t DemolishRatingReduction { get; set; } public TreeFlagsUnk var_3C { get; set; } // something with images - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32 inclusive.", [nameof(CostIndex)]); } // 230/256 = ~90% if (-ClearCostFactor > BuildCostFactor * 230 / 256) { - return false; + yield return new ValidationResult($"The negative of {nameof(ClearCostFactor)} must be less than or equal to ~90% of {nameof(BuildCostFactor)}.", [nameof(ClearCostFactor), nameof(BuildCostFactor)]); } - switch (NumRotations) + if (NumRotations is not 1 or 2 or 4) { - default: - return false; - case 1: - case 2: - case 4: - break; + yield return new ValidationResult($"{nameof(NumRotations)} must be either 1, 2, or 4.", [nameof(NumRotations)]); } if (NumGrowthStages is < 1 or > 8) { - return false; + yield return new ValidationResult($"{nameof(NumGrowthStages)} must be between 1 and 8 inclusive.", [nameof(NumGrowthStages)]); } if (Height < Clearance) { - return false; + yield return new ValidationResult($"{nameof(Height)} must be greater than or equal to {nameof(Clearance)}.", [nameof(Height), nameof(Clearance)]); } - return var_05 >= var_04; + if (var_05 < var_04) + { + yield return new ValidationResult($"{nameof(var_05)} must be greater than or equal to {nameof(var_04)}.", [nameof(var_05), nameof(var_04)]); + } } } diff --git a/Definitions/ObjectModels/Objects/Tunnel/TunnelObject.cs b/Definitions/ObjectModels/Objects/Tunnel/TunnelObject.cs index 9de4de22..49ed9eed 100644 --- a/Definitions/ObjectModels/Objects/Tunnel/TunnelObject.cs +++ b/Definitions/ObjectModels/Objects/Tunnel/TunnelObject.cs @@ -1,17 +1,10 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + namespace Definitions.ObjectModels.Objects.Tunnel; -public class TunnelObject : ILocoStruct, IImageTableNameProvider +public class TunnelObject : ILocoStruct { - public bool Validate() => true; - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static readonly Dictionary ImageIdNameMap = new() - { - { 0, "south west back" }, - { 1, "south west front" }, - { 2, "south east back" }, - { 3, "south east front" }, - }; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Vehicle/BodySprite.cs b/Definitions/ObjectModels/Objects/Vehicle/BodySprite.cs index 5c46842c..5782ccae 100644 --- a/Definitions/ObjectModels/Objects/Vehicle/BodySprite.cs +++ b/Definitions/ObjectModels/Objects/Vehicle/BodySprite.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Vehicle; @@ -27,5 +28,34 @@ public class BodySprite : ILocoStruct //public Dictionary> ImageIds { get; set; } = []; //public int NumImages { get; set; } - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + { + if (Flags.HasFlag(BodySpriteFlags.HasSprites)) + { + if (NumFlatRotationFrames is not 8 or 16 or 32 or 64 or 128) + { + yield return new ValidationResult($"{nameof(NumFlatRotationFrames)} must be one of the following values: 8, 16, 32, 64, 128.", [nameof(NumFlatRotationFrames)]); + } + + if (NumSlopedRotationFrames is not 4 or 8 or 16 or 32) + { + yield return new ValidationResult($"{nameof(NumSlopedRotationFrames)} must be one of the following values: 8, 16, 32, 64, 128.", [nameof(NumSlopedRotationFrames)]); + } + + if (NumAnimationFrames is not 1 or 2 or 4) + { + yield return new ValidationResult($"{nameof(NumAnimationFrames)} must be one of the following values: 1, 2, 4.", [nameof(NumAnimationFrames)]); + } + + if (NumCargoLoadFrames is < 1 or > 5) + { + yield return new ValidationResult($"{nameof(NumCargoLoadFrames)} must be between 1 and 5 inclusive.", [nameof(NumCargoLoadFrames)]); + } + + if (NumRollFrames is not 1 or 3) + { + yield return new ValidationResult($"{nameof(NumRollFrames)} must be one of the following values: 1, 3.", [nameof(NumRollFrames)]); + } + } + } } diff --git a/Definitions/ObjectModels/Objects/Vehicle/BogieSprite.cs b/Definitions/ObjectModels/Objects/Vehicle/BogieSprite.cs index dc9992df..daf35c07 100644 --- a/Definitions/ObjectModels/Objects/Vehicle/BogieSprite.cs +++ b/Definitions/ObjectModels/Objects/Vehicle/BogieSprite.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Vehicle; @@ -19,5 +20,14 @@ public class BogieSprite : ILocoStruct public Dictionary> ImageIds { get; set; } = new(); public int NumImages { get; set; } - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + { + if (Flags.HasFlag(BogieSpriteFlags.HasSprites)) + { + if (RollStates is not 1 or 2 or 4) + { + yield return new ValidationResult($"{nameof(RollStates)} must be either 1, 2, or 4 when {nameof(Flags)} includes {nameof(BogieSpriteFlags.HasSprites)}.", [nameof(RollStates), nameof(Flags)]); + } + } + } } diff --git a/Definitions/ObjectModels/Objects/Vehicle/SimpleAnimation.cs b/Definitions/ObjectModels/Objects/Vehicle/SimpleAnimation.cs index d440283d..e6cd670f 100644 --- a/Definitions/ObjectModels/Objects/Vehicle/SimpleAnimation.cs +++ b/Definitions/ObjectModels/Objects/Vehicle/SimpleAnimation.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Vehicle; @@ -9,5 +10,6 @@ public class SimpleAnimation : ILocoStruct public uint8_t Height { get; set; } public SimpleAnimationType Type { get; set; } - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Vehicle/VehicleObject.cs b/Definitions/ObjectModels/Objects/Vehicle/VehicleObject.cs index c1a39565..41a632bd 100644 --- a/Definitions/ObjectModels/Objects/Vehicle/VehicleObject.cs +++ b/Definitions/ObjectModels/Objects/Vehicle/VehicleObject.cs @@ -1,5 +1,6 @@ using Definitions.ObjectModels.Objects.Cargo; using Definitions.ObjectModels.Types; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Vehicle; @@ -48,134 +49,75 @@ public class VehicleObject : ILocoStruct public uint8_t[] var_135 { get; set; } = []; - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32 inclusive.", [nameof(CostIndex)]); } if (RunCostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(RunCostIndex)} must be between 0 and 32 inclusive.", [nameof(RunCostIndex)]); } if (CostFactor <= 0) { - return false; + yield return new ValidationResult($"{nameof(CostFactor)} must be positive.", [nameof(CostFactor)]); } if (RunCostFactor < 0) { - return false; + yield return new ValidationResult($"{nameof(RunCostFactor)} must be non-negative.", [nameof(RunCostFactor)]); } if (Flags.HasFlag(VehicleObjectFlags.AnyRoadType)) { if (RequiredTrackExtras.Length != 0) { - return false; + yield return new ValidationResult($"{nameof(RequiredTrackExtras)} must be empty if {nameof(VehicleObjectFlags.AnyRoadType)} is set.", [nameof(RequiredTrackExtras), nameof(Flags)]); } if (Flags.HasFlag(VehicleObjectFlags.RackRail)) { - return false; + yield return new ValidationResult($"{nameof(Flags)} cannot have both {nameof(VehicleObjectFlags.AnyRoadType)} and {nameof(VehicleObjectFlags.RackRail)} set.", [nameof(Flags)]); } } if (RequiredTrackExtras.Length > 4) { - return false; + yield return new ValidationResult($"{nameof(RequiredTrackExtras)} must have at most 4 entries.", [nameof(RequiredTrackExtras)]); } if (NumSimultaneousCargoTypes > 2) { - return false; + yield return new ValidationResult($"{nameof(NumSimultaneousCargoTypes)} must be between 0 and 2 inclusive.", [nameof(NumSimultaneousCargoTypes)]); } if (CompatibleVehicles.Length > 8) { - return false; + yield return new ValidationResult($"{nameof(CompatibleVehicles)} must have at most 8 entries.", [nameof(CompatibleVehicles)]); } if (RackSpeed > Speed) { - return false; + yield return new ValidationResult($"{nameof(RackSpeed)} must be less than or equal to {nameof(Speed)}.", [nameof(RackSpeed), nameof(Speed)]); } foreach (var bodySprite in BodySprites) { - if (!bodySprite.Flags.HasFlag(BodySpriteFlags.HasSprites)) + foreach (var result in bodySprite.Validate(validationContext)) { - continue; - } - - switch (bodySprite.NumFlatRotationFrames) - { - case 8: - case 16: - case 32: - case 64: - case 128: - break; - default: - return false; - } - - switch (bodySprite.NumSlopedRotationFrames) - { - case 4: - case 8: - case 16: - case 32: - break; - default: - return false; - } - - switch (bodySprite.NumAnimationFrames) - { - case 1: - case 2: - case 4: - break; - default: - return false; - } - - if (bodySprite.NumCargoLoadFrames is < 1 or > 5) - { - return false; - } - - switch (bodySprite.NumRollFrames) - { - case 1: - case 3: - break; - default: - return false; + yield return result; } } foreach (var bogieSprite in BogieSprites) { - if (!bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasSprites)) + foreach (var result in bogieSprite.Validate(validationContext)) { - continue; - } - - switch (bogieSprite.RollStates) - { - case 1: - case 2: - case 4: - break; - default: - return false; + yield return result; } } - - return true; } } diff --git a/Definitions/ObjectModels/Objects/Vehicle/VehicleObjectCar.cs b/Definitions/ObjectModels/Objects/Vehicle/VehicleObjectCar.cs index 13807ffd..9c0dba5f 100644 --- a/Definitions/ObjectModels/Objects/Vehicle/VehicleObjectCar.cs +++ b/Definitions/ObjectModels/Objects/Vehicle/VehicleObjectCar.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Vehicle; @@ -12,5 +13,6 @@ public class VehicleObjectCar : ILocoStruct public uint8_t BodySpriteIndex { get; set; } public uint8_t var_05 { get; set; } - public bool Validate() => true; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Wall/WallObject.cs b/Definitions/ObjectModels/Objects/Wall/WallObject.cs index 35b1b889..95968ac0 100644 --- a/Definitions/ObjectModels/Objects/Wall/WallObject.cs +++ b/Definitions/ObjectModels/Objects/Wall/WallObject.cs @@ -1,48 +1,14 @@ +using System.ComponentModel.DataAnnotations; + namespace Definitions.ObjectModels.Objects.Wall; -public class WallObject : ILocoStruct, IImageTableNameProvider +public class WallObject : ILocoStruct { public uint8_t Height { get; set; } public uint8_t ToolId { get; set; } // unused in loco??? public WallObjectFlags1 Flags1 { get; set; } = WallObjectFlags1.None; public WallObjectFlags2 Flags2 { get; set; } = WallObjectFlags2.None; // unused in loco??? - public bool Validate() => true; - - public bool TryGetImageName(int id, out string? value) - { - var result = ImageIdNameMap.TryGetValue(id, out value); - - if (id is >= 0 and <= 5 && Flags1.HasFlag(WallObjectFlags1.DoubleSided)) - { - value = $"{value} back"; - } - - if (id is >= 6 and <= 11 && Flags1.HasFlag(WallObjectFlags1.HasGlass)) - { - value = $"{value} glass overlay"; - } - else - { - value = $"{value} front"; - } - - return result; - } - - public static Dictionary ImageIdNameMap = new() - { - { 0, "Flat SE" }, - { 1, "Flat NE" }, - { 2, "Sloped SE" }, - { 3, "Sloped NE" }, - { 4, "Sloped NW" }, - { 5, "Sloped SW" }, - { 6, "Flat SE" }, - { 7, "Flat NE" }, - { 8, "Sloped SE" }, - { 9, "Sloped NE" }, - { 10, "Sloped NW" }, - { 11, "Sloped SW" }, - }; + public IEnumerable Validate(ValidationContext validationContext) + => []; } diff --git a/Definitions/ObjectModels/Objects/Water/WaterObject.cs b/Definitions/ObjectModels/Objects/Water/WaterObject.cs index a5bca8ff..4fa59a96 100644 --- a/Definitions/ObjectModels/Objects/Water/WaterObject.cs +++ b/Definitions/ObjectModels/Objects/Water/WaterObject.cs @@ -1,109 +1,23 @@ -using System.Text.Json.Serialization; +using System.ComponentModel.DataAnnotations; namespace Definitions.ObjectModels.Objects.Water; -public class WaterObject : ILocoStruct, IImageTableNameProvider +public class WaterObject : ILocoStruct { - [JsonInclude] public uint8_t CostIndex { get; set; } public uint8_t var_03 { get; set; } public int16_t CostFactor { get; set; } - public bool Validate() + public IEnumerable Validate(ValidationContext validationContext) { if (CostIndex > 32) { - return false; + yield return new ValidationResult($"{nameof(CostIndex)} must be between 0 and 32.", [nameof(CostIndex)]); } if (CostFactor <= 0) { - return false; + yield return new ValidationResult($"{nameof(CostFactor)} must be positive.", [nameof(CostFactor)]); } - - return true; } - - public bool TryGetImageName(int id, out string? value) - => ImageIdNameMap.TryGetValue(id, out value); - - public static Dictionary ImageIdNameMap = new() - { - { 0, "zoom1 wave overlay full" }, - { 1, "zoom1 wave overlay west" }, - { 2, "zoom1 wave overlay east" }, - { 3, "zoom1 wave overlay north" }, - { 4, "zoom1 wave overlay south" }, - { 5, "zoom1 wave overlay full" }, - { 6, "zoom1 wave half-tile west" }, - { 7, "zoom1 wave half-tile east" }, - { 8, "zoom1 wave half-tile north" }, - { 9, "zoom1 wave half-tile south" }, - { 10, "zoom2 wave overlay full" }, - { 11, "zoom2 wave overlay west" }, - { 12, "zoom2 wave overlay east" }, - { 13, "zoom2 wave overlay north" }, - { 14, "zoom2 wave overlay south" }, - { 15, "zoom2 wave overlay full" }, - { 16, "zoom2 wave half-tile west" }, - { 17, "zoom2 wave half-tile east" }, - { 18, "zoom2 wave half-tile north" }, - { 19, "zoom2 wave half-tile south" }, - { 20, "zoom3 wave overlay full" }, - { 21, "zoom3 wave overlay west" }, - { 22, "zoom3 wave overlay east" }, - { 23, "zoom3 wave overlay north" }, - { 24, "zoom3 wave overlay south" }, - { 25, "zoom3 wave overlay full" }, - { 26, "zoom3 wave half-tile west" }, - { 27, "zoom3 wave half-tile east" }, - { 28, "zoom3 wave half-tile north" }, - { 29, "zoom3 wave half-tile south" }, - { 30, "zoom4 wave overlay full" }, - { 31, "zoom4 wave overlay west" }, - { 32, "zoom4 wave overlay east" }, - { 33, "zoom4 wave overlay north" }, - { 34, "zoom4 wave overlay south" }, - { 35, "zoom4 wave overlay full" }, - { 36, "zoom4 wave half-tile west" }, - { 37, "zoom4 wave half-tile east" }, - { 38, "zoom4 wave half-tile north" }, - { 39, "zoom4 wave half-tile south" }, - { 40, "minimap palette" }, - { 41, "water colour palette" }, - { 42, "water icon animation 0" }, - { 43, "water icon animation 1" }, - { 44, "water icon animation 2" }, - { 45, "water icon animation 3" }, - { 46, "water icon animation 4" }, - { 47, "water icon animation 5" }, - { 48, "water icon animation 6" }, - { 49, "water icon animation 7" }, - { 50, "water icon animation 8" }, - { 51, "water icon animation 9" }, - { 52, "water icon animation 10" }, - { 54, "water icon animation 11" }, - { 55, "water icon animation 12" }, - { 56, "water icon animation 13" }, - { 57, "water icon animation 14" }, - { 53, "water icon animation 15" }, - { 58, "pick up water vehicle" }, - { 59, "place down water vehicle" }, - { 60, "water animation frame 0" }, - { 61, "water animation frame 1" }, - { 62, "water animation frame 2" }, - { 63, "water animation frame 3" }, - { 64, "water animation frame 4" }, - { 65, "water animation frame 5" }, - { 66, "water animation frame 6" }, - { 67, "water animation frame 7" }, - { 68, "water animation frame 8" }, - { 69, "water animation frame 9" }, - { 70, "water animation frame 10" }, - { 71, "water animation frame 11" }, - { 72, "water animation frame 12" }, - { 73, "water animation frame 13" }, - { 74, "water animation frame 14" }, - { 75, "water animation frame 15" }, - }; } diff --git a/Definitions/ObjectModels/PaletteMap.cs b/Definitions/ObjectModels/PaletteMap.cs index 96a0628e..4650bc00 100644 --- a/Definitions/ObjectModels/PaletteMap.cs +++ b/Definitions/ObjectModels/PaletteMap.cs @@ -125,7 +125,7 @@ public static (Color Color, byte Index) Transparent public (Color Color, byte Index)[] Brown => Palette[214..226]; public (Color Color, byte Index)[] Amber - => [.. Palette[230..240], .. Palette[244..246]]; + => [.. Palette[230..240], .. Palette[243..245]]; #endregion diff --git a/Definitions/ObjectModels/Types/GraphicsElement.cs b/Definitions/ObjectModels/Types/GraphicsElement.cs index 748183b3..44c7633d 100644 --- a/Definitions/ObjectModels/Types/GraphicsElement.cs +++ b/Definitions/ObjectModels/Types/GraphicsElement.cs @@ -1,3 +1,7 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System.Text.Json.Serialization; + namespace Definitions.ObjectModels.Types; [Flags] @@ -22,5 +26,14 @@ public class GraphicsElement // follows G1Element32, except XOffset and YOffset public GraphicsElementFlags Flags { get; set; } public short ZoomOffset { get; set; } public byte[] ImageData { get; set; } = []; - // string Name - taken from IImageNameProvider + + [JsonIgnore] + public Image? Image { get; set; } + + // todo: use indexed image in SixLabors + //[JsonIgnore] + //public IndexedImageFrame? Image { get; set; } + + [JsonIgnore] // this is calculated based on object type, its not really a part of the object definition itself + public string Name { get; set; } // taken from IImageNameProvider } diff --git a/Gui/App.axaml b/Gui/App.axaml index cf6723a7..f20cf2a6 100644 --- a/Gui/App.axaml +++ b/Gui/App.axaml @@ -1,14 +1,16 @@ + @@ -39,10 +41,10 @@ - + - + @@ -54,6 +56,9 @@ + + + diff --git a/Gui/Converters/ColourArrayToGradientStopsConverter.cs b/Gui/Converters/ColourArrayToGradientStopsConverter.cs new file mode 100644 index 00000000..41dc1b23 --- /dev/null +++ b/Gui/Converters/ColourArrayToGradientStopsConverter.cs @@ -0,0 +1,37 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; +using System; +using System.Globalization; +using AvaColour = Avalonia.Media.Color; + +namespace Gui.Converters; + +public class ColourArrayToGradientStopsConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is AvaColour[] colours) + { + var stops = new GradientStops(); + var segmentWidth = 1.0 / colours.Length; + + for (var i = 0; i < colours.Length; i++) + { + // Add a stop at the beginning of the segment + stops.Add(new GradientStop(colours[i], i * segmentWidth)); + + // Add a second stop at the end of the segment to create a sharp transition + stops.Add(new GradientStop(colours[i], (i + 1) * segmentWidth)); + } + + return stops; + } + + return new GradientStops(); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/Gui/Models/Converters/EnumDescriptionConverter.cs b/Gui/Converters/EnumDescriptionConverter.cs similarity index 95% rename from Gui/Models/Converters/EnumDescriptionConverter.cs rename to Gui/Converters/EnumDescriptionConverter.cs index 56b80966..6d17f287 100644 --- a/Gui/Models/Converters/EnumDescriptionConverter.cs +++ b/Gui/Converters/EnumDescriptionConverter.cs @@ -3,7 +3,7 @@ using System; using System.Globalization; -namespace Gui.Models.Converters; +namespace Gui.Converters; public class EnumDescriptionConverter : MarkupExtension, IValueConverter { diff --git a/Gui/Models/Converters/EnumToBooleanConverter.cs b/Gui/Converters/EnumToBooleanConverter.cs similarity index 94% rename from Gui/Models/Converters/EnumToBooleanConverter.cs rename to Gui/Converters/EnumToBooleanConverter.cs index 55b2972d..62178362 100644 --- a/Gui/Models/Converters/EnumToBooleanConverter.cs +++ b/Gui/Converters/EnumToBooleanConverter.cs @@ -2,7 +2,7 @@ using System; using System.Globalization; -namespace Gui.Models.Converters; +namespace Gui.Converters; public class EnumToBooleanConverter : IValueConverter { diff --git a/Gui/Models/Converters/EnumToMaterialIconConverter.cs b/Gui/Converters/EnumToMaterialIconConverter.cs similarity index 96% rename from Gui/Models/Converters/EnumToMaterialIconConverter.cs rename to Gui/Converters/EnumToMaterialIconConverter.cs index 7498de94..c4263916 100644 --- a/Gui/Models/Converters/EnumToMaterialIconConverter.cs +++ b/Gui/Converters/EnumToMaterialIconConverter.cs @@ -1,11 +1,12 @@ using Avalonia.Data.Converters; using Definitions.ObjectModels.Objects.Vehicle; using Definitions.ObjectModels.Types; +using Gui.Models; using System; using System.Collections.Generic; using System.Globalization; -namespace Gui.Models.Converters; +namespace Gui.Converters; public class EnumToMaterialIconConverter : IValueConverter { @@ -23,7 +24,7 @@ public class EnumToMaterialIconConverter : IValueConverter { return icon; } - else if ((value is T source2) && mapping.TryGetValue(source2, out var icon2)) + else if (value is T source2 && mapping.TryGetValue(source2, out var icon2)) { return icon2; } diff --git a/Gui/Models/Converters/NegativeValueConverter.cs b/Gui/Converters/NegativeValueConverter.cs similarity index 96% rename from Gui/Models/Converters/NegativeValueConverter.cs rename to Gui/Converters/NegativeValueConverter.cs index 86eae332..37548ebf 100644 --- a/Gui/Models/Converters/NegativeValueConverter.cs +++ b/Gui/Converters/NegativeValueConverter.cs @@ -1,7 +1,7 @@ using Avalonia.Data.Converters; using System; -namespace Gui.Models.Converters; +namespace Gui.Converters; public class NegativeValueConverter : IValueConverter { diff --git a/Gui/Gui.csproj b/Gui/Gui.csproj index 4c222343..69b63b17 100644 --- a/Gui/Gui.csproj +++ b/Gui/Gui.csproj @@ -43,25 +43,26 @@ - - - + + + - - - + + + - - - + + + - + + diff --git a/Gui/Models/ObjectEditorModel.cs b/Gui/Models/ObjectEditorModel.cs index 7a42a756..460bf781 100644 --- a/Gui/Models/ObjectEditorModel.cs +++ b/Gui/Models/ObjectEditorModel.cs @@ -342,7 +342,6 @@ bool TryLoadLocalFile(FileSystemItem filesystemItem, out UiDatLocoFile? locoDatF DatFileInfo? fileInfo = null; LocoObject? locoObject = null; MetadataModel? metadata = null; - //List> images = []; var filename = File.Exists(filesystemItem.FileName) ? filesystemItem.FileName @@ -359,17 +358,6 @@ bool TryLoadLocalFile(FileSystemItem filesystemItem, out UiDatLocoFile? locoDatF //DatObjects = [new(0)], }; // todo: look up the rest of the data from internet - //if (locoObject != null) - //{ - // foreach (var i in locoObject.GraphicsElements) - // { - // if (PaletteMap.TryConvertG1ToRgba32Bitmap(i, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var image)) - // { - // images.Add(image!); - // } - // } - //} - locoDatFile = new UiDatLocoFile() { DatFileInfo = fileInfo, LocoObject = locoObject, Metadata = metadata }; return true; } diff --git a/Gui/Models/UiG1.cs b/Gui/Models/UiG1.cs index 041ec7ac..1c0dc5c0 100644 --- a/Gui/Models/UiG1.cs +++ b/Gui/Models/UiG1.cs @@ -1,6 +1,4 @@ using Dat.Types; -using Definitions.ObjectModels.Types; -using System.Collections.Generic; using System.ComponentModel; namespace Gui.Models; @@ -9,14 +7,4 @@ namespace Gui.Models; public class UiG1 : IUiObject { public required G1Dat G1 { get; set; } - - public List G1Elements - { - get => G1.GraphicsElements; - set - { - G1.GraphicsElements.Clear(); - G1.GraphicsElements.AddRange(value); - } - } } diff --git a/Gui/ViewModels/SubObjectTypes/AudioViewModel.cs b/Gui/ViewModels/AudioViewModel.cs similarity index 100% rename from Gui/ViewModels/SubObjectTypes/AudioViewModel.cs rename to Gui/ViewModels/AudioViewModel.cs diff --git a/Gui/ViewModels/DatTypes/HexAnnotationLine.cs b/Gui/ViewModels/DatTypes/HexAnnotationLine.cs deleted file mode 100644 index ecb831d1..00000000 --- a/Gui/ViewModels/DatTypes/HexAnnotationLine.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Gui.ViewModels; - -public record HexAnnotationLine(string Address, string Data, int? SelectionStart, int? SelectionEnd); diff --git a/Gui/ViewModels/SubObjectTypes/GraphicsElementJson.cs b/Gui/ViewModels/Graphics/GraphicsElementJson.cs similarity index 66% rename from Gui/ViewModels/SubObjectTypes/GraphicsElementJson.cs rename to Gui/ViewModels/Graphics/GraphicsElementJson.cs index 85acfa4d..a832af84 100644 --- a/Gui/ViewModels/SubObjectTypes/GraphicsElementJson.cs +++ b/Gui/ViewModels/Graphics/GraphicsElementJson.cs @@ -1,28 +1,30 @@ using Definitions.ObjectModels.Types; using System.Text.Json.Serialization; -namespace Gui.ViewModels; +namespace Gui.ViewModels.Graphics; public record GraphicsElementJson( [property: JsonPropertyName("path")] string Path, [property: JsonPropertyName("x")] int16_t XOffset, [property: JsonPropertyName("y")] int16_t YOffset, [property: JsonPropertyName("zoomOffset")] int16_t? ZoomOffset, - [property: JsonPropertyName("flags")] GraphicsElementFlags? Flags + [property: JsonPropertyName("flags")] GraphicsElementFlags? Flags, + [property: JsonPropertyName("name")] string? Name + // todo: add name ) { public GraphicsElementJson() - : this("", 0, 0, null, null) + : this("", 0, 0, null, null, null) { } - public GraphicsElementJson(string path, int16_t xOffset, int16_t yOffset) - : this(path, xOffset, yOffset, null, null) + public GraphicsElementJson(string path, int16_t xOffset, int16_t yOffset, string name) + : this(path, xOffset, yOffset, null, null, name) { } public GraphicsElementJson(string path, GraphicsElement g1Element) - : this(path, g1Element.XOffset, g1Element.YOffset, g1Element.ZoomOffset, g1Element.Flags) + : this(path, g1Element.XOffset, g1Element.YOffset, g1Element.ZoomOffset, g1Element.Flags, g1Element.Name) { } public static GraphicsElementJson Zero - => new(string.Empty, 0, 0, 0, GraphicsElementFlags.None); + => new(string.Empty, 0, 0, 0, GraphicsElementFlags.None, string.Empty); } diff --git a/Gui/ViewModels/Graphics/GroupedImageViewModel.cs b/Gui/ViewModels/Graphics/GroupedImageViewModel.cs new file mode 100644 index 00000000..a8ccc73c --- /dev/null +++ b/Gui/ViewModels/Graphics/GroupedImageViewModel.cs @@ -0,0 +1,27 @@ +using Avalonia.Controls.Selection; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Gui.ViewModels.Graphics; + +public class GroupedImageViewModel : ReactiveObject +{ + public string GroupName { get; set; } + public ObservableCollection Images { get; set; } + + [Reactive] + public SelectionModel SelectionModel { get; set; } + + public GroupedImageViewModel(string groupName, IEnumerable images) + { + GroupName = groupName; + Images = new ObservableCollection(images); + + SelectionModel = new SelectionModel + { + SingleSelect = false + }; + } +} diff --git a/Gui/ViewModels/SubObjectTypes/ImageTableViewModel.cs b/Gui/ViewModels/Graphics/ImageTableViewModel.cs similarity index 62% rename from Gui/ViewModels/SubObjectTypes/ImageTableViewModel.cs rename to Gui/ViewModels/Graphics/ImageTableViewModel.cs index 5bad548b..014782fe 100644 --- a/Gui/ViewModels/SubObjectTypes/ImageTableViewModel.cs +++ b/Gui/ViewModels/Graphics/ImageTableViewModel.cs @@ -4,6 +4,7 @@ using Common.Json; using Common.Logging; using Definitions.ObjectModels; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Types; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -17,8 +18,31 @@ using System.Reactive.Linq; using System.Threading.Tasks; using System.Windows.Input; +using SLColour = SixLabors.ImageSharp.Color; +using AvaColour = Avalonia.Media.Color; +using Gui.ViewModels.LocoTypes.Objects.Building; -namespace Gui.ViewModels; +namespace Gui.ViewModels.Graphics; + +public static class SixLaboursAvaloniaColor +{ + public static AvaColour ToAvaloniaColor(this SLColour color) + { + var pixel = color.ToPixel(); + return AvaColour.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B); + } + + public static SLColour ToSixLaborsColor(this AvaColour color) + => SLColour.FromRgba(color.R, color.G, color.B, color.A); +} + +public class ColourRemapSwatchViewModel +{ + [Reactive] public ColourRemapSwatch Swatch { get; init; } + [Reactive] public AvaColour Colour { get; init; } + + [Reactive] public AvaColour[] GradientColours { get; init; } +} public class ImageTableViewModel : ReactiveObject, IExtraContentViewModel { @@ -28,10 +52,13 @@ public class ImageTableViewModel : ReactiveObject, IExtraContentViewModel public static ColourRemapSwatch[] ColourSwatchesArr { get; } = Enum.GetValues(); [Reactive] - public ColourRemapSwatch SelectedPrimarySwatch { get; set; } = ColourRemapSwatch.PrimaryRemap; + public List ColourSwatches { get; init; } [Reactive] - public ColourRemapSwatch SelectedSecondarySwatch { get; set; } = ColourRemapSwatch.SecondaryRemap; + public ColourRemapSwatchViewModel SelectedPrimarySwatch { get; set; } + + [Reactive] + public ColourRemapSwatchViewModel SelectedSecondarySwatch { get; set; } [Reactive] public ICommand ImportImagesCommand { get; set; } @@ -53,13 +80,6 @@ public class ImageTableViewModel : ReactiveObject, IExtraContentViewModel [Reactive] public ICommand CenterOffsetAllImagesCommand { get; set; } - // what is displaying on the ui - [Reactive] - public ObservableCollection ImageViewModels { get; set; } = []; - - [Reactive] - public int SelectedImageIndex { get; set; } = -1; - [Reactive] public SelectionModel SelectionModel { get; set; } @@ -71,45 +91,53 @@ public class ImageTableViewModel : ReactiveObject, IExtraContentViewModel readonly DispatcherTimer animationTimer; int currentFrameIndex; - public readonly IImageTableNameProvider NameProvider; // Can remove this when we put name inside of GraphicsElement public PaletteMap PaletteMap { get; init; } public readonly ILogger Logger; - public ImageTableViewModel(IList graphicsElements, IImageTableNameProvider imageNameProvider, PaletteMap paletteMap, ILogger logger) + [Reactive] + public ObservableCollection GroupedImageViewModels { get; set; } = []; + + [Reactive] + public ObservableCollection LayeredImages { get; set; } = []; + + public BuildingComponentsViewModel? BuildingComponents { get; set; } + + public ImageTableViewModel(ImageTable imageTable, ILogger logger, BuildingComponentsModel? buildingComponents = null) { - ArgumentNullException.ThrowIfNull(paletteMap); + ArgumentNullException.ThrowIfNull(imageTable); + PaletteMap = imageTable.PaletteMap; + Logger = logger; - var index = 0; - foreach (var ge in graphicsElements) + // swatches/palettes + ColourSwatches = [.. ColourSwatchesArr.Select(x => new ColourRemapSwatchViewModel() { - var success = imageNameProvider.TryGetImageName(index, out var imageName); - ImageViewModels.Add(new ImageViewModel(index, success ? imageName! : $"{index}-unnamed", ge, paletteMap)); - index++; - } + Swatch = x, + Colour = PaletteMap.GetRemapSwatchFromName(x)[0].Color.ToAvaloniaColor(), + GradientColours = [.. PaletteMap.GetRemapSwatchFromName(x).Select(x => x.Color.ToAvaloniaColor())], + })]; - NameProvider = imageNameProvider; - PaletteMap = paletteMap; - Logger = logger; + SelectedPrimarySwatch = ColourSwatches.Single(x => x.Swatch == ColourRemapSwatch.PrimaryRemap); + SelectedSecondarySwatch = ColourSwatches.Single(x => x.Swatch == ColourRemapSwatch.SecondaryRemap); - SelectionModel = new SelectionModel + // image tables + foreach (var group in imageTable.Groups) { - SingleSelect = false - }; - SelectionModel.SelectionChanged += SelectionChanged; + var imageViewModels = group.GraphicsElements.Select(ge => new ImageViewModel(ge, PaletteMap)); + var givm = new GroupedImageViewModel(group.Name, imageViewModels); + givm.SelectionModel.SelectionChanged += SelectionChanged; + GroupedImageViewModels.Add(givm); + } + + // building components + if (buildingComponents != null) + { + BuildingComponents = new(buildingComponents, imageTable); + } _ = this.WhenAnyValue(o => o.SelectedPrimarySwatch).Skip(1) - .Subscribe(_ => RecolourImages(SelectedPrimarySwatch, SelectedSecondarySwatch)); + .Subscribe(_ => RecolourImages(SelectedPrimarySwatch.Swatch, SelectedSecondarySwatch.Swatch)); _ = this.WhenAnyValue(o => o.SelectedSecondarySwatch).Skip(1) - .Subscribe(_ => RecolourImages(SelectedPrimarySwatch, SelectedSecondarySwatch)); - - _ = this.WhenAnyValue(o => o.SelectedImageIndex) - .Subscribe(index => - { - SelectedImage = SelectedImageIndexIsValid() - ? ImageViewModels[SelectedImageIndex] - : null; - }); - + .Subscribe(_ => RecolourImages(SelectedPrimarySwatch.Swatch, SelectedSecondarySwatch.Swatch)); _ = this.WhenAnyValue(o => o.AnimationSpeed) .Where(_ => animationTimer != null) .Subscribe(_ => animationTimer!.Interval = TimeSpan.FromMilliseconds(1000 / AnimationSpeed)); @@ -122,7 +150,7 @@ public ImageTableViewModel(IList graphicsElements, IImageTableN ZeroOffsetAllImagesCommand = ReactiveCommand.Create(() => { - foreach (var ivm in ImageViewModels) + foreach (var ivm in GroupedImageViewModels.SelectMany(x => x.Images)) { ivm.XOffset = 0; ivm.YOffset = 0; @@ -131,13 +159,19 @@ public ImageTableViewModel(IList graphicsElements, IImageTableN CenterOffsetAllImagesCommand = ReactiveCommand.Create(() => { - foreach (var ivm in ImageViewModels) + foreach (var ivm in GroupedImageViewModels.SelectMany(x => x.Images)) { ivm.XOffset = (short)(-ivm.Width / 2); ivm.YOffset = (short)(-ivm.Height / 2); } }); + SelectionModel = new SelectionModel + { + SingleSelect = false + }; + SelectionModel.SelectionChanged += SelectionChanged; + // Set up the animation timer animationTimer = new DispatcherTimer { @@ -147,47 +181,40 @@ public ImageTableViewModel(IList graphicsElements, IImageTableN animationTimer.Start(); } - bool SelectedImageIndexIsValid() - => SelectedImageIndex >= 0 && SelectedImageIndex < ImageViewModels.Count; - - void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) - { - var sm = (SelectionModel)sender; - - if (sm.SelectedIndexes.Count > 0) - { - SelectedImageIndex = sm.SelectedIndex; - } - - if (sm.SelectedItems.Count == 0) - { - return; - } - } - void AnimationTimer_Tick(object? sender, EventArgs e) { - if (SelectionModel == null || SelectionModel.SelectedIndexes.Count == 0) + if (SelectionModel == null || SelectionModel.SelectedItems.Count == 0) { return; } - if (currentFrameIndex >= SelectionModel.SelectedIndexes.Count) + if (currentFrameIndex >= SelectionModel.SelectedItems.Count) { currentFrameIndex = 0; } // Update the displayed image viewmodel - SelectedImageIndex = SelectionModel.SelectedIndexes[currentFrameIndex]; // disabling this also makes the memory leaks stop + SelectedImage = SelectionModel.SelectedItems[currentFrameIndex]; // disabling this also makes the memory leaks stop // Move to the next frame, looping back to the beginning if necessary - currentFrameIndex = (currentFrameIndex + 1) % SelectionModel.SelectedIndexes.Count; + currentFrameIndex = (currentFrameIndex + 1) % SelectionModel.SelectedItems.Count; + } + + void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) + { + var sm = (SelectionModel)sender; + SelectionModel = sm; + SelectedImage = SelectionModel.SelectedItems.Count > 0 ? sm.SelectedItems[0] : null; } public void ClearSelectionModel() { + foreach (var givm in GroupedImageViewModels) + { + givm.SelectionModel.Clear(); + } + SelectionModel.Clear(); - SelectedImageIndex = -1; } public async Task ImportImages() @@ -224,11 +251,6 @@ public async Task ExportImages() public async Task ReplaceImage() { - if (SelectedImageIndex == -1) - { - return; - } - var openFile = await PlatformSpecific.OpenFilePicker(PlatformSpecific.PngFileTypes); if (openFile == null) { @@ -241,46 +263,29 @@ public async Task ReplaceImage() return; } - ImageViewModels[SelectedImageIndex].UnderlyingImage = Image.Load(filename); + _ = SelectedImage?.UnderlyingImage = Image.Load(filename); } // model stuff public void RecolourImages(ColourRemapSwatch primary, ColourRemapSwatch secondary) { - if (SelectedImageIndexIsValid()) - { - return; - } - - foreach (var ivm in ImageViewModels) + foreach (var ivm in GroupedImageViewModels.SelectMany(x => x.Images)) { ivm.RecolourImage(primary, secondary); } } public void CropImage() - { - if (!SelectedImageIndexIsValid()) - { - return; - } - - ImageViewModels[SelectedImageIndex].CropImage(); - } + => SelectedImage?.CropImage(); public void CropAllImages() { - foreach (var ivm in ImageViewModels) + foreach (var ivm in GroupedImageViewModels.SelectMany(x => x.Images)) { ivm.CropImage(); } } - public string GetImageName(int index) - => NameProvider.TryGetImageName(index, out var value) && !string.IsNullOrEmpty(value) - ? value - : index.ToString(); - public static string TrimZeroes(string str) { var result = str.Trim().TrimStart('0'); @@ -307,7 +312,6 @@ public async Task ImportImages(string directory) try { - Logger.Debug($"{ImageViewModels.Count} images in current object"); ICollection offsets; // check for offsets file @@ -323,8 +327,8 @@ public async Task ImportImages(string directory) var files = Directory.GetFiles(directory, "*.png", SearchOption.AllDirectories); var sanitised = files.Select(TrimZeroes).ToList(); - offsets = [.. ImageViewModels - .Select((x, i) => new GraphicsElementJson(sanitised[i], (short)x.XOffset, (short)x.YOffset)) + offsets = [.. GroupedImageViewModels.SelectMany(x => x.Images) + .Select((x, i) => new GraphicsElementJson(sanitised[i], (short)x.XOffset, (short)x.YOffset, x.Name)) .Fill(files.Length, GraphicsElementJson.Zero)]; Logger.Debug($"Didn't find sprites.json file, using existing GraphicsElement offsets with {offsets.Count} images"); @@ -332,21 +336,29 @@ public async Task ImportImages(string directory) // clear existing images Logger.Debug("Clearing current images"); - ImageViewModels.Clear(); + GroupedImageViewModels.Clear(); // load files + var currentGroup = 0; + var currentGroupIndex = 0; foreach (var (offset, i) in offsets.Select((x, i) => (x, i))) { var is1Pixel = string.IsNullOrEmpty(offset.Path); var img = is1Pixel ? OnePixelTransparent : Image.Load(Path.Combine(directory, offset.Path)); var newOffset = is1Pixel ? offset with { Flags = GraphicsElementFlags.HasTransparency } : offset; var graphicsElement = GraphicsElementFromImage(newOffset, img, PaletteMap); - - _ = NameProvider.TryGetImageName(i, out var imageName); - ImageViewModels.Add(new ImageViewModel(i, imageName ?? "", graphicsElement, PaletteMap)); + graphicsElement.Name = string.IsNullOrEmpty(graphicsElement.Name) + ? DefaultImageTableNameProvider.GetImageName(i) + : graphicsElement.Name; + + GroupedImageViewModels[currentGroup].Images.Add(new ImageViewModel(graphicsElement, PaletteMap)); + currentGroupIndex++; + if (currentGroupIndex >= GroupedImageViewModels[currentGroup].Images.Count) + { + currentGroup++; + currentGroupIndex = 0; + } } - - Logger.Debug($"Imported {ImageViewModels.Count} images successfully"); } catch (Exception ex) { @@ -365,7 +377,8 @@ static GraphicsElement GraphicsElementFromImage(GraphicsElementJson ele, Image(); - foreach (var image in ImageViewModels) + foreach (var group in GroupedImageViewModels) { - var imageName = counter.ToString(); // todo: maybe use image name provider below (but number must still exist) - counter++; - - var fileName = $"{imageName}.png"; - var path = Path.Combine(directory, fileName); - await image.UnderlyingImage.SaveAsPngAsync(path); + foreach (var image in group.Images) + { + var imageName = image.Name.Trim().ToLower().Replace(' ', '-'); + var groupName = group.GroupName.Trim().ToLower().Replace(' ', '-'); + var fileName = $"{groupName}_{imageName}.png"; + var path = Path.Combine(directory, fileName); + await image.UnderlyingImage.SaveAsPngAsync(path); - offsets.Add(new GraphicsElementJson(fileName, image.ToGraphicsElement())); + offsets.Add(new GraphicsElementJson(fileName, image.ToGraphicsElement())); + } } var offsetsFile = Path.Combine(directory, "sprites.json"); diff --git a/Gui/ViewModels/SubObjectTypes/ImageViewModel.cs b/Gui/ViewModels/Graphics/ImageViewModel.cs similarity index 86% rename from Gui/ViewModels/SubObjectTypes/ImageViewModel.cs rename to Gui/ViewModels/Graphics/ImageViewModel.cs index 2dc3682a..b8c90b50 100644 --- a/Gui/ViewModels/SubObjectTypes/ImageViewModel.cs +++ b/Gui/ViewModels/Graphics/ImageViewModel.cs @@ -11,14 +11,14 @@ using System.ComponentModel; using System.Reactive.Linq; -namespace Gui.ViewModels; +namespace Gui.ViewModels.Graphics; public class ImageViewModel : ReactiveObject { - [ReadOnly(true)] public int ImageIndex { get; } - [ReadOnly(true)] public string ImageName { get; } public int Width => UnderlyingImage.Width; public int Height => UnderlyingImage.Height; + public string Name { get; set; } + [Reactive] public int XOffset { get; set; } [Reactive] public int YOffset { get; set; } @@ -43,10 +43,9 @@ public Avalonia.Rect SelectedBitmapPreviewBorder readonly PaletteMap PaletteMap; - public ImageViewModel(int imageIndex, string imageName, GraphicsElement graphicsElement, PaletteMap paletteMap) + public ImageViewModel(GraphicsElement graphicsElement, PaletteMap paletteMap) { - ImageIndex = imageIndex; - ImageName = imageName; + Name = graphicsElement.Name; XOffset = graphicsElement.XOffset; YOffset = graphicsElement.YOffset; Flags = graphicsElement.Flags; @@ -60,12 +59,7 @@ public ImageViewModel(int imageIndex, string imageName, GraphicsElement graphics _ = this.WhenAnyValue(o => o.DisplayedImage) .Subscribe(_ => this.RaisePropertyChanged(nameof(SelectedBitmapPreviewBorder))); - if (!PaletteMap.TryConvertG1ToRgba32Bitmap(graphicsElement, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var image)) - { - throw new Exception("Failed to convert image"); - } - - UnderlyingImage = image!; + UnderlyingImage = graphicsElement.Image!; } public void RecolourImage(ColourRemapSwatch primary, ColourRemapSwatch secondary) @@ -147,10 +141,11 @@ static Rectangle FindCropRegion(Image image) public GraphicsElement ToGraphicsElement() { - if (ImageIndex < 0 || UnderlyingImage == null) + if (UnderlyingImage == null) { - throw new InvalidOperationException("Cannot convert to GraphicsElement when ImageIndex is less than 0 or UnderlyingImage is null"); + throw new InvalidOperationException("Cannot convert to GraphicsElement when UnderlyingImage is null"); } + // turn rgba32 into raw palette image var rawData = PaletteMap.ConvertRgba32ImageToG1Data(UnderlyingImage, Flags); return new GraphicsElement diff --git a/Gui/ViewModels/SubObjectTypes/IExtraContentViewModel.cs b/Gui/ViewModels/IExtraContentViewModel.cs similarity index 100% rename from Gui/ViewModels/SubObjectTypes/IExtraContentViewModel.cs rename to Gui/ViewModels/IExtraContentViewModel.cs diff --git a/Gui/ViewModels/DatTypes/BaseLocoFileViewModel.cs b/Gui/ViewModels/LocoTypes/BaseLocoFileViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/BaseLocoFileViewModel.cs rename to Gui/ViewModels/LocoTypes/BaseLocoFileViewModel.cs diff --git a/Gui/ViewModels/DatTypes/G1ViewModel.cs b/Gui/ViewModels/LocoTypes/G1ViewModel.cs similarity index 84% rename from Gui/ViewModels/DatTypes/G1ViewModel.cs rename to Gui/ViewModels/LocoTypes/G1ViewModel.cs index 1ae42371..43fc3520 100644 --- a/Gui/ViewModels/DatTypes/G1ViewModel.cs +++ b/Gui/ViewModels/LocoTypes/G1ViewModel.cs @@ -1,8 +1,8 @@ using Dat.FileParsing; using Gui.Models; +using Gui.ViewModels.Graphics; using ReactiveUI.Fody.Helpers; using System.IO; -using System.Linq; using System.Threading.Tasks; namespace Gui.ViewModels; @@ -26,7 +26,8 @@ public override void Load() return; } - ImageTableViewModel = new ImageTableViewModel(Model.G1.GraphicsElements, Model.G1, Model.PaletteMap, logger); + Model.G1.ImageTable.PaletteMap = Model.PaletteMap; + ImageTableViewModel = new ImageTableViewModel(Model.G1.ImageTable, logger); } public override void Save() @@ -37,7 +38,7 @@ public override void Save() return; } - Model.G1.GraphicsElements = [.. ImageTableViewModel.ImageViewModels.Select(x => x.ToGraphicsElement())]; + //Model.G1.ImageTable.GraphicsElements = [.. ImageTableViewModel.ImageViewModels.Select(x => x.ToGraphicsElement())]; var savePath = CurrentFile.FileLocation == FileLocation.Local ? Path.Combine(Model.Settings.ObjDataDirectory, CurrentFile.FileName) diff --git a/Gui/ViewModels/DatTypes/ILocoFileViewModel.cs b/Gui/ViewModels/LocoTypes/ILocoFileViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/ILocoFileViewModel.cs rename to Gui/ViewModels/LocoTypes/ILocoFileViewModel.cs diff --git a/Gui/ViewModels/DatTypes/IObjectViewModel.cs b/Gui/ViewModels/LocoTypes/IObjectViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/IObjectViewModel.cs rename to Gui/ViewModels/LocoTypes/IObjectViewModel.cs diff --git a/Gui/ViewModels/DatTypes/LocoObjectViewModel.cs b/Gui/ViewModels/LocoTypes/LocoObjectViewModel.cs similarity index 76% rename from Gui/ViewModels/DatTypes/LocoObjectViewModel.cs rename to Gui/ViewModels/LocoTypes/LocoObjectViewModel.cs index 8821bf3e..68157f96 100644 --- a/Gui/ViewModels/DatTypes/LocoObjectViewModel.cs +++ b/Gui/ViewModels/LocoTypes/LocoObjectViewModel.cs @@ -7,5 +7,6 @@ public abstract class LocoObjectViewModel : ReactiveObject, IObjectViewModel< { public abstract T GetAsModel(); - ILocoStruct IObjectViewModel.GetAsModel() => GetAsModel(); + ILocoStruct IObjectViewModel.GetAsModel() + => GetAsModel(); } diff --git a/Gui/ViewModels/DatTypes/MusicViewModel.cs b/Gui/ViewModels/LocoTypes/MusicViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/MusicViewModel.cs rename to Gui/ViewModels/LocoTypes/MusicViewModel.cs diff --git a/Gui/ViewModels/DatTypes/ObjectEditorViewModel.cs b/Gui/ViewModels/LocoTypes/ObjectEditorViewModel.cs similarity index 88% rename from Gui/ViewModels/DatTypes/ObjectEditorViewModel.cs rename to Gui/ViewModels/LocoTypes/ObjectEditorViewModel.cs index 459279bd..90e541ee 100644 --- a/Gui/ViewModels/DatTypes/ObjectEditorViewModel.cs +++ b/Gui/ViewModels/LocoTypes/ObjectEditorViewModel.cs @@ -5,9 +5,11 @@ using Dat.Data; using Dat.FileParsing; using Definitions.ObjectModels; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Objects.Sound; using Gui.Models; using Gui.Models.Audio; +using Gui.ViewModels.Graphics; using Gui.Views; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -158,13 +160,31 @@ public override void Load() CurrentObjectViewModel = GetViewModelFromStruct(CurrentObject.LocoObject.Object); StringTableViewModel = new(CurrentObject.LocoObject.StringTable); - var imageNameProvider = (CurrentObject.LocoObject.Object is IImageTableNameProvider itnp) - ? itnp - : new DefaultImageTableNameProvider(); - - ExtraContentViewModel = CurrentObject.LocoObject.Object is SoundObject soundObject - ? new AudioViewModel(logger, CurrentObject.DatFileInfo.S5Header.Name, soundObject.SoundObjectData.PcmHeader, soundObject.PcmData) - : new ImageTableViewModel(CurrentObject.LocoObject.GraphicsElements, imageNameProvider, Model.PaletteMap, Model.Logger); + if (CurrentObject.LocoObject.Object is SoundObject soundObject) + { + ExtraContentViewModel = new AudioViewModel(logger, CurrentObject.DatFileInfo.S5Header.Name, soundObject.SoundObjectData.PcmHeader, soundObject.PcmData); + } + else + { + CurrentObject.LocoObject.ImageTable?.PaletteMap = Model.PaletteMap; + + // temporary hack to show building components + if (CurrentObject.LocoObject.ObjectType == Definitions.ObjectModels.Types.ObjectType.Building) + { + ExtraContentViewModel = new ImageTableViewModel(CurrentObject.LocoObject.ImageTable, Model.Logger, (CurrentObject.LocoObject.Object as IHasBuildingComponents)?.BuildingComponents); + } + else + { + if (CurrentObject.LocoObject.ImageTable == null) + { + logger.Info("${CurrentFile.DisplayName has no image table"); + } + else + { + ExtraContentViewModel = new ImageTableViewModel(CurrentObject.LocoObject.ImageTable, Model.Logger, null); + } + } + } } else { @@ -279,7 +299,7 @@ void SaveCore(string filename, SawyerEncoding? encodingToUse = null) if (ExtraContentViewModel is ImageTableViewModel itvm) { - CurrentObject.LocoObject.GraphicsElements = itvm.ImageViewModels.Select(x => x.ToGraphicsElement()).ToList(); + CurrentObject.LocoObject.ImageTable.GraphicsElements = itvm.GroupedImageViewModels.SelectMany(x => x.Images).Select(x => x.ToGraphicsElement()).ToList(); } // this is hacky but it should work diff --git a/Gui/ViewModels/DatTypes/ObjectHeaderViewModel.cs b/Gui/ViewModels/LocoTypes/ObjectHeaderViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/ObjectHeaderViewModel.cs rename to Gui/ViewModels/LocoTypes/ObjectHeaderViewModel.cs diff --git a/Gui/ViewModels/DatTypes/ObjectModelHeaderViewModel.cs b/Gui/ViewModels/LocoTypes/ObjectModelHeaderViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/ObjectModelHeaderViewModel.cs rename to Gui/ViewModels/LocoTypes/ObjectModelHeaderViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/AirportViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs similarity index 85% rename from Gui/ViewModels/DatTypes/Objects/AirportViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs index 4f023873..6f18f865 100644 --- a/Gui/ViewModels/DatTypes/Objects/AirportViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs @@ -1,6 +1,6 @@ using Dat.Loaders; using Definitions.ObjectModels.Objects.Airport; -using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; using PropertyModels.Extensions; using System.Collections.ObjectModel; using System.ComponentModel; @@ -44,9 +44,9 @@ public AirportViewModel(AirportObject ao) CostIndex = ao.CostIndex; BuildCostFactor = ao.BuildCostFactor; SellCostFactor = ao.SellCostFactor; - BuildingHeights = new(ao.BuildingHeights); - BuildingAnimations = new(ao.BuildingAnimations); - BuildingVariations = new(ao.BuildingVariations.Select(x => new ObservableCollection(x))); + BuildingHeights = new(ao.BuildingComponents.BuildingHeights); + BuildingAnimations = new(ao.BuildingComponents.BuildingAnimations); + BuildingVariations = new(ao.BuildingComponents.BuildingVariations.Select(x => new ObservableCollection(x))); BuildingPositions = new(ao.BuildingPositions); MovementNodes = new(ao.MovementNodes); MovementEdges = new(ao.MovementEdges); @@ -70,9 +70,12 @@ public override AirportObject GetAsModel() CostIndex = CostIndex, BuildCostFactor = BuildCostFactor, SellCostFactor = SellCostFactor, - BuildingHeights = [.. BuildingHeights], - BuildingAnimations = [.. BuildingAnimations], - BuildingVariations = BuildingVariations.ToList().ConvertAll(x => x.ToList()), + BuildingComponents = new BuildingComponentsModel() + { + BuildingHeights = [.. BuildingHeights], + BuildingAnimations = [.. BuildingAnimations], + BuildingVariations = [.. BuildingVariations.Select(x => x.ToList())], + }, BuildingPositions = [.. BuildingPositions], MovementNodes = [.. MovementNodes], MovementEdges = [.. MovementEdges], diff --git a/Gui/ViewModels/DatTypes/Objects/BridgeViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/BridgeViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs diff --git a/Gui/ViewModels/LocoTypes/Objects/Building/BuildingComponentsViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingComponentsViewModel.cs new file mode 100644 index 00000000..1d9aa771 --- /dev/null +++ b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingComponentsViewModel.cs @@ -0,0 +1,149 @@ +using Definitions.ObjectModels; +using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; +using Gui.Models; +using PropertyModels.ComponentModel; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using ReactiveObject = ReactiveUI.ReactiveObject; + +namespace Gui.ViewModels.LocoTypes.Objects.Building; + +[TypeConverter(typeof(ExpandableObjectConverter))] +public class BuildingComponentsViewModel : ReactiveObject +{ + [Reactive] + [Trackable(0, 64)] + public int VerticalLayerSpacing { get; set; } + + [Reactive] public int MaxWidth { get; set; } + [Reactive] public int MaxHeight { get; set; } + + //[Reactive] + //public BuildingComponentsModel BuildingComponentsModel { get; set; } + + [Reactive, Browsable(false)] + public ObservableCollection BuildingHeights { get; set; } = []; + + [Reactive, Browsable(false)] + public ObservableCollection BuildingAnimations { get; set; } = []; + + //[Reactive] + //public List> BuildingVariations { get; set; } = []; + + //[Browsable(false)] + [Reactive] + public ObservableCollection BuildingVariationViewModels { get; set; } = []; + + protected ImageTable ImageTable { get; set; } + + public BuildingComponentsViewModel() + { + //_ = this.WhenAnyValue(x => x.BuildingVariations) + // .Subscribe(_ => this.RaisePropertyChanged(nameof(BuildingVariationViewModels))); + + _ = this.WhenAnyValue(x => x.VerticalLayerSpacing) + .Subscribe(ApplyOffsetToAllLayers); + } + + public BuildingComponentsViewModel(BuildingComponentsModel buildingComponents, ImageTable imageTable) : this() + { + ArgumentNullException.ThrowIfNull(buildingComponents); + ArgumentNullException.ThrowIfNull(imageTable); + + //_ = this.WhenAnyValue(x => x.BuildingVariationViewModels) + // .Where(x => x != null && ImageTable != null) + // .Subscribe(_ => RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations)); + + ImageTable = imageTable; + BuildingHeights = new ObservableCollection(buildingComponents.BuildingHeights); + BuildingAnimations = new ObservableCollection(buildingComponents.BuildingAnimations); + + RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations); + + //BuildingVariations = buildingComponents.BuildingVariations; + //BuildingComponentsModel = buildingComponents; + } + + protected void RecomputeBuildingVariationViewModels(List> buildingVariations) + { + var layers = ImageTable.Groups.ConvertAll(x => x.GraphicsElements); + + BuildingVariationViewModels.Clear(); + + MaxWidth = layers.Max(x => x.Max(y => y.Width)) + 16; + MaxHeight = (layers.Max(x => x.Max(y => y.Height)) * BuildingHeights.Count) + BuildingHeights.Sum(x => x) + buildingVariations.Max(x => x.Count) * VerticalLayerSpacing; + + var x = 0; + foreach (var variation in buildingVariations) + { + var bv = new BuildingVariationViewModel() + { + VariationName = $"Variation {x++}", + }; + + const int numDirections = 4; + for (var i = 0; i < numDirections; ++i) + { + var bs = new BuildingStackViewModel() + { + Direction = (CardinalDirection)i + }; + + var cumulativeOffset = 0; + foreach (var variationItem in variation) + { + var layer = layers[variationItem]; + var bl = new BuildingLayerViewModel + { + XBase = layer[i].XOffset, // + (MaxWidth / 2), + YBase = layer[i].YOffset - cumulativeOffset + MaxHeight * 0.80, + DisplayedImage = layer[i].Image.ToAvaloniaBitmap(), + XOffset = 0, + YOffset = 0, + }; + + cumulativeOffset += BuildingHeights[variationItem]; + bs.Layers.Add(bl); + } + + bv.Directions.Add(bs); // [i] = bs; + } + + BuildingVariationViewModels.Add(bv); + } + } + + private void ApplyOffsetToAllLayers(int offset) + { + foreach (var variation in BuildingVariationViewModels) + { + if (variation?.Directions == null) + { + continue; + } + + foreach (var dir in variation.Directions) + { + if (dir?.Layers == null) + { + continue; + } + + var cumulativeOffset = 0; + foreach (var layer in dir.Layers) + { + layer.YOffset = cumulativeOffset; + cumulativeOffset -= VerticalLayerSpacing; + } + } + } + } +} + diff --git a/Gui/ViewModels/LocoTypes/Objects/Building/BuildingLayerViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingLayerViewModel.cs new file mode 100644 index 00000000..6266d0ae --- /dev/null +++ b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingLayerViewModel.cs @@ -0,0 +1,40 @@ +using Avalonia.Media.Imaging; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using System; +using System.ComponentModel; +using System.Reactive.Linq; + +namespace Gui.ViewModels.LocoTypes.Objects.Building; + +[TypeConverter(typeof(ExpandableObjectConverter))] +public class BuildingLayerViewModel : ReactiveObject +{ + [Reactive] public Bitmap DisplayedImage { get; set; } + + public double Width => DisplayedImage.Size.Width; + public double Height => DisplayedImage.Size.Height; + + public double X => XBase + XOffset; + public double Y => YBase + YOffset; + + [Reactive] public double XBase { get; set; } + [Reactive] public double YBase { get; set; } + + [Reactive] public double XOffset { get; set; } + [Reactive] public double YOffset { get; set; } + + public BuildingLayerViewModel() + { + _ = this.WhenAnyValue(o => o.XBase) + .Subscribe(_ => this.RaisePropertyChanged(nameof(X))); + _ = this.WhenAnyValue(o => o.XOffset) + .Subscribe(_ => this.RaisePropertyChanged(nameof(X))); + + _ = this.WhenAnyValue(o => o.YBase) + .Subscribe(_ => this.RaisePropertyChanged(nameof(Y))); + _ = this.WhenAnyValue(o => o.YOffset) + .Subscribe(_ => this.RaisePropertyChanged(nameof(Y))); + } +} + diff --git a/Gui/ViewModels/LocoTypes/Objects/Building/BuildingStackViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingStackViewModel.cs new file mode 100644 index 00000000..13d8b6dd --- /dev/null +++ b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingStackViewModel.cs @@ -0,0 +1,14 @@ +using Definitions.ObjectModels.Objects.Building; +using ReactiveUI; +using System.Collections.ObjectModel; +using System.ComponentModel; + +namespace Gui.ViewModels.LocoTypes.Objects.Building; + +[TypeConverter(typeof(ExpandableObjectConverter))] +public class BuildingStackViewModel : ReactiveObject +{ + public CardinalDirection Direction { get; init; } + public ObservableCollection Layers { get; set; } = []; +} + diff --git a/Gui/ViewModels/LocoTypes/Objects/Building/BuildingVariationViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingVariationViewModel.cs new file mode 100644 index 00000000..2a43dc56 --- /dev/null +++ b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingVariationViewModel.cs @@ -0,0 +1,13 @@ +using ReactiveUI; +using System.Collections.ObjectModel; +using System.ComponentModel; + +namespace Gui.ViewModels.LocoTypes.Objects.Building; + +[TypeConverter(typeof(ExpandableObjectConverter))] +public class BuildingVariationViewModel : ReactiveObject +{ + public string VariationName { get; init; } + public ObservableCollection Directions { get; set; } = []; +} + diff --git a/Gui/ViewModels/DatTypes/Objects/BuildingViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingViewModel.cs similarity index 71% rename from Gui/ViewModels/DatTypes/Objects/BuildingViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/Building/BuildingViewModel.cs index 0b4e060f..1c1de5de 100644 --- a/Gui/ViewModels/DatTypes/Objects/BuildingViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/Building/BuildingViewModel.cs @@ -1,8 +1,8 @@ using Dat.Loaders; using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Types; using PropertyModels.ComponentModel.DataAnnotations; -using PropertyModels.Extensions; using ReactiveUI.Fody.Helpers; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; -namespace Gui.ViewModels; +namespace Gui.ViewModels.LocoTypes.Objects.Building; public class BuildingViewModel : LocoObjectViewModel { @@ -28,16 +28,18 @@ public class BuildingViewModel : LocoObjectViewModel [Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public ObservableCollection ProducedCargo { get; set; } [Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public ObservableCollection RequiredCargo { get; set; } [Category("Production"), Length(1, BuildingObjectLoader.Constants.MaxProducedCargoType)] public ObservableCollection ProducedQuantity { get; set; } - [Category("Building"), Length(1, BuildingObjectLoader.Constants.BuildingVariationCount)] public ObservableCollection> BuildingVariations { get; set; } // NumBuildingVariations - [Category("Building"), Length(1, BuildingObjectLoader.Constants.BuildingHeightCount)] public ObservableCollection BuildingHeights { get; set; } // NumBuildingParts - [Category("Building"), Length(1, BuildingObjectLoader.Constants.BuildingAnimationCount)] public ObservableCollection BuildingAnimations { get; set; } // NumBuildingParts + + [Category("Building"), Length(1, AirportObjectLoader.Constants.BuildingVariationCount)] public ObservableCollection> BuildingVariations { get; set; } // NumBuildingVariations + [Category("Building"), Length(1, AirportObjectLoader.Constants.BuildingHeightCount)] public ObservableCollection BuildingHeights { get; set; } // NumBuildingParts + [Category("Building"), Length(1, AirportObjectLoader.Constants.BuildingAnimationCount)] public ObservableCollection BuildingAnimations { get; set; } // NumBuildingParts // note: these height sequences are massive. BLDCTY28 has 2 sequences, 512 in length and 1024 in length. Avalonia PropertyGrid takes 30+ seconds to render this. todo: don't use property grid in future - //[Reactive, Category("Building"), Length(1, BuildingObject.MaxElevatorHeightSequences), Browsable(false)] public ObservableCollection> ElevatorHeightSequences { get; set; } // NumElevatorSequences - [Browsable(false)] public uint8_t[]? ElevatorSequence1 { get; set; } - [Browsable(false)] public uint8_t[]? ElevatorSequence2 { get; set; } - [Browsable(false)] public uint8_t[]? ElevatorSequence3 { get; set; } - [Browsable(false)] public uint8_t[]? ElevatorSequence4 { get; set; } + //[Reactive, Category("Building"), Length(1, BuildingObject.MaxElevatorHeightSequences), Browsable(false)] public BindingList> ElevatorHeightSequences { get; set; } // NumElevatorSequences + + [Category("Elevator"), Browsable(false)] public uint8_t[]? ElevatorSequence1 { get; set; } + [Category("Elevator"), Browsable(false)] public uint8_t[]? ElevatorSequence2 { get; set; } + [Category("Elevator"), Browsable(false)] public uint8_t[]? ElevatorSequence3 { get; set; } + [Category("Elevator"), Browsable(false)] public uint8_t[]? ElevatorSequence4 { get; set; } [Reactive, Category("")] public uint8_t var_A6 { get; set; } [Reactive, Category("")] public uint8_t var_A7 { get; set; } @@ -58,12 +60,12 @@ public BuildingViewModel(BuildingObject bo) ObsoleteYear = bo.ObsoleteYear; CostIndex = bo.CostIndex; SellCostFactor = bo.SellCostFactor; - ProducedCargo = new(bo.ProducedCargo.ConvertAll(x => new ObjectModelHeaderViewModel(x))); - RequiredCargo = new(bo.RequiredCargo.ConvertAll(x => new ObjectModelHeaderViewModel(x))); + ProducedCargo = [.. bo.ProducedCargo.ConvertAll(x => new ObjectModelHeaderViewModel(x))]; + RequiredCargo = [.. bo.RequiredCargo.ConvertAll(x => new ObjectModelHeaderViewModel(x))]; ProducedQuantity = [.. bo.ProducedQuantity]; - BuildingHeights = new(bo.BuildingHeights); - BuildingAnimations = new(bo.BuildingAnimations); - BuildingVariations = new(bo.BuildingVariations.Select(x => new ObservableCollection(x))); + BuildingHeights = new(bo.BuildingComponents.BuildingHeights); + BuildingAnimations = new(bo.BuildingComponents.BuildingAnimations); + BuildingVariations = new(bo.BuildingComponents.BuildingVariations.Select(x => new ObservableCollection(x))); ElevatorSequence1 = bo.ElevatorHeightSequences.Count > 0 ? bo.ElevatorHeightSequences[0] : null; ElevatorSequence2 = bo.ElevatorHeightSequences.Count > 1 ? bo.ElevatorHeightSequences[1] : null; ElevatorSequence3 = bo.ElevatorHeightSequences.Count > 2 ? bo.ElevatorHeightSequences[2] : null; @@ -79,11 +81,14 @@ public BuildingViewModel(BuildingObject bo) // validation: // BuildingVariationHeights.Count MUST equal BuildingVariationAnimations.Count public override BuildingObject GetAsModel() - => new BuildingObject() + => new() { - BuildingAnimations = [.. BuildingAnimations], - BuildingHeights = [.. BuildingHeights], - BuildingVariations = BuildingVariations.ToList().ConvertAll(x => x.ToList()), + BuildingComponents = new BuildingComponentsModel() + { + BuildingHeights = [.. BuildingHeights], + BuildingAnimations = [.. BuildingAnimations], + BuildingVariations = BuildingVariations.ToList().ConvertAll(x => x.ToList()), + }, Flags = Flags, Colours = Colours, ScaffoldingColour = ScaffoldingColour, @@ -114,18 +119,22 @@ List GetElevatorSequences() { result.Add(ElevatorSequence1); } + if (ElevatorSequence2 != null) { result.Add(ElevatorSequence2); } + if (ElevatorSequence3 != null) { result.Add(ElevatorSequence3); } + if (ElevatorSequence4 != null) { result.Add(ElevatorSequence4); } + return result; } } diff --git a/Gui/ViewModels/LocoTypes/Objects/Building/DesignBuildingComponentsViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/Building/DesignBuildingComponentsViewModel.cs new file mode 100644 index 00000000..7837f26a --- /dev/null +++ b/Gui/ViewModels/LocoTypes/Objects/Building/DesignBuildingComponentsViewModel.cs @@ -0,0 +1,86 @@ +using Definitions.ObjectModels; +using Definitions.ObjectModels.Objects.Common; +using Definitions.ObjectModels.Types; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using System.Collections.Generic; + +namespace Gui.ViewModels.LocoTypes.Objects.Building; + +public class DesignBuildingComponentsViewModel : BuildingComponentsViewModel +{ + public Image CreateDummyImage(int width, int height) + { + var image = new Image(width, height); + + // Fill the entire image with a background color (e.g., white) + image.Mutate(ctx => + { + ctx.BackgroundColor(Color.White); + + // Draw a red rectangle + var border = 1; + ctx.Fill(Color.Red, new RectangleF(border, border, width - (2 * border), height - (2 * border))); + + // Draw a blue circle + //ctx.Fill(Color.Blue, new EllipsePolygon(400, 300, 100)); + + // Draw text + // You'll need to load a font for this + // Example: Font font = SystemFonts.CreateFont("Arial", 24); + // ctx.DrawText("Hello, ImageSharp!", font, Color.Black, new PointF(50, 450)); + }); + + // Save the image to a file + //image.Save("myGeneratedImage.png"); + + return image; + } + + public DesignBuildingComponentsViewModel() + { + var width = (short)32; + var height = (short)32; + + ImageTable = new ImageTable() + { + Groups = [ + ( + "Layer 0", + [ + new GraphicsElement() { Name = "Layer 0 - South", Width = width, Height = height, Image = CreateDummyImage(width, height) }, + new GraphicsElement() { Name = "Layer 0 - West ", Width = width, Height = height, Image = CreateDummyImage(width, height) }, + new GraphicsElement() { Name = "Layer 0 - North", Width = width, Height = height, Image = CreateDummyImage(width, height) }, + new GraphicsElement() { Name = "Layer 0 - East ", Width = width, Height = height, Image = CreateDummyImage(width, height) }, + ] + ), + ( + "Layer 1", + [ + new GraphicsElement() { Name = "Layer 1 - South", Width = width, Height = height, Image = CreateDummyImage(width, height) }, + new GraphicsElement() { Name = "Layer 1 - West ", Width = width, Height = height, Image = CreateDummyImage(width, height) }, + new GraphicsElement() { Name = "Layer 1 - North", Width = width, Height = height, Image = CreateDummyImage(width, height) }, + new GraphicsElement() { Name = "Layer 1 - East ", Width = width, Height = height, Image = CreateDummyImage(width, height) }, + ] + ) + ] + }; + + BuildingHeights = [16, 16]; + BuildingAnimations = + [ + new BuildingPartAnimation() { AnimationSpeed = 40, NumFrames = 20 }, + new BuildingPartAnimation() { AnimationSpeed = 30, NumFrames = 15 }, + ]; + List> buildingVariations = + [ + [0, 1], + [0, 1, 1], + ]; + + RecomputeBuildingVariationViewModels(buildingVariations); + } +} diff --git a/Gui/ViewModels/DatTypes/Objects/CargoViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/CargoViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/CargoViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/CargoViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/CliffEdgeViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/CliffEdgeViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/CliffEdgeViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/CliffEdgeViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/ClimateViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/ClimateViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/ClimateViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/ClimateViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/CompetitorViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/CompetitorViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/CompetitorViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/CompetitorViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/CurrencyViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/CurrencyViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/CurrencyViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/CurrencyViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/DockViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs similarity index 79% rename from Gui/ViewModels/DatTypes/Objects/DockViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs index e66cd922..4747037c 100644 --- a/Gui/ViewModels/DatTypes/Objects/DockViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs @@ -1,5 +1,5 @@ using Dat.Loaders; -using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Objects.Dock; using Definitions.ObjectModels.Types; using PropertyModels.ComponentModel.DataAnnotations; @@ -35,9 +35,9 @@ public DockViewModel(DockObject @do) SellCostFactor = @do.SellCostFactor; BoatPosition = @do.BoatPosition; var_07 = @do.var_07; - BuildingHeights = new(@do.BuildingHeights); - BuildingAnimations = new(@do.BuildingAnimations); - BuildingVariations = new(@do.BuildingVariations.Select(x => new ObservableCollection(x))); + BuildingHeights = new(@do.BuildingComponents.BuildingHeights); + BuildingAnimations = new(@do.BuildingComponents.BuildingAnimations); + BuildingVariations = new(@do.BuildingComponents.BuildingVariations.Select(x => new ObservableCollection(x))); } public override DockObject GetAsModel() @@ -52,9 +52,12 @@ public override DockObject GetAsModel() SellCostFactor = SellCostFactor, BoatPosition = BoatPosition, var_07 = var_07, - BuildingHeights = [.. BuildingHeights], - BuildingAnimations = [.. BuildingAnimations], - BuildingVariations = BuildingVariations.ToList().ConvertAll(x => x.ToList()), + BuildingComponents = new BuildingComponentsModel() + { + BuildingHeights = [.. BuildingHeights], + BuildingAnimations = [.. BuildingAnimations], + BuildingVariations = [.. BuildingVariations.Select(x => x.ToList())], + }, }; return dockObject; } diff --git a/Gui/ViewModels/DatTypes/Objects/HillShapesViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/HillShapesViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/HillShapesViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/HillShapesViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/IndustryViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs similarity index 91% rename from Gui/ViewModels/DatTypes/Objects/IndustryViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs index 054ef0ad..ef16daab 100644 --- a/Gui/ViewModels/DatTypes/Objects/IndustryViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs @@ -1,5 +1,5 @@ using Dat.Loaders; -using Definitions.ObjectModels.Objects.Building; +using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Objects.Industry; using Definitions.ObjectModels.Types; using PropertyModels.ComponentModel.DataAnnotations; @@ -49,9 +49,9 @@ public class IndustryViewModel : LocoObjectViewModel public IndustryViewModel(IndustryObject io) { AnimationSequences = new(io.AnimationSequences.Select(x => new ObservableCollection(x))); - BuildingAnimations = new(io.BuildingAnimations); - BuildingHeights = new(io.BuildingHeights); - BuildingVariations = new(io.BuildingVariations.Select(x => new ObservableCollection(x))); + BuildingHeights = new(io.BuildingComponents.BuildingHeights); + BuildingAnimations = new(io.BuildingComponents.BuildingAnimations); + BuildingVariations = new(io.BuildingComponents.BuildingVariations.Select(x => new ObservableCollection(x))); UnkBuildingData = new(io.UnkBuildingData); BuildingSizeFlags = io.BuildingSizeFlags; BuildingWall = io.BuildingWall == null ? null : new(io.BuildingWall); @@ -61,7 +61,7 @@ public IndustryViewModel(IndustryObject io) InitialProductionRate = new(io.InitialProductionRate); ProducedCargo = new(io.ProducedCargo.ConvertAll(x => new ObjectModelHeaderViewModel(x))); RequiredCargo = new(io.RequiredCargo.ConvertAll(x => new ObjectModelHeaderViewModel(x))); - WallTypes = new(io.WallTypes.ConvertAll(x => new ObjectModelHeaderViewModel(x))); + WallTypes = [.. io.WallTypes.ConvertAll(x => new ObjectModelHeaderViewModel(x))]; Colours = io.Colours; DesignedYear = io.DesignedYear; ObsoleteYear = io.ObsoleteYear; @@ -79,7 +79,7 @@ public IndustryViewModel(IndustryObject io) FarmNumStagesOfGrowth = io.FarmNumStagesOfGrowth; MonthlyClosureChance = io.MonthlyClosureChance; var_E8 = io.var_E8; - var_38 = new(io.var_38); + var_38 = [.. io.var_38]; } // validation: @@ -88,9 +88,12 @@ public override IndustryObject GetAsModel() => new() { AnimationSequences = AnimationSequences.ToList().ConvertAll(x => x.ToList()), - BuildingAnimations = [.. BuildingAnimations], - BuildingHeights = [.. BuildingHeights], - BuildingVariations = BuildingVariations.ToList().ConvertAll(x => x.ToList()), + BuildingComponents = new BuildingComponentsModel() + { + BuildingHeights = [.. BuildingHeights], + BuildingAnimations = [.. BuildingAnimations], + BuildingVariations = [.. BuildingVariations.Select(x => x.ToList())], + }, UnkBuildingData = [.. UnkBuildingData], BuildingSizeFlags = BuildingSizeFlags, BuildingWall = BuildingWall?.GetAsModel(), diff --git a/Gui/ViewModels/DatTypes/Objects/InterfaceSkinViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/InterfaceSkinViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/InterfaceSkinViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/InterfaceSkinViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/LandViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/LandViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/LandViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/LandViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/LevelCrossingViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/LevelCrossingViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/LevelCrossingViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/LevelCrossingViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/RegionViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RegionViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/RegionViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/RegionViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/RoadExtraViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/RoadExtraViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/RoadStationViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/RoadStationViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/RoadViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/RoadViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/ScaffoldingViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/ScaffoldingViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/ScaffoldingViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/ScaffoldingViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/ScenarioTextViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/ScenarioTextViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/ScenarioTextViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/ScenarioTextViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/SnowViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/SnowViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/SnowViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/SnowViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/SoundViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/SoundViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/SoundViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/SoundViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/SteamViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/SteamViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/SteamViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/SteamViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/StreetLightViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/StreetLightViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/StreetLightViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/StreetLightViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/TownNamesViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TownNamesViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/TownNamesViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/TownNamesViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/TrackExtraViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/TrackExtraViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/TrackSignalViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/TrackSignalViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/TrackStationViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/TrackStationViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/TrackViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/TrackViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/TreeViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/TreeViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/TunnelViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TunnelViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/TunnelViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/TunnelViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/VehicleViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/VehicleViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/WallViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/WallViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/WallViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/WallViewModel.cs diff --git a/Gui/ViewModels/DatTypes/Objects/WaterViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/WaterViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/Objects/WaterViewModel.cs rename to Gui/ViewModels/LocoTypes/Objects/WaterViewModel.cs diff --git a/Gui/ViewModels/DatTypes/SCV5ViewModel.cs b/Gui/ViewModels/LocoTypes/SCV5ViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/SCV5ViewModel.cs rename to Gui/ViewModels/LocoTypes/SCV5ViewModel.cs diff --git a/Gui/ViewModels/DatTypes/SoundEffectsViewModel.cs b/Gui/ViewModels/LocoTypes/SoundEffectsViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/SoundEffectsViewModel.cs rename to Gui/ViewModels/LocoTypes/SoundEffectsViewModel.cs diff --git a/Gui/ViewModels/DatTypes/TutorialViewModel.cs b/Gui/ViewModels/LocoTypes/TutorialViewModel.cs similarity index 100% rename from Gui/ViewModels/DatTypes/TutorialViewModel.cs rename to Gui/ViewModels/LocoTypes/TutorialViewModel.cs diff --git a/Gui/ViewModels/MainWindowViewModel.cs b/Gui/ViewModels/MainWindowViewModel.cs index 0a013d77..21ead87e 100644 --- a/Gui/ViewModels/MainWindowViewModel.cs +++ b/Gui/ViewModels/MainWindowViewModel.cs @@ -24,9 +24,6 @@ #if !DEBUG using Common; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text.Json; #endif namespace Gui.ViewModels; diff --git a/Gui/ViewModels/SubObjectTypes/StringTableViewModel.cs b/Gui/ViewModels/StringTableViewModel.cs similarity index 100% rename from Gui/ViewModels/SubObjectTypes/StringTableViewModel.cs rename to Gui/ViewModels/StringTableViewModel.cs diff --git a/Gui/Views/BuildingComponentsView.axaml b/Gui/Views/BuildingComponentsView.axaml new file mode 100644 index 00000000..615a892a --- /dev/null +++ b/Gui/Views/BuildingComponentsView.axaml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gui/Views/BuildingComponentsView.axaml.cs b/Gui/Views/BuildingComponentsView.axaml.cs new file mode 100644 index 00000000..5a11c335 --- /dev/null +++ b/Gui/Views/BuildingComponentsView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Gui.Views; + +public partial class BuildingComponentsView : UserControl +{ + public BuildingComponentsView() + { + InitializeComponent(); + } +} diff --git a/Gui/Views/FolderTreeView.axaml b/Gui/Views/FolderTreeView.axaml index 6acaf47a..5e0fc8d6 100644 --- a/Gui/Views/FolderTreeView.axaml +++ b/Gui/Views/FolderTreeView.axaml @@ -5,7 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:Gui.ViewModels" xmlns:mo="using:Gui.Models" - xmlns:moc="using:Gui.Models.Converters" + xmlns:cnv="using:Gui.Converters" xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="384" d:DesignHeight="768" x:Class="Gui.Views.FolderTreeView" @@ -16,8 +16,8 @@ - - + + diff --git a/Gui/Views/ImageTableView.axaml b/Gui/Views/ImageTableView.axaml index e0e838e1..1a0f6192 100644 --- a/Gui/Views/ImageTableView.axaml +++ b/Gui/Views/ImageTableView.axaml @@ -3,22 +3,24 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:vm="using:Gui.ViewModels" xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:pgc="clr-namespace:Avalonia.PropertyGrid.Controls;assembly=Avalonia.PropertyGrid" xmlns:amxc="using:Avalonia.Markup.Xaml.Converters" - xmlns:gmc="using:Gui.Models.Converters" xmlns:paz="using:Avalonia.Controls.PanAndZoom" xmlns:vi="using:Gui.Views" + xmlns:cnv="using:Gui.Converters" + xmlns:vmg="using:Gui.ViewModels.Graphics" + xmlns:vmlt="using:Gui.ViewModels.LocoTypes" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="900" x:Class="Gui.Views.ImageTableView" - x:DataType="vm:ImageTableViewModel"> + x:DataType="vmg:ImageTableViewModel"> - + + @@ -35,61 +37,165 @@ Export - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + + @@ -132,7 +238,8 @@ - + + @@ -144,6 +251,7 @@ + diff --git a/Gui/Views/ImageView.axaml b/Gui/Views/ImageView.axaml index 916bd448..f67379ba 100644 --- a/Gui/Views/ImageView.axaml +++ b/Gui/Views/ImageView.axaml @@ -3,15 +3,14 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:vm="using:Gui.ViewModels" + xmlns:vmg="using:Gui.ViewModels.Graphics" xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" - xmlns:pgc="clr-namespace:Avalonia.PropertyGrid.Controls;assembly=Avalonia.PropertyGrid" - + xmlns:pgc="clr-namespace:Avalonia.PropertyGrid.Controls;assembly=Avalonia.PropertyGrid" xmlns:amxc="using:Avalonia.Markup.Xaml.Converters" xmlns:gmc="using:Gui.Models.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Gui.Views.ImageView" - x:DataType="vm:ImageViewModel"> + x:DataType="vmg:ImageViewModel"> diff --git a/Gui/Views/MainWindow.axaml b/Gui/Views/MainWindow.axaml index 7f28da2d..6798145d 100644 --- a/Gui/Views/MainWindow.axaml +++ b/Gui/Views/MainWindow.axaml @@ -1,4 +1,3 @@ - - - - + + + @@ -76,7 +75,7 @@ - + diff --git a/Gui/Views/SCV5View.axaml b/Gui/Views/SCV5View.axaml index 91846c61..0249bac5 100644 --- a/Gui/Views/SCV5View.axaml +++ b/Gui/Views/SCV5View.axaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:Gui.ViewModels" - xmlns:moc="using:Gui.Models.Converters" + xmlns:cnv="using:Gui.Converters" xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:pgc="clr-namespace:Avalonia.PropertyGrid.Controls;assembly=Avalonia.PropertyGrid" xmlns:gui="using:Gui" @@ -13,9 +13,9 @@ x:DataType="vm:SCV5ViewModel"> - - - + + + diff --git a/Gui/Views/WindowInteractionExtensions.cs b/Gui/Views/WindowInteractionExtensions.cs index 030c75eb..98609326 100644 --- a/Gui/Views/WindowInteractionExtensions.cs +++ b/Gui/Views/WindowInteractionExtensions.cs @@ -7,7 +7,7 @@ namespace Gui.Views; public static class WindowInteractionExtensions { public static async Task DoShowDialogAsync(this Window owner, IInteractionContext interaction) - where TWindow : Window, new() + where TWindow : Window, new() { var dialog = new TWindow { diff --git a/ImageConversion/ImageConversion.csproj b/ImageConversion/ImageConversion.csproj new file mode 100644 index 00000000..752e02a9 --- /dev/null +++ b/ImageConversion/ImageConversion.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + enable + enable + win-x64 + + + + + + + diff --git a/ImageConversion/Program.cs b/ImageConversion/Program.cs new file mode 100644 index 00000000..cd8287b3 --- /dev/null +++ b/ImageConversion/Program.cs @@ -0,0 +1,40 @@ +using System.Drawing; +using System.Drawing.Imaging; + +foreach (var file in Directory.EnumerateFiles(AppContext.BaseDirectory, "*.png", SearchOption.AllDirectories)) +{ + try + { + Bitmap newBitmap; + + using (var originalBitmap = new Bitmap(file)) + { + newBitmap = new Bitmap(originalBitmap.Width, originalBitmap.Height, PixelFormat.Format32bppArgb); + + for (var y = 0; y < originalBitmap.Height; y++) + { + for (var x = 0; x < originalBitmap.Width; x++) + { + var pixelColor = originalBitmap.GetPixel(x, y); + if (pixelColor.R == 0 && pixelColor.G == 0 && pixelColor.B == 255) + { + newBitmap.SetPixel(x, y, Color.Transparent); + } + else + { + newBitmap.SetPixel(x, y, pixelColor); + } + } + } + } + + newBitmap.Save(file, ImageFormat.Png); + + Console.WriteLine($"Processed {Path.GetFileName(file)} and saved successfully!"); + } + catch (Exception ex) + { + Console.WriteLine("An error occurred: " + ex.Message); + Console.WriteLine($"Please make sure {Path.GetFileName(file)} exists and is a valid PNG image file."); + } +} diff --git a/ObjectEditor.sln b/ObjectEditor.sln index b744cb00..66e08b2d 100644 --- a/ObjectEditor.sln +++ b/ObjectEditor.sln @@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore build.sh = build.sh build_object_service.sh = build_object_service.sh + .github\copilot-instructions.md = .github\copilot-instructions.md dat-object-layout.md = dat-object-layout.md loco.db.dbml = loco.db.dbml loco_icon.ico = loco_icon.ico @@ -43,6 +44,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DatabaseImporter", "Databas EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Index", "Index\Index.csproj", "{26FB082F-DEC4-4501-907F-16E99C4D6442}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageConversion", "ImageConversion\ImageConversion.csproj", "{EA404621-9BBC-48E1-B956-CD9F99939F91}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -185,6 +188,18 @@ Global {26FB082F-DEC4-4501-907F-16E99C4D6442}.Release|x64.Build.0 = Release|Any CPU {26FB082F-DEC4-4501-907F-16E99C4D6442}.Release|x86.ActiveCfg = Release|Any CPU {26FB082F-DEC4-4501-907F-16E99C4D6442}.Release|x86.Build.0 = Release|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Debug|x64.Build.0 = Debug|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Debug|x86.ActiveCfg = Debug|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Debug|x86.Build.0 = Debug|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Release|Any CPU.Build.0 = Release|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Release|x64.ActiveCfg = Release|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Release|x64.Build.0 = Release|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Release|x86.ActiveCfg = Release|Any CPU + {EA404621-9BBC-48E1-B956-CD9F99939F91}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ObjectService/RouteHandlers/TableHandlers/AuthorRouteHandler.cs b/ObjectService/RouteHandlers/TableHandlers/AuthorRouteHandler.cs index 9135b182..fcd838a3 100644 --- a/ObjectService/RouteHandlers/TableHandlers/AuthorRouteHandler.cs +++ b/ObjectService/RouteHandlers/TableHandlers/AuthorRouteHandler.cs @@ -36,6 +36,7 @@ public static bool TryValidateCreate(DtoAuthorEntry request, [FromServices] Loco result = Results.BadRequest("Cannot add an empty or whitespace-only name."); return false; } + result = null; return true; } diff --git a/ObjectService/RouteHandlers/TableHandlers/LicenceRouteHandler.cs b/ObjectService/RouteHandlers/TableHandlers/LicenceRouteHandler.cs index 8605b6df..7820e83e 100644 --- a/ObjectService/RouteHandlers/TableHandlers/LicenceRouteHandler.cs +++ b/ObjectService/RouteHandlers/TableHandlers/LicenceRouteHandler.cs @@ -39,6 +39,7 @@ public static bool TryValidateCreate([FromBody] DtoLicenceEntry request, [FromSe result = Results.BadRequest("Cannot add an empty or whitespace-only name."); return false; } + result = null; return true; } diff --git a/ObjectService/RouteHandlers/TableHandlers/ObjectRouteHandler.cs b/ObjectService/RouteHandlers/TableHandlers/ObjectRouteHandler.cs index 71d68c89..72ef5242 100644 --- a/ObjectService/RouteHandlers/TableHandlers/ObjectRouteHandler.cs +++ b/ObjectService/RouteHandlers/TableHandlers/ObjectRouteHandler.cs @@ -571,7 +571,7 @@ static async Task GetObjectImagesAsync([FromRoute] UniqueObjectId id, [ using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) { var count = 0; - foreach (var g1 in locoObj!.LocoObject!.GraphicsElements) + foreach (var g1 in locoObj!.LocoObject!.ImageTable.GraphicsElements) { if (!pm.TryConvertG1ToRgba32Bitmap(g1, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var image)) { diff --git a/ObjectService/RouteHandlers/TableHandlers/RoleRouteHandler.cs b/ObjectService/RouteHandlers/TableHandlers/RoleRouteHandler.cs index e1f12c33..7912ea59 100644 --- a/ObjectService/RouteHandlers/TableHandlers/RoleRouteHandler.cs +++ b/ObjectService/RouteHandlers/TableHandlers/RoleRouteHandler.cs @@ -36,6 +36,7 @@ public static bool TryValidateCreate(DtoRoleEntry request, [FromServices] LocoDb result = Results.BadRequest("Cannot add an empty or whitespace-only name."); return false; } + result = null; return true; } diff --git a/ObjectService/RouteHandlers/TableHandlers/TagRouteHandler.cs b/ObjectService/RouteHandlers/TableHandlers/TagRouteHandler.cs index 68a0fc87..500e68be 100644 --- a/ObjectService/RouteHandlers/TableHandlers/TagRouteHandler.cs +++ b/ObjectService/RouteHandlers/TableHandlers/TagRouteHandler.cs @@ -36,6 +36,7 @@ public static bool TryValidateCreate([FromBody] DtoTagEntry request, [FromServic result = Results.BadRequest("Cannot add an empty or whitespace-only name."); return false; } + result = null; return true; } diff --git a/ObjectService/RouteHandlers/TableHandlers/UserRouteHandler.cs b/ObjectService/RouteHandlers/TableHandlers/UserRouteHandler.cs index 0c4331ef..5c462dc4 100644 --- a/ObjectService/RouteHandlers/TableHandlers/UserRouteHandler.cs +++ b/ObjectService/RouteHandlers/TableHandlers/UserRouteHandler.cs @@ -36,6 +36,7 @@ public static bool TryValidateCreate([FromBody] DtoUserEntry request, [FromServi result = Results.BadRequest("Cannot add an empty or whitespace-only name."); return false; } + result = null; return true; } diff --git a/ObjectService/RouteHandlers/TableHandlers/V1RouteHandler.cs b/ObjectService/RouteHandlers/TableHandlers/V1RouteHandler.cs index 9329d780..39dd5bc3 100644 --- a/ObjectService/RouteHandlers/TableHandlers/V1RouteHandler.cs +++ b/ObjectService/RouteHandlers/TableHandlers/V1RouteHandler.cs @@ -246,7 +246,7 @@ public static async Task GetObjectImages(UniqueObjectId uniqueObjectId, using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) { var count = 0; - foreach (var g1 in locoObj!.LocoObject!.GraphicsElements) + foreach (var g1 in locoObj!.LocoObject!.ImageTable.GraphicsElements) { if (!pm.TryConvertG1ToRgba32Bitmap(g1, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var image)) { diff --git a/Tests/G1Tests.cs b/Tests/G1Tests.cs index 8b070022..0f0122ff 100644 --- a/Tests/G1Tests.cs +++ b/Tests/G1Tests.cs @@ -11,10 +11,12 @@ namespace Dat.Tests; public class G1Tests { readonly ILogger Logger = new Logger(); - const string g1File = "Q:\\Games\\Locomotion\\G1\\g1.dat"; + const string Steam_G1 = "Q:\\Games\\Locomotion\\G1\\steam-g1.dat"; // todo: check both steam and gog + const string GoG_G1 = "Q:\\Games\\Locomotion\\G1\\gog-g1.dat"; // todo: check both steam and gog - [Test] - public void LoadSaveLoadG1() + [TestCase(Steam_G1)] + [TestCase(GoG_G1)] + public void LoadSaveLoadG1(string g1File) { var g1 = SawyerStreamReader.LoadG1(g1File, Logger); ArgumentNullException.ThrowIfNull(g1); @@ -28,12 +30,12 @@ public void LoadSaveLoadG1() { Assert.That(g1.G1Header.NumEntries, Is.EqualTo(g1a.G1Header.NumEntries)); Assert.That(g1.G1Header.TotalSize, Is.EqualTo(g1a.G1Header.TotalSize)); - Assert.That(g1.GraphicsElements, Has.Count.EqualTo(g1a.GraphicsElements.Count)); + Assert.That(g1.ImageTable.GraphicsElements, Has.Count.EqualTo(g1a.ImageTable.GraphicsElements.Count)); } using (Assert.EnterMultipleScope()) { - foreach (var (expected, actual, i) in g1.GraphicsElements.Zip(g1a.GraphicsElements).Select((item, i) => (item.First, item.Second, i))) + foreach (var (expected, actual, i) in g1.ImageTable.GraphicsElements.Zip(g1a.ImageTable.GraphicsElements).Select((item, i) => (item.First, item.Second, i))) { AssertG1ElementsEqual(expected, actual, i); } @@ -43,16 +45,22 @@ public void LoadSaveLoadG1() // These images have RLE runs/segment lengths > 127, which require special handling in the encode // method. I split these out to initially debug why they weren't working. The code now works but // I will leave these tests in as they serve as a kind of documentation of this quirk of the g1 encoding. - [TestCase(3539)] - [TestCase(3540)] - [TestCase(3541)] - [TestCase(3542)] - [TestCase(3618)] - [TestCase(3619)] - public void LoadSaveLoadG1_RLERunsGreaterThan127(int element) + [TestCase(Steam_G1, 3539)] + [TestCase(Steam_G1, 3540)] + [TestCase(Steam_G1, 3541)] + [TestCase(Steam_G1, 3542)] + [TestCase(Steam_G1, 3618)] + [TestCase(Steam_G1, 3619)] + [TestCase(GoG_G1, 3539)] + [TestCase(GoG_G1, 3540)] + [TestCase(GoG_G1, 3541)] + [TestCase(GoG_G1, 3542)] + [TestCase(GoG_G1, 3618)] + [TestCase(GoG_G1, 3619)] + public void LoadSaveLoadG1_RLERunsGreaterThan127(string g1File, int element) { var g1 = SawyerStreamReader.LoadG1(g1File, Logger); - var d1 = g1!.GraphicsElements[element]; + var d1 = g1!.ImageTable.GraphicsElements[element]; var e1 = SawyerStreamWriter.EncodeRLEImageData(d1); var dd1 = new DatG1Element32(0, d1.Width, d1.Height, d1.XOffset, d1.YOffset, (DatG1ElementFlags)d1.Flags, d1.ZoomOffset) { diff --git a/Tests/IdempotenceTests.cs b/Tests/IdempotenceTests.cs index a73f95fb..c6723b80 100644 --- a/Tests/IdempotenceTests.cs +++ b/Tests/IdempotenceTests.cs @@ -65,23 +65,26 @@ public void LoadSaveLoad(string filename) Assert.That(JsonSerializer.Serialize((object)actual.Object), Is.EqualTo(JsonSerializer.Serialize((object)expected.Object))); Assert.That(JsonSerializer.Serialize(actual.StringTable), Is.EqualTo(JsonSerializer.Serialize(expected.StringTable))); - var pm = new PaletteMap("C:\\Users\\bigba\\source\\repos\\OpenLoco\\ObjectEditor\\Gui\\Assets\\palette.png"); - var i = 0; - foreach (var ae in actual.GraphicsElements.Zip(expected.GraphicsElements)) + if (actual.ImageTable != null && expected.ImageTable != null) { - var ac = JsonSerializer.Serialize(ae.First); - var ex = JsonSerializer.Serialize(ae.Second); - - if (ac != ex) + var pm = new PaletteMap("C:\\Users\\bigba\\source\\repos\\OpenLoco\\ObjectEditor\\Gui\\Assets\\palette.png"); + var i = 0; + foreach (var ae in actual.ImageTable.GraphicsElements.Zip(expected.ImageTable.GraphicsElements)) { - _ = pm.TryConvertG1ToRgba32Bitmap(ae.First, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var img1); - _ = pm.TryConvertG1ToRgba32Bitmap(ae.Second, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var img2); - img1.SaveAsBmp($"{Path.GetFileName(filename)}-{i}-actual.bmp"); - img2.SaveAsBmp($"{Path.GetFileName(filename)}-{i}-expected.bmp"); + var ac = JsonSerializer.Serialize(ae.First); + var ex = JsonSerializer.Serialize(ae.Second); + + if (ac != ex) + { + _ = pm.TryConvertG1ToRgba32Bitmap(ae.First, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var img1); + _ = pm.TryConvertG1ToRgba32Bitmap(ae.Second, ColourRemapSwatch.PrimaryRemap, ColourRemapSwatch.SecondaryRemap, out var img2); + img1.SaveAsBmp($"{Path.GetFileName(filename)}-{i}-actual.bmp"); + img2.SaveAsBmp($"{Path.GetFileName(filename)}-{i}-expected.bmp"); + } + + Assert.That(ac, Is.EqualTo(ex)); + i++; } - - Assert.That(ac, Is.EqualTo(ex)); - i++; } } } diff --git a/Tests/ImagePaletteConversionTests.cs b/Tests/ImagePaletteConversionTests.cs index 630535a2..e2719cce 100644 --- a/Tests/ImagePaletteConversionTests.cs +++ b/Tests/ImagePaletteConversionTests.cs @@ -55,7 +55,7 @@ public void G1ElementToPNGAndBack(string objectSource) var paletteFile = Path.Combine(BasePalettePath, PaletteFileName); var paletteMap = new PaletteMap(paletteFile); var obj = SawyerStreamReader.LoadFullObject(Path.Combine(BaseObjDataPath, objectSource), Logger); - var g1Elements = obj!.LocoObject!.GraphicsElements; + var g1Elements = obj!.LocoObject!.ImageTable.GraphicsElements; using (Assert.EnterMultipleScope()) { diff --git a/Tests/LoadSaveTests.cs b/Tests/LoadSaveTests.cs index 2ddd8fe8..13cedb57 100644 --- a/Tests/LoadSaveTests.cs +++ b/Tests/LoadSaveTests.cs @@ -78,12 +78,12 @@ static void LoadSaveGenericTest(string filename, Action assert var bytes2 = SawyerStreamWriter.WriteLocoObject(datInfo2.S5Header.Name, obj2.ObjectType, datInfo2.S5Header.ObjectSource.Convert(), datInfo2.ObjectHeader.Encoding, logger, obj2, true).ToArray(); // grab headers first - var s5Header1 = S5Header.Read(bytes1[0..S5Header.StructLength]); - var s5Header2 = S5Header.Read(bytes2[0..S5Header.StructLength]); + var s5Header1 = S5Header.Read(bytes1.AsSpan()[0..S5Header.StructLength]); + var s5Header2 = S5Header.Read(bytes2.AsSpan()[0..S5Header.StructLength]); AssertS5Headers(s5Header1, s5Header2); - var objHeader1 = ObjectHeader.Read(bytes1[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]); - var objHeader2 = ObjectHeader.Read(bytes2[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]); + var objHeader1 = ObjectHeader.Read(bytes1.AsSpan()[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]); + var objHeader2 = ObjectHeader.Read(bytes2.AsSpan()[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]); AssertObjHeaders(objHeader1, objHeader2); // then grab object bytes @@ -118,9 +118,9 @@ void assertFunc(LocoObject obj, AirportObject struc) => Assert.Multiple(() => // Assert.That(struc.Image, Is.Zero, nameof(struc.Image)); //Assert.That(struc.ImageOffset, Is.Zero, nameof(struc.ImageOffset)); Assert.That(struc.AllowedPlaneTypes, Is.EqualTo(24), nameof(struc.AllowedPlaneTypes)); - Assert.That(struc.BuildingHeights.Count, Is.EqualTo(94), nameof(struc.BuildingHeights)); - Assert.That(struc.BuildingAnimations.Count, Is.EqualTo(94), nameof(struc.BuildingAnimations)); - Assert.That(struc.BuildingVariations.Count, Is.EqualTo(23), nameof(struc.BuildingVariations)); + Assert.That(struc.BuildingComponents.BuildingHeights, Has.Count.EqualTo(94), nameof(struc.BuildingComponents.BuildingHeights)); + Assert.That(struc.BuildingComponents.BuildingAnimations, Has.Count.EqualTo(94), nameof(struc.BuildingComponents.BuildingAnimations)); + Assert.That(struc.BuildingComponents.BuildingVariations, Has.Count.EqualTo(23), nameof(struc.BuildingComponents.BuildingVariations)); //Assert.That(struc.var_14, Is.EqualTo(0), nameof(struc.var_14)); //Assert.That(struc.var_18, Is.EqualTo(0), nameof(struc.var_18)); @@ -134,8 +134,8 @@ void assertFunc(LocoObject obj, AirportObject struc) => Assert.Multiple(() => Assert.That(struc.MaxY, Is.EqualTo(5), nameof(struc.MaxY)); Assert.That(struc.DesignedYear, Is.EqualTo(1970), nameof(struc.DesignedYear)); Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); - Assert.That(struc.MovementNodes.Count, Is.EqualTo(26), nameof(struc.MovementNodes)); - Assert.That(struc.MovementEdges.Count, Is.EqualTo(30), nameof(struc.MovementEdges)); + Assert.That(struc.MovementNodes, Has.Count.EqualTo(26), nameof(struc.MovementNodes)); + Assert.That(struc.MovementEdges, Has.Count.EqualTo(30), nameof(struc.MovementEdges)); //Assert.That(struc.MovementNodes, Is.EqualTo(0), nameof(struc.MovementNodes)); //Assert.That(struc.MovementEdges, Is.EqualTo(0), nameof(struc.MovementEdges)); @@ -145,7 +145,7 @@ void assertFunc(LocoObject obj, AirportObject struc) => Assert.Multiple(() => Assert.That(struc.var_B6[2], Is.Zero, nameof(struc.var_B6) + "[2]"); Assert.That(struc.var_B6[3], Is.Zero, nameof(struc.var_B6) + "[3]"); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(377)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(377)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -185,7 +185,7 @@ void assertFunc(LocoObject obj, BridgeObject struc) => Assert.Multiple(() => //CollectionAssert.AreEqual(struc.RoadMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.RoadMods)); Assert.That(struc.DesignedYear, Is.Zero, nameof(struc.DesignedYear)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(124)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(124)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -195,7 +195,7 @@ public void BuildingObject(string objectName) { void assertFunc(LocoObject obj, BuildingObject struc) => Assert.Multiple(() => { - Assert.That(struc.BuildingVariations.Count, Is.EqualTo(5), nameof(struc.BuildingVariations)); + Assert.That(struc.BuildingComponents.BuildingVariations, Has.Count.EqualTo(5), nameof(struc.BuildingComponents.BuildingVariations)); // CollectionAssert.AreEqual(struc.VariationHeights, Array.CreateInstance(typeof(byte), 4), nameof(struc.VariationHeights)); // VariationHeights // VariationAnimations @@ -218,13 +218,13 @@ void assertFunc(LocoObject obj, BuildingObject struc) => Assert.Multiple(() => // CollectionAssert.AreEqual(struc.var_A4, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A4)); Assert.That(struc.DemolishRatingReduction, Is.Zero, nameof(struc.DemolishRatingReduction)); Assert.That(struc.var_AC, Is.EqualTo(255), nameof(struc.var_AC)); - Assert.That(struc.ElevatorHeightSequences.Count, Is.Zero, nameof(struc.ElevatorHeightSequences)); + Assert.That(struc.ElevatorHeightSequences, Is.Empty, nameof(struc.ElevatorHeightSequences)); //Assert.That(struc.ElevatorHeightSequences[0].Count, Is.Zero, nameof(struc.ElevatorHeightSequences) + "[0]"); //Assert.That(struc.ElevatorHeightSequences[1].Count, Is.Zero, nameof(struc.ElevatorHeightSequences) + "[1]"); //Assert.That(struc.ElevatorHeightSequences[2].Count, Is.Zero, nameof(struc.ElevatorHeightSequences) + "[2]"); //Assert.That(struc.ElevatorHeightSequences[3].Count, Is.Zero, nameof(struc.ElevatorHeightSequences) + "[3]"); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(64)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(64)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -248,7 +248,8 @@ void assertFunc(LocoObject obj, CargoObject struc) => Assert.Multiple(() => Assert.That(struc.PaymentFactor, Is.EqualTo(62), nameof(struc.PaymentFactor)); Assert.That(struc.PaymentIndex, Is.EqualTo(10), nameof(struc.PaymentIndex)); Assert.That(struc.UnitSize, Is.EqualTo(10), nameof(struc.UnitSize)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(9)); + + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(9)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -268,7 +269,7 @@ void assertFunc(LocoObject obj, CliffEdgeObject _) => Assert.Multiple(() => Assert.That(entry[LanguageId.English_UK], Is.EqualTo("Brown Rock")); Assert.That(entry[LanguageId.English_US], Is.EqualTo("Brown Rock")); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(70)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(70)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -286,7 +287,7 @@ void assertFunc(LocoObject obj, ClimateObject struc) => Assert.Multiple(() => Assert.That(struc.WinterSnowLine, Is.EqualTo(48), nameof(struc.WinterSnowLine)); Assert.That(struc.SummerSnowLine, Is.EqualTo(76), nameof(struc.SummerSnowLine)); - Assert.That(obj.GraphicsElements, Is.Empty); + Assert.That(obj.ImageTable, Is.Null); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -330,7 +331,7 @@ void assertFunc(LocoObject obj, CompetitorObject struc) => Assert.Multiple(() => Assert.That(struc.Competitiveness, Is.EqualTo(6), nameof(struc.Competitiveness)); Assert.That(struc.var_37, Is.Zero, nameof(struc.var_37)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(18)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(18)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -344,7 +345,7 @@ void assertFunc(LocoObject obj, CurrencyObject struc) => Assert.Multiple(() => Assert.That(struc.Separator, Is.Zero, nameof(struc.Separator)); Assert.That(struc.Factor, Is.EqualTo(1), nameof(struc.Factor)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(5)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(5)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -360,8 +361,8 @@ void assertFunc(LocoObject obj, DockObject struc) => Assert.Multiple(() => Assert.That(struc.var_07, Is.Zero, nameof(struc.var_07)); //Assert.That(struc.UnkImage, Is.Zero, nameof(struc.UnkImage)); Assert.That(struc.Flags, Is.EqualTo(DockObjectFlags.None), nameof(struc.Flags)); - Assert.That(struc.BuildingAnimations.Count, Is.EqualTo(2), nameof(struc.BuildingAnimations)); - Assert.That(struc.BuildingVariations.Count, Is.EqualTo(1), nameof(struc.BuildingVariations)); + Assert.That(struc.BuildingComponents.BuildingAnimations, Has.Count.EqualTo(2), nameof(struc.BuildingComponents.BuildingAnimations)); + Assert.That(struc.BuildingComponents.BuildingVariations, Has.Count.EqualTo(1), nameof(struc.BuildingComponents.BuildingVariations)); //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_14)); //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_18)); @@ -372,7 +373,7 @@ void assertFunc(LocoObject obj, DockObject struc) => Assert.Multiple(() => Assert.That(struc.BoatPosition.X, Is.EqualTo(48), nameof(struc.BoatPosition)); Assert.That(struc.BoatPosition.Y, Is.EqualTo(0), nameof(struc.BoatPosition)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(9)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(9)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -385,9 +386,9 @@ void assertFunc(LocoObject obj, HillShapesObject struc) => Assert.Multiple(() => Assert.That(struc.HillHeightMapCount, Is.EqualTo(2), nameof(struc.HillHeightMapCount)); Assert.That(struc.MountainHeightMapCount, Is.EqualTo(2), nameof(struc.MountainHeightMapCount)); //Assert.That(struc.var_08, Is.EqualTo(0), nameof(struc.var_08)); - Assert.That(struc.IsHeightMap, Is.EqualTo(false), nameof(struc.IsHeightMap)); + Assert.That(struc.IsHeightMap, Is.False, nameof(struc.IsHeightMap)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(5)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(5)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -402,46 +403,46 @@ void assertFunc(LocoObject obj, IndustryObject struc) => Assert.Multiple(() => // Buildings Assert.That(struc.BuildingSizeFlags, Is.EqualTo(7), nameof(struc.BuildingSizeFlags)); // BuildingPartHeights - Assert.That(struc.BuildingHeights, Is.EqualTo(new List() { 0, 56, 0, 66, 0, 122, 0, 48, 0, 36 })); + Assert.That(struc.BuildingComponents.BuildingHeights, Is.EqualTo(new List() { 0, 56, 0, 66, 0, 122, 0, 48, 0, 36 })); // BuildingPartAnimations - Assert.That(struc.BuildingAnimations, Has.Count.EqualTo(10)); - Assert.That(struc.BuildingAnimations[0].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[0].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[1].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[1].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[2].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[2].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[3].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[3].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[4].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[4].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[5].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[5].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[6].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[6].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[7].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[7].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[8].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[8].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[9].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[9].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations, Has.Count.EqualTo(10)); + Assert.That(struc.BuildingComponents.BuildingAnimations[0].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[0].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[1].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[1].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[2].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[2].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[3].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[3].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[4].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[4].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[5].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[5].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[6].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[6].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[7].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[7].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[8].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[8].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[9].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[9].AnimationSpeed, Is.Zero); // BuildingParts - Assert.That(struc.BuildingVariations, Has.Count.EqualTo(5)); - Assert.That(struc.BuildingVariations[0], Has.Count.EqualTo(2)); - Assert.That(struc.BuildingVariations[0][0], Is.Zero); - Assert.That(struc.BuildingVariations[0][1], Is.EqualTo(1)); - Assert.That(struc.BuildingVariations[1], Has.Count.EqualTo(2)); - Assert.That(struc.BuildingVariations[1][0], Is.EqualTo(2)); - Assert.That(struc.BuildingVariations[1][1], Is.EqualTo(3)); - Assert.That(struc.BuildingVariations[2], Has.Count.EqualTo(2)); - Assert.That(struc.BuildingVariations[2][0], Is.EqualTo(4)); - Assert.That(struc.BuildingVariations[2][1], Is.EqualTo(5)); - Assert.That(struc.BuildingVariations[3], Has.Count.EqualTo(2)); - Assert.That(struc.BuildingVariations[3][0], Is.EqualTo(6)); - Assert.That(struc.BuildingVariations[3][1], Is.EqualTo(7)); - Assert.That(struc.BuildingVariations[4], Has.Count.EqualTo(2)); - Assert.That(struc.BuildingVariations[4][0], Is.EqualTo(8)); - Assert.That(struc.BuildingVariations[4][1], Is.EqualTo(9)); + Assert.That(struc.BuildingComponents.BuildingVariations, Has.Count.EqualTo(5)); + Assert.That(struc.BuildingComponents.BuildingVariations[0], Has.Count.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[0][0], Is.Zero); + Assert.That(struc.BuildingComponents.BuildingVariations[0][1], Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingVariations[1], Has.Count.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[1][0], Is.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[1][1], Is.EqualTo(3)); + Assert.That(struc.BuildingComponents.BuildingVariations[2], Has.Count.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[2][0], Is.EqualTo(4)); + Assert.That(struc.BuildingComponents.BuildingVariations[2][1], Is.EqualTo(5)); + Assert.That(struc.BuildingComponents.BuildingVariations[3], Has.Count.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[3][0], Is.EqualTo(6)); + Assert.That(struc.BuildingComponents.BuildingVariations[3][1], Is.EqualTo(7)); + Assert.That(struc.BuildingComponents.BuildingVariations[4], Has.Count.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[4][0], Is.EqualTo(8)); + Assert.That(struc.BuildingComponents.BuildingVariations[4][1], Is.EqualTo(9)); // Rest of object Assert.That(struc.SellCostFactor, Is.EqualTo(240), nameof(struc.SellCostFactor)); Assert.That(struc.BuildCostFactor, Is.EqualTo(400), nameof(struc.BuildCostFactor)); @@ -454,9 +455,9 @@ void assertFunc(LocoObject obj, IndustryObject struc) => Assert.Multiple(() => Assert.That(struc.InitialProductionRate[1].Max, Is.Zero); Assert.That(struc.MaxNumBuildings, Is.EqualTo(11), nameof(struc.MaxNumBuildings)); Assert.That(struc.MinNumBuildings, Is.EqualTo(9), nameof(struc.MinNumBuildings)); - Assert.That(struc.BuildingHeights.Count, Is.EqualTo(10), nameof(struc.BuildingHeights)); - Assert.That(struc.BuildingAnimations.Count, Is.EqualTo(10), nameof(struc.BuildingAnimations)); - Assert.That(struc.BuildingVariations.Count, Is.EqualTo(5), nameof(struc.BuildingVariations)); + Assert.That(struc.BuildingComponents.BuildingHeights, Has.Count.EqualTo(10), nameof(struc.BuildingComponents.BuildingHeights)); + Assert.That(struc.BuildingComponents.BuildingAnimations, Has.Count.EqualTo(10), nameof(struc.BuildingComponents.BuildingAnimations)); + Assert.That(struc.BuildingComponents.BuildingVariations, Has.Count.EqualTo(5), nameof(struc.BuildingComponents.BuildingVariations)); Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); Assert.That(struc.MapColour, Is.EqualTo(Colour.Yellow), nameof(struc.MapColour)); // ProducedCargo @@ -487,7 +488,7 @@ void assertFunc(LocoObject obj, IndustryObject struc) => Assert.Multiple(() => Assert.That(struc.BuildingWallEntrance!.Name, Is.EqualTo("SECFENCG")); Assert.That(struc.BuildingWallEntrance.ObjectType, Is.EqualTo(ObjectType.Wall)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(61)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(61)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -504,25 +505,25 @@ void assertFunc(LocoObject obj, IndustryObject struc) => Assert.Multiple(() => //Assert.That(struc._BuildingWall, Is.Zero, nameof(struc._BuildingWall)); //Assert.That(struc._BuildingWallEntrance, Is.Zero, nameof(struc._BuildingWallEntrance)); // BuildingPartHeights - Assert.That(struc.BuildingHeights, Is.EqualTo(new List() { 0, 166, 0, 64, })); + Assert.That(struc.BuildingComponents.BuildingHeights, Is.EqualTo(new List() { 0, 166, 0, 64, })); // BuildingPartAnimations - Assert.That(struc.BuildingAnimations, Has.Count.EqualTo(4)); - Assert.That(struc.BuildingAnimations[0].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[0].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[1].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[1].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[2].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[2].AnimationSpeed, Is.Zero); - Assert.That(struc.BuildingAnimations[3].NumFrames, Is.EqualTo(1)); - Assert.That(struc.BuildingAnimations[3].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations, Has.Count.EqualTo(4)); + Assert.That(struc.BuildingComponents.BuildingAnimations[0].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[0].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[1].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[1].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[2].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[2].AnimationSpeed, Is.Zero); + Assert.That(struc.BuildingComponents.BuildingAnimations[3].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingAnimations[3].AnimationSpeed, Is.Zero); // BuildingParts - Assert.That(struc.BuildingVariations, Has.Count.EqualTo(2)); - Assert.That(struc.BuildingVariations[0], Has.Count.EqualTo(2)); - Assert.That(struc.BuildingVariations[0][0], Is.Zero); - Assert.That(struc.BuildingVariations[0][1], Is.EqualTo(1)); - Assert.That(struc.BuildingVariations[1], Has.Count.EqualTo(2)); - Assert.That(struc.BuildingVariations[1][0], Is.EqualTo(2)); - Assert.That(struc.BuildingVariations[1][1], Is.EqualTo(3)); + Assert.That(struc.BuildingComponents.BuildingVariations, Has.Count.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[0], Has.Count.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[0][0], Is.Zero); + Assert.That(struc.BuildingComponents.BuildingVariations[0][1], Is.EqualTo(1)); + Assert.That(struc.BuildingComponents.BuildingVariations[1], Has.Count.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[1][0], Is.EqualTo(2)); + Assert.That(struc.BuildingComponents.BuildingVariations[1][1], Is.EqualTo(3)); // Rest of object Assert.That(struc.SellCostFactor, Is.EqualTo(240), nameof(struc.SellCostFactor)); @@ -536,9 +537,9 @@ void assertFunc(LocoObject obj, IndustryObject struc) => Assert.Multiple(() => Assert.That(struc.InitialProductionRate[1].Max, Is.Zero); Assert.That(struc.MaxNumBuildings, Is.EqualTo(8), nameof(struc.MaxNumBuildings)); Assert.That(struc.MinNumBuildings, Is.EqualTo(4), nameof(struc.MinNumBuildings)); - Assert.That(struc.BuildingHeights.Count, Is.EqualTo(4), nameof(struc.BuildingHeights)); - Assert.That(struc.BuildingAnimations.Count, Is.EqualTo(4), nameof(struc.BuildingAnimations)); - Assert.That(struc.BuildingVariations.Count, Is.EqualTo(2), nameof(struc.BuildingVariations)); + Assert.That(struc.BuildingComponents.BuildingHeights, Has.Count.EqualTo(4), nameof(struc.BuildingComponents.BuildingHeights)); + Assert.That(struc.BuildingComponents.BuildingAnimations, Has.Count.EqualTo(4), nameof(struc.BuildingComponents.BuildingAnimations)); + Assert.That(struc.BuildingComponents.BuildingVariations, Has.Count.EqualTo(2), nameof(struc.BuildingComponents.BuildingVariations)); Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); Assert.That(struc.MapColour, Is.EqualTo(Colour.MutedPurple), nameof(struc.MapColour)); // ProducedCargo @@ -569,7 +570,7 @@ void assertFunc(LocoObject obj, IndustryObject struc) => Assert.Multiple(() => Assert.That(struc.BuildingWall, Is.Null); Assert.That(struc.BuildingWallEntrance, Is.Null); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(37)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(37)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -598,7 +599,7 @@ void assertFunc(LocoObject obj, InterfaceSkinObject struc) => Assert.Multiple(() Assert.That(struc.PlayerInfoToolbarColour, Is.EqualTo(Colour.Grey), nameof(struc.PlayerInfoToolbarColour)); Assert.That(struc.TimeToolbarColour, Is.EqualTo(Colour.Grey), nameof(struc.TimeToolbarColour)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(470)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(470)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -619,13 +620,13 @@ void assertFunc(LocoObject obj, LandObject struc) => Assert.Multiple(() => Assert.That(struc.VariationLikelihood, Is.EqualTo(10), nameof(struc.VariationLikelihood)); Assert.That(struc.CliffEdgeHeader.Name, Is.EqualTo("LSBROWN"), nameof(struc.CliffEdgeHeader)); - Assert.That(struc.CliffEdgeHeader.Checksum, Is.EqualTo(0), nameof(struc.CliffEdgeHeader)); + Assert.That(struc.CliffEdgeHeader.Checksum, Is.Zero, nameof(struc.CliffEdgeHeader)); Assert.That(struc.CliffEdgeHeader.ObjectType, Is.EqualTo(ObjectType.CliffEdge), nameof(struc.CliffEdgeHeader)); - Assert.That(struc.CliffEdgeHeader.ObjectSource, Is.EqualTo((ObjectSource)0), nameof(struc.CliffEdgeHeader)); + Assert.That(struc.CliffEdgeHeader.ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.CliffEdgeHeader)); Assert.That(struc.UnkObjectHeader, Is.Null, nameof(struc.UnkObjectHeader)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(417)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(417)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -648,7 +649,7 @@ void assertFunc(LocoObject obj, LevelCrossingObject struc) => Assert.Multiple(() Assert.That(struc.DesignedYear, Is.EqualTo(1955), nameof(struc.DesignedYear)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(128)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(128)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -659,11 +660,11 @@ public void RegionObject(string objectName) void assertFunc(LocoObject obj, RegionObject struc) => Assert.Multiple(() => { Assert.That(struc.VehiclesDriveOnThe, Is.EqualTo(DrivingSide.Left), nameof(struc.VehiclesDriveOnThe)); - Assert.That(struc.CargoInfluenceObjects.Count, Is.EqualTo(1), nameof(struc.CargoInfluenceObjects)); + Assert.That(struc.CargoInfluenceObjects, Has.Count.EqualTo(1), nameof(struc.CargoInfluenceObjects)); Assert.That(struc.DependentObjects, Has.Count.EqualTo(239), nameof(struc.DependentObjects)); Assert.That(struc.CargoInfluenceTownFilter, Is.EquivalentTo(Enumerable.Repeat(CargoInfluenceTownFilterType.AllTowns, 4)), nameof(struc.CargoInfluenceTownFilter)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(1)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(1)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -680,7 +681,7 @@ void assertFunc(LocoObject obj, RoadExtraObject struc) => Assert.Multiple(() => Assert.That(struc.SellCostFactor, Is.EqualTo(-3), nameof(struc.SellCostFactor)); //Assert.That(struc.BaseImageOffset, Is.Zero, nameof(struc.BaseImageOffset)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(46)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(46)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -715,48 +716,48 @@ void assertFunc(LocoObject obj, RoadObject struc) => Assert.Multiple(() => | RoadTraitFlags.unk_04 | RoadTraitFlags.unk_06), nameof(struc.RoadPieces)); - Assert.That(struc.Bridges.Count, Is.EqualTo(5), nameof(struc.Bridges)); + Assert.That(struc.Bridges, Has.Count.EqualTo(5), nameof(struc.Bridges)); Assert.That(struc.Bridges[0].Name, Is.EqualTo("BRDGBRCK"), nameof(struc.Bridges)); - Assert.That(struc.Bridges[0].Checksum, Is.EqualTo(0), nameof(struc.Bridges)); + Assert.That(struc.Bridges[0].Checksum, Is.Zero, nameof(struc.Bridges)); Assert.That(struc.Bridges[0].ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.Bridges)); Assert.That(struc.Bridges[0].ObjectType, Is.EqualTo(ObjectType.Bridge), nameof(struc.Bridges)); Assert.That(struc.Bridges[1].Name, Is.EqualTo("BRDGSTAR"), nameof(struc.Bridges)); - Assert.That(struc.Bridges[1].Checksum, Is.EqualTo(0), nameof(struc.Bridges)); + Assert.That(struc.Bridges[1].Checksum, Is.Zero, nameof(struc.Bridges)); Assert.That(struc.Bridges[1].ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.Bridges)); Assert.That(struc.Bridges[1].ObjectType, Is.EqualTo(ObjectType.Bridge), nameof(struc.Bridges)); Assert.That(struc.Bridges[2].Name, Is.EqualTo("BRDGGIRD"), nameof(struc.Bridges)); - Assert.That(struc.Bridges[2].Checksum, Is.EqualTo(0), nameof(struc.Bridges)); + Assert.That(struc.Bridges[2].Checksum, Is.Zero, nameof(struc.Bridges)); Assert.That(struc.Bridges[2].ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.Bridges)); Assert.That(struc.Bridges[2].ObjectType, Is.EqualTo(ObjectType.Bridge), nameof(struc.Bridges)); Assert.That(struc.Bridges[3].Name, Is.EqualTo("BRDGSUSP"), nameof(struc.Bridges)); - Assert.That(struc.Bridges[3].Checksum, Is.EqualTo(0), nameof(struc.Bridges)); + Assert.That(struc.Bridges[3].Checksum, Is.Zero, nameof(struc.Bridges)); Assert.That(struc.Bridges[3].ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.Bridges)); Assert.That(struc.Bridges[3].ObjectType, Is.EqualTo(ObjectType.Bridge), nameof(struc.Bridges)); Assert.That(struc.Bridges[4].Name, Is.EqualTo("BRDGWOOD"), nameof(struc.Bridges)); - Assert.That(struc.Bridges[4].Checksum, Is.EqualTo(0), nameof(struc.Bridges)); + Assert.That(struc.Bridges[4].Checksum, Is.Zero, nameof(struc.Bridges)); Assert.That(struc.Bridges[4].ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.Bridges)); Assert.That(struc.Bridges[4].ObjectType, Is.EqualTo(ObjectType.Bridge), nameof(struc.Bridges)); - Assert.That(struc.CompatibleTracksAndRoads.Count, Is.EqualTo(1), nameof(struc.CompatibleTracksAndRoads)); + Assert.That(struc.CompatibleTracksAndRoads, Has.Count.EqualTo(1), nameof(struc.CompatibleTracksAndRoads)); Assert.That(struc.CompatibleTracksAndRoads[0].Name, Is.EqualTo("ROADTRAM"), nameof(struc.CompatibleTracksAndRoads)); - Assert.That(struc.CompatibleTracksAndRoads[0].Checksum, Is.EqualTo(0), nameof(struc.CompatibleTracksAndRoads)); + Assert.That(struc.CompatibleTracksAndRoads[0].Checksum, Is.Zero, nameof(struc.CompatibleTracksAndRoads)); Assert.That(struc.CompatibleTracksAndRoads[0].ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.CompatibleTracksAndRoads)); Assert.That(struc.CompatibleTracksAndRoads[0].ObjectType, Is.EqualTo(ObjectType.Road), nameof(struc.CompatibleTracksAndRoads)); - Assert.That(struc.RoadMods.Count, Is.Zero, nameof(struc.RoadMods)); + Assert.That(struc.RoadMods, Is.Empty, nameof(struc.RoadMods)); - Assert.That(struc.Stations.Count, Is.EqualTo(1), nameof(struc.Stations)); + Assert.That(struc.Stations, Has.Count.EqualTo(1), nameof(struc.Stations)); Assert.That(struc.Stations[0].Name, Is.EqualTo("BUSSTOP"), nameof(struc.Stations)); - Assert.That(struc.Stations[0].Checksum, Is.EqualTo(0), nameof(struc.Stations)); + Assert.That(struc.Stations[0].Checksum, Is.Zero, nameof(struc.Stations)); Assert.That(struc.Stations[0].ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.Stations)); Assert.That(struc.Stations[0].ObjectType, Is.EqualTo(ObjectType.RoadStation), nameof(struc.Stations)); Assert.That(struc.Tunnel.Name, Is.EqualTo("TUNNEL2"), nameof(struc.Tunnel)); - Assert.That(struc.Tunnel.Checksum, Is.EqualTo(0), nameof(struc.Tunnel.Checksum)); + Assert.That(struc.Tunnel.Checksum, Is.Zero, nameof(struc.Tunnel.Checksum)); Assert.That(struc.Tunnel.ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.Tunnel.ObjectSource)); Assert.That(struc.Tunnel.ObjectType, Is.EqualTo(ObjectType.Tunnel), nameof(struc.Tunnel.ObjectType)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(73)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(73)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -773,13 +774,13 @@ void assertFunc(LocoObject obj, RoadStationObject struc) => Assert.Multiple(() = Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); Assert.That(struc.DesignedYear, Is.Zero, nameof(struc.DesignedYear)); Assert.That(struc.Flags, Is.EqualTo(RoadStationObjectFlags.Passenger | RoadStationObjectFlags.RoadEnd), nameof(struc.Flags)); - Assert.That(struc.CompatibleRoadObjects.Count, Is.Zero, nameof(struc.CompatibleRoadObjects)); + Assert.That(struc.CompatibleRoadObjects, Is.Empty, nameof(struc.CompatibleRoadObjects)); Assert.That(struc.ObsoleteYear, Is.EqualTo(1945), nameof(struc.ObsoleteYear)); Assert.That(struc.PaintStyle, Is.Zero, nameof(struc.PaintStyle)); Assert.That(struc.RoadPieces, Is.EqualTo(RoadTraitFlags.None), nameof(struc.RoadPieces)); Assert.That(struc.SellCostFactor, Is.EqualTo(-17), nameof(struc.SellCostFactor)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(18)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(18)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -797,7 +798,7 @@ void assertFunc(LocoObject obj, ScaffoldingObject struc) => Assert.Multiple(() = Assert.That(struc.RoofHeights[1], Is.Zero, nameof(struc.RoofHeights) + "[1]"); Assert.That(struc.RoofHeights[2], Is.EqualTo(14), nameof(struc.RoofHeights) + "[2]"); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(36)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(36)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -805,10 +806,8 @@ void assertFunc(LocoObject obj, ScaffoldingObject struc) => Assert.Multiple(() = [TestCase("STEX000.DAT")] public void ScenarioTextObject(string objectName) { - void assertFunc(LocoObject obj, ScenarioTextObject struc) => Assert.Multiple(() => - { - Assert.That(obj.GraphicsElements, Is.Empty); - }); + void assertFunc(LocoObject obj, ScenarioTextObject struc) + => Assert.Multiple(() => Assert.That(obj.ImageTable, Is.Null)); LoadSaveGenericTest(objectName, assertFunc); } @@ -818,9 +817,9 @@ public void SnowObject(string objectName) void assertFunc(LocoObject obj, SnowObject struc) => Assert.Multiple(() => { Assert.That(obj.StringTable.Table, Has.Count.EqualTo(1), nameof(obj.StringTable.Table)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(139), nameof(obj.GraphicsElements)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(139), nameof(obj.ImageTable.GraphicsElements)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(139)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(139)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -847,7 +846,7 @@ void assertFunc(LocoObject obj, SoundObject struc) => Assert.Multiple(() => Assert.That(struc.SoundObjectData.PcmHeader.SampleRate, Is.EqualTo(22050), nameof(struc.SoundObjectData.PcmHeader.SampleRate)); Assert.That(struc.SoundObjectData.PcmHeader.WaveFormatTag, Is.EqualTo(1), nameof(struc.SoundObjectData.PcmHeader.WaveFormatTag)); - Assert.That(obj.GraphicsElements, Is.Empty); + Assert.That(obj.ImageTable, Is.Null); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -861,20 +860,20 @@ void assertFunc(LocoObject obj, SteamObject struc) => Assert.Multiple(() => // FrameInfoType0 contents // FrameInfoType1 contents //Assert.That(struc.NumImages, Is.EqualTo(57), nameof(struc.NumImages)); - Assert.That(struc.SoundEffects.Count, Is.EqualTo(8), nameof(struc.SoundEffects)); + Assert.That(struc.SoundEffects, Has.Count.EqualTo(8), nameof(struc.SoundEffects)); Assert.That(struc.NumStationaryTicks, Is.EqualTo(2), nameof(struc.NumStationaryTicks)); // these aren't currently calculated in this tool - Assert.That(struc.SpriteWidth, Is.EqualTo(0), nameof(struc.SpriteWidth)); - Assert.That(struc.SpriteHeightNegative, Is.EqualTo(0), nameof(struc.SpriteHeightNegative)); - Assert.That(struc.SpriteHeightPositive, Is.EqualTo(0), nameof(struc.SpriteHeightPositive)); + Assert.That(struc.SpriteWidth, Is.Zero, nameof(struc.SpriteWidth)); + Assert.That(struc.SpriteHeightNegative, Is.Zero, nameof(struc.SpriteHeightNegative)); + Assert.That(struc.SpriteHeightPositive, Is.Zero, nameof(struc.SpriteHeightPositive)); - Assert.That(struc.FrameInfoType0.Count, Is.EqualTo(47), nameof(struc.FrameInfoType0)); - Assert.That(struc.FrameInfoType1.Count, Is.EqualTo(30), nameof(struc.FrameInfoType1)); + Assert.That(struc.FrameInfoType0, Has.Count.EqualTo(47), nameof(struc.FrameInfoType0)); + Assert.That(struc.FrameInfoType1, Has.Count.EqualTo(30), nameof(struc.FrameInfoType1)); Assert.That(struc.var_0A, Is.Zero, nameof(struc.var_0A)); // SoundEffects - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(57)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(57)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -891,7 +890,7 @@ void assertFunc(LocoObject obj, StreetLightObject struc) => Assert.Multiple(() = Assert.That(obj.StringTable["Name"][LanguageId.English_UK], Is.EqualTo("Street Lights")); Assert.That(obj.StringTable["Name"][LanguageId.English_US], Is.EqualTo("Street Lights")); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(12)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(12)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -928,7 +927,7 @@ void assertFunc(LocoObject obj, TownNamesObject struc) => Assert.Multiple(() => Assert.That(obj.StringTable["Name"][LanguageId.English_UK], Is.EqualTo("North-American style town names")); Assert.That(obj.StringTable["Name"][LanguageId.English_US], Is.EqualTo("North-American style town names")); - Assert.That(obj.GraphicsElements, Is.Empty); + Assert.That(obj.ImageTable, Is.Null); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -944,7 +943,7 @@ void assertFunc(LocoObject obj, TrackExtraObject struc) => Assert.Multiple(() => Assert.That(struc.BuildCostFactor, Is.EqualTo(2), nameof(struc.BuildCostFactor)); Assert.That(struc.SellCostFactor, Is.EqualTo(-1), nameof(struc.SellCostFactor)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(134)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(134)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -964,11 +963,11 @@ void assertFunc(LocoObject obj, TrackObject struc) => Assert.Multiple(() => Assert.That(struc.DisplayOffset, Is.EqualTo(3), nameof(struc.DisplayOffset)); Assert.That(struc.Flags, Is.EqualTo(TrackObjectFlags.unk_00), nameof(struc.Flags)); // Mods - Assert.That(struc.Bridges.Count, Is.EqualTo(5), nameof(struc.Bridges)); - Assert.That(struc.CompatibleTracksAndRoads.Count, Is.EqualTo(7), nameof(struc.CompatibleTracksAndRoads)); - Assert.That(struc.TrackMods.Count, Is.EqualTo(2), nameof(struc.TrackMods)); - Assert.That(struc.Signals.Count, Is.EqualTo(10), nameof(struc.Signals)); - Assert.That(struc.Stations.Count, Is.EqualTo(5), nameof(struc.Stations)); + Assert.That(struc.Bridges, Has.Count.EqualTo(5), nameof(struc.Bridges)); + Assert.That(struc.CompatibleTracksAndRoads, Has.Count.EqualTo(7), nameof(struc.CompatibleTracksAndRoads)); + Assert.That(struc.TrackMods, Has.Count.EqualTo(2), nameof(struc.TrackMods)); + Assert.That(struc.Signals, Has.Count.EqualTo(10), nameof(struc.Signals)); + Assert.That(struc.Stations, Has.Count.EqualTo(5), nameof(struc.Stations)); Assert.That(struc.SellCostFactor, Is.EqualTo(-10), nameof(struc.SellCostFactor)); // Signals // Stations @@ -978,10 +977,10 @@ void assertFunc(LocoObject obj, TrackObject struc) => Assert.Multiple(() => Assert.That(struc.var_06, Is.Zero, nameof(struc.var_06)); Assert.That(obj.StringTable.Table, Has.Count.EqualTo(1), nameof(obj.StringTable.Table)); - Assert.That(obj.GraphicsElements, Is.Not.Null); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(400), nameof(obj.GraphicsElements)); + Assert.That(obj.ImageTable.GraphicsElements, Is.Not.Null); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(400), nameof(obj.ImageTable.GraphicsElements)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(400)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(400)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -997,13 +996,13 @@ void assertFunc(LocoObject obj, TrackSignalObject struc) => Assert.Multiple(() = Assert.That(struc.DesignedYear, Is.Zero, nameof(struc.DesignedYear)); Assert.That(struc.Flags, Is.EqualTo(TrackSignalObjectFlags.IsLeft), nameof(struc.Flags)); // Mods - Assert.That(struc.CompatibleTrackObjects.Count, Is.Zero, nameof(struc.CompatibleTrackObjects)); + Assert.That(struc.CompatibleTrackObjects, Is.Empty, nameof(struc.CompatibleTrackObjects)); Assert.That(struc.NumFrames, Is.EqualTo(7), nameof(struc.NumFrames)); Assert.That(struc.ObsoleteYear, Is.EqualTo(1955), nameof(struc.ObsoleteYear)); Assert.That(struc.SellCostFactor, Is.EqualTo(-3), nameof(struc.SellCostFactor)); Assert.That(struc.var_0B, Is.Zero, nameof(struc.var_0B)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(56)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(56)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -1022,14 +1021,14 @@ void assertFunc(LocoObject obj, TrackStationObject struc) => Assert.Multiple(() Assert.That(struc.Flags, Is.EqualTo(TrackStationObjectFlags.None), nameof(struc.Flags)); // ManualPower Assert.That(struc.Height, Is.Zero, nameof(struc.Height)); - Assert.That(struc.CompatibleTrackObjects.Count, Is.Zero, nameof(struc.CompatibleTrackObjects)); + Assert.That(struc.CompatibleTrackObjects, Is.Empty, nameof(struc.CompatibleTrackObjects)); Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); Assert.That(struc.SellCostFactor, Is.EqualTo(-7), nameof(struc.SellCostFactor)); Assert.That(struc.TrackPieces, Is.EqualTo(TrackTraitFlags.None), nameof(struc.TrackPieces)); Assert.That(struc.var_0B, Is.EqualTo(2), nameof(struc.var_0B)); Assert.That(struc.var_0D, Is.Zero, nameof(struc.var_0D)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(36)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(36)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -1059,7 +1058,7 @@ void assertFunc(LocoObject obj, TreeObject struc) => Assert.Multiple(() => Assert.That(struc.Rating, Is.EqualTo(10), nameof(struc.Rating)); Assert.That(struc.DemolishRatingReduction, Is.EqualTo(-15), nameof(struc.DemolishRatingReduction)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(32)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(32)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -1067,10 +1066,7 @@ void assertFunc(LocoObject obj, TreeObject struc) => Assert.Multiple(() => [TestCase("TUNNEL1.DAT")] public void TunnelObject(string objectName) { - void assertFunc(LocoObject obj, TunnelObject struc) => Assert.Multiple(() => - { - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(4)); - }); + void assertFunc(LocoObject obj, TunnelObject struc) => Assert.Multiple(() => Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(4))); LoadSaveGenericTest(objectName, assertFunc); } @@ -1142,7 +1138,7 @@ void assertFunc(LocoObject obj, VehicleObject struc) => Assert.Multiple(() => Assert.That(struc.StartSounds[1].ObjectSource, Is.EqualTo(ObjectSource.Custom), nameof(struc.StartSounds) + "[1]Checksum"); Assert.That(struc.StartSounds[1].ObjectType, Is.EqualTo(ObjectType.Sound), nameof(struc.StartSounds) + "[1]Flags"); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(168)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(168)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -1157,7 +1153,7 @@ void assertFunc(LocoObject obj, WallObject struc) => Assert.Multiple(() => Assert.That(struc.Height, Is.EqualTo(2), nameof(struc.Height)); Assert.That(struc.Flags2, Is.EqualTo(WallObjectFlags2.Opaque), nameof(struc.Flags2)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(6)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(6)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -1172,7 +1168,7 @@ void assertFunc(LocoObject obj, WaterObject struc) => Assert.Multiple(() => Assert.That(struc.CostFactor, Is.EqualTo(51), nameof(struc.CostFactor)); //Assert.That(struc.var_0A, Is.EqualTo(0), nameof(struc.var_0A)); - Assert.That(obj.GraphicsElements, Has.Count.EqualTo(76)); + Assert.That(obj.ImageTable.GraphicsElements, Has.Count.EqualTo(76)); }); LoadSaveGenericTest(objectName, assertFunc); } diff --git a/Tests/Models/ImageTableModelTests.cs b/Tests/Models/ImageTableModelTests.cs index f94892ed..7737a13f 100644 --- a/Tests/Models/ImageTableModelTests.cs +++ b/Tests/Models/ImageTableModelTests.cs @@ -1,5 +1,5 @@ +using Gui.ViewModels.Graphics; using NUnit.Framework; -using Gui.ViewModels; namespace Models.Tests;