diff --git a/Core/DatFileParsing/SawyerStreamReader.cs b/Core/DatFileParsing/SawyerStreamReader.cs index 9bc29b5e..3f4e7b2a 100644 --- a/Core/DatFileParsing/SawyerStreamReader.cs +++ b/Core/DatFileParsing/SawyerStreamReader.cs @@ -12,15 +12,18 @@ namespace OpenLoco.ObjectEditor.DatFileParsing { public static class SawyerStreamReader { - public static List LoadVariableCountS5Headers(ReadOnlySpan data, int count) + public static List LoadVariableCountS5Headers(ReadOnlySpan data, int max) { List result = []; - for (var i = 0; i < count; ++i) + for (var i = 0; i < max; ++i) { - var header = S5Header.Read(data[..S5Header.StructLength]); - if (header.Checksum != 0 || header.Flags != 255) + if (data[0] != 0xFF) { - result.Add(header); + var header = S5Header.Read(data[..S5Header.StructLength]); + if (header.Checksum != 0 || header.Flags != 255) + { + result.Add(header); + } } data = data[S5Header.StructLength..]; @@ -148,12 +151,33 @@ public static (DatFileInfo DatFileInfo, ILocoObject? LocoObject) LoadFullObjectF locoStructPostLoad.PostLoad(); } + ValidateLocoStruct(s5Header, locoStruct, logger); + // add to object manager SObjectManager.Add(newObj); return new(new DatFileInfo(s5Header, objectHeader), newObj); } + static void ValidateLocoStruct(S5Header s5Header, ILocoStruct locoStruct, ILogger? logger) + { + try + { + if (!locoStruct.Validate()) + { + logger?.Warning($"\"{s5Header.Name}\" failed validation"); + } + else + { + logger?.Info($"\"{s5Header.Name}\" validated successfully"); + } + } + catch (NotImplementedException) + { + logger?.Debug2($"{s5Header.ObjectType} object type is missing validation function"); + } + } + static string CStringToString(ReadOnlySpan data, Encoding enc) { var ptr = 0; diff --git a/Core/Objects/Airport/AirportObject.cs b/Core/Objects/Airport/AirportObject.cs index ffb8af5f..8e828285 100644 --- a/Core/Objects/Airport/AirportObject.cs +++ b/Core/Objects/Airport/AirportObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -164,5 +164,20 @@ public ReadOnlySpan Save() return ms.ToArray(); } + + public bool Validate() + { + if (CostIndex > 32) + { + return false; + } + + if (-SellCostFactor > BuildCostFactor) + { + return false; + } + + return BuildCostFactor > 0; + } } } diff --git a/Core/Objects/Airport/MovementEdge.cs b/Core/Objects/Airport/MovementEdge.cs index f03269e6..487e5ec0 100644 --- a/Core/Objects/Airport/MovementEdge.cs +++ b/Core/Objects/Airport/MovementEdge.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace Core.Objects @@ -12,5 +12,8 @@ public record MovementEdge( [property: LocoStructOffset(0x03)] uint8_t var_03, [property: LocoStructOffset(0x04)] uint32_t MustBeClearEdges, // Which edges must be clear to use the transition edge. should probably be some kind of flags? [property: LocoStructOffset(0x08)] uint32_t AtLeastOneClearEdges // Which edges must have at least one clear to use transition edge. should probably be some kind of flags? - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Airport/MovementNode.cs b/Core/Objects/Airport/MovementNode.cs index 7bd02d4e..dd23dba9 100644 --- a/Core/Objects/Airport/MovementNode.cs +++ b/Core/Objects/Airport/MovementNode.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace Core.Objects @@ -10,5 +10,8 @@ public record MovementNode( [property: LocoStructOffset(0x02)] int16_t Y, [property: LocoStructOffset(0x04)] int16_t Z, [property: LocoStructOffset(0x06)] AirportMovementNodeFlags Flags - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/BridgeObject.cs b/Core/Objects/BridgeObject.cs index 2be3f8f9..09edf692 100644 --- a/Core/Objects/BridgeObject.cs +++ b/Core/Objects/BridgeObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Headers; @@ -61,5 +61,39 @@ public ReadOnlySpan Save() return headers.SelectMany(h => h.Write().ToArray()).ToArray(); } + + public bool Validate() + { + if (CostIndex > 32) + { + return false; + } + + if (-SellCostFactor > BaseCostFactor) + { + return false; + } + if (BaseCostFactor <= 0) + { + return false; + } + if (HeightCostFactor < 0) + { + return false; + } + if (var_06 != 16 && var_06 != 32) + { + return false; + } + if (SpanLength != 1 && SpanLength != 2 && SpanLength != 4) + { + return false; + } + if (NumCompatibleTrackMods > 7) + { + return false; + } + return NumCompatibleRoadMods <= 7; + } } -} \ No newline at end of file +} diff --git a/Core/Objects/BuildingObject.cs b/Core/Objects/BuildingObject.cs index 91923d2e..0b21beee 100644 --- a/Core/Objects/BuildingObject.cs +++ b/Core/Objects/BuildingObject.cs @@ -161,5 +161,9 @@ public ReadOnlySpan Save() return ms.ToArray(); } + + public bool Validate() + => NumAnimations is not 0 and not > 63 + && NumVariations is not 0 and <= 31; } } diff --git a/Core/Objects/CargoObject.cs b/Core/Objects/CargoObject.cs index 09057d41..87031d4f 100644 --- a/Core/Objects/CargoObject.cs +++ b/Core/Objects/CargoObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -53,5 +53,10 @@ public record CargoObject( [property: LocoStructOffset(0x1B)] uint16_t PaymentFactor, [property: LocoStructOffset(0x1D)] uint8_t PaymentIndex, [property: LocoStructOffset(0x1E)] uint8_t UnitSize - ) : ILocoStruct; -} \ No newline at end of file + ) : ILocoStruct + { + public bool Validate() + => var_02 <= 3840 + && CargoTransferTime != 0; + } +} diff --git a/Core/Objects/CliffEdgeObject.cs b/Core/Objects/CliffEdgeObject.cs index 2ca06c7d..7691b239 100644 --- a/Core/Objects/CliffEdgeObject.cs +++ b/Core/Objects/CliffEdgeObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -11,5 +11,8 @@ namespace OpenLoco.ObjectEditor.Objects public record CliffEdgeObject( [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, [property: LocoStructOffset(0x02), LocoString, Browsable(false)] image_id Image - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/ClimateObject.cs b/Core/Objects/ClimateObject.cs index 5c4c9b23..37749948 100644 --- a/Core/Objects/ClimateObject.cs +++ b/Core/Objects/ClimateObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -18,5 +18,9 @@ public record ClimateObject( ) : ILocoStruct { public const int Seasons = 4; + + public bool Validate() + => WinterSnowLine <= SummerSnowLine + && FirstSeason < 4; } } diff --git a/Core/Objects/CompetitorObject.cs b/Core/Objects/CompetitorObject.cs index cbe9feb0..61588ebf 100644 --- a/Core/Objects/CompetitorObject.cs +++ b/Core/Objects/CompetitorObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -13,7 +13,7 @@ public record CompetitorObject( [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id LastName, [property: LocoStructOffset(0x04)] uint32_t var_04, [property: LocoStructOffset(0x08)] uint32_t var_08, - [property: LocoStructOffset(0x0C)] uint8_t Emotions, + [property: LocoStructOffset(0x0C)] uint32_t Emotions, [property: LocoStructOffset(0x10), Browsable(false), LocoArrayLength(CompetitorObject.ImagesLength)] image_id[] Images, [property: LocoStructOffset(0x34)] uint8_t Intelligence, [property: LocoStructOffset(0x35)] uint8_t Aggressiveness, @@ -22,5 +22,22 @@ public record CompetitorObject( ) : ILocoStruct { public const int ImagesLength = 9; + + public bool Validate() + { + if ((Emotions & (1 << 0)) == 0) + { + return false; + } + if (Intelligence < 1 || Intelligence > 9) + { + return false; + } + if (Aggressiveness < 1 || Aggressiveness > 9) + { + return false; + } + return Competitiveness >= 1 && Competitiveness <= 9; + } } } diff --git a/Core/Objects/CurrencyObject.cs b/Core/Objects/CurrencyObject.cs index 273eb3fe..048374ba 100644 --- a/Core/Objects/CurrencyObject.cs +++ b/Core/Objects/CurrencyObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -15,5 +15,19 @@ public record CurrencyObject( [property: LocoStructOffset(0x06), Browsable(false)] image_id ObjectIcon, [property: LocoStructOffset(0x0A)] uint8_t Separator, [property: LocoStructOffset(0x0B)] uint8_t Factor - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() + { + if (Separator > 4) + { + return false; + } + if (Factor > 3) + { + return false; + } + return true; + } + } } diff --git a/Core/Objects/DockObject.cs b/Core/Objects/DockObject.cs index 6c6ceaf3..ec37d7ed 100644 --- a/Core/Objects/DockObject.cs +++ b/Core/Objects/DockObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Types; @@ -78,5 +78,19 @@ public ReadOnlySpan Save() .Concat(var_1C) .Concat(new byte[] { 0xFF }) .ToArray(); + public bool Validate() + { + if (CostIndex > 32) + { + return false; + } + + if (-SellCostFactor > BuildCostFactor) + { + return false; + } + + return BuildCostFactor > 0; + } } } diff --git a/Core/Objects/HillShapesObject.cs b/Core/Objects/HillShapesObject.cs index d5fd885d..e697bdd8 100644 --- a/Core/Objects/HillShapesObject.cs +++ b/Core/Objects/HillShapesObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -15,5 +15,8 @@ public record HillShapesObject( [property: LocoStructOffset(0x04), Browsable(false)] image_id Image, [property: LocoStructOffset(0x08), Browsable(false)] image_id ImageHill, [property: LocoStructOffset(0x0C), LocoArrayLength(0x0E - 0x0C), Browsable(false)] uint8_t[] pad_0C - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Industry/BuildingPartAnimation.cs b/Core/Objects/Industry/BuildingPartAnimation.cs index 31b537fe..5fbc718f 100644 --- a/Core/Objects/Industry/BuildingPartAnimation.cs +++ b/Core/Objects/Industry/BuildingPartAnimation.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace Core.Objects @@ -9,5 +9,12 @@ namespace Core.Objects public record BuildingPartAnimation( [property: LocoStructOffset(0x00)] uint8_t NumFrames, // Must be a power of 2 (0 = no part animation, could still have animation sequence) [property: LocoStructOffset(0x01)] uint8_t AnimationSpeed // Also encodes in bit 7 if the animation is position modified - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() + => IsPowerOfTwo(NumFrames); + + static bool IsPowerOfTwo(uint8_t x) + => (x & (x - 1)) == 0 && x > 0; + } } diff --git a/Core/Objects/Industry/IndustryObject.cs b/Core/Objects/Industry/IndustryObject.cs index c59aba1c..21534288 100644 --- a/Core/Objects/Industry/IndustryObject.cs +++ b/Core/Objects/Industry/IndustryObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -54,17 +54,17 @@ public record IndustryObject( [property: LocoStructOffset(0x08), LocoString, Browsable(false)] string_id NameDownProduction, [property: LocoStructOffset(0x0A), LocoString, Browsable(false)] string_id NameSingular, [property: LocoStructOffset(0x0C), LocoString, Browsable(false)] string_id NamePlural, - [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E, // shadows image id base - [property: LocoStructOffset(0x12), Browsable(false)] image_id var_12, // Base image id for building 0 - [property: LocoStructOffset(0x16), Browsable(false)] image_id var_16, - [property: LocoStructOffset(0x1A), Browsable(false)] image_id var_1A, - [property: LocoStructOffset(0x1E)] uint8_t NumBuildingAnimations, - [property: LocoStructOffset(0x1F)] uint8_t NumBuildingVariations, - [property: LocoStructOffset(0x20), LocoStructVariableLoad] List BuildingVariationHeights, // This is the height of a building image - [property: LocoStructOffset(0x24), LocoStructVariableLoad] List BuildingVariationAnimations, + [property: LocoStructOffset(0x0E), Browsable(false)] image_id _var_0E, // shadows image id base + [property: LocoStructOffset(0x12), Browsable(false)] image_id _var_12, // Base image id for building 0 + [property: LocoStructOffset(0x16), Browsable(false)] image_id _var_16, + [property: LocoStructOffset(0x1A), Browsable(false)] image_id _var_1A, + [property: LocoStructOffset(0x1E)] uint8_t var_1E, + [property: LocoStructOffset(0x1F)] uint8_t var_1F, + [property: LocoStructOffset(0x20), LocoStructVariableLoad] List BuildingPartHeights, // This is the height of a building image + [property: LocoStructOffset(0x24), LocoStructVariableLoad] List BuildingPartAnimations, [property: LocoStructOffset(0x28), LocoStructVariableLoad, LocoArrayLength(IndustryObject.AnimationSequencesCount)] List AnimationSequences, // Access with getAnimationSequence helper method [property: LocoStructOffset(0x38), LocoStructVariableLoad] List var_38, // Access with getUnk38 helper method - [property: LocoStructOffset(0x3C), LocoStructVariableLoad, LocoArrayLength(IndustryObject.VariationPartCount)] List BuildingVariationParts, // Access with getBuildingParts helper method + [property: LocoStructOffset(0x3C), LocoStructVariableLoad, LocoArrayLength(IndustryObject.VariationPartCount)] List BuildingParts, // Access with getBuildingParts helper method [property: LocoStructOffset(0xBC)] uint8_t MinNumBuildings, [property: LocoStructOffset(0xBD)] uint8_t MaxNumBuildings, [property: LocoStructOffset(0xBE), LocoStructVariableLoad] List Buildings, @@ -107,19 +107,24 @@ public record IndustryObject( public S5Header BuildingWallEntrance { get; set; } + public image_id var_0E { get; private set; } // shadows image id base + public image_id var_12 { get; private set; } // Base image id for building 0 + public image_id var_16 { get; private set; } + public image_id var_1A { get; private set; } + public ReadOnlySpan Load(ReadOnlySpan remainingData) { - // variation heights - BuildingVariationHeights.Clear(); - BuildingVariationHeights.AddRange(ByteReaderT.Read_Array(remainingData[..(NumBuildingAnimations * 1)], NumBuildingAnimations)); - remainingData = remainingData[(NumBuildingAnimations * 1)..]; // uint8_t* + // part heights + BuildingPartHeights.Clear(); + BuildingPartHeights.AddRange(ByteReaderT.Read_Array(remainingData[..(var_1E * 1)], var_1E)); + remainingData = remainingData[(var_1E * 1)..]; // uint8_t* - // variation animations - BuildingVariationAnimations.Clear(); + // part animations + BuildingPartAnimations.Clear(); var buildingAnimationSize = ObjectAttributes.StructSize(); - BuildingVariationAnimations.AddRange(ByteReader.ReadLocoStructArray(remainingData[..(NumBuildingAnimations * buildingAnimationSize)], typeof(BuildingPartAnimation), NumBuildingAnimations, buildingAnimationSize) + BuildingPartAnimations.AddRange(ByteReader.ReadLocoStructArray(remainingData[..(var_1E * buildingAnimationSize)], typeof(BuildingPartAnimation), var_1E, buildingAnimationSize) .Cast()); - remainingData = remainingData[(NumBuildingAnimations * 2)..]; // uint16_t* + remainingData = remainingData[(var_1E * 2)..]; // uint16_t* // animation sequences AnimationSequences.Clear(); @@ -148,8 +153,8 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) remainingData = remainingData[1..]; // skip final 0xFF byte // variation parts - BuildingVariationParts.Clear(); - for (var i = 0; i < NumBuildingVariations; ++i) + BuildingParts.Clear(); + for (var i = 0; i < var_1F; ++i) { var ptr_1F = 0; while (remainingData[++ptr_1F] != 0xFF) @@ -157,7 +162,7 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) ; } - BuildingVariationParts.Add(remainingData[..ptr_1F].ToArray()); + BuildingParts.Add(remainingData[..ptr_1F].ToArray()); ptr_1F++; remainingData = remainingData[ptr_1F..]; } @@ -183,12 +188,28 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) remainingData = remainingData[(S5Header.StructLength * WallTypeCount)..]; // wall type - BuildingWall = S5Header.Read(remainingData[..S5Header.StructLength]); - remainingData = remainingData[S5Header.StructLength..]; + if (remainingData[0] != 0xFF) + { + BuildingWall = S5Header.Read(remainingData[..S5Header.StructLength]); + remainingData = remainingData[S5Header.StructLength..]; + } // wall type entrance - BuildingWallEntrance = S5Header.Read(remainingData[..S5Header.StructLength]); - remainingData = remainingData[S5Header.StructLength..]; + if (remainingData[0] != 0xFF) + { + BuildingWallEntrance = S5Header.Read(remainingData[..S5Header.StructLength]); + remainingData = remainingData[S5Header.StructLength..]; + } + + // image stuff, in openloco it happens after image table load, but only to get image offsets, which we can just set to 0 here + var_0E = 0; + var_12 = var_0E; + if (Flags.HasFlag(IndustryObjectFlags.HasShadows)) + { + var_12 += var_1F * 4u; + } + var_16 = (var_1E * 4u) + var_12; + var_1A = var_E9 * 21u; return remainingData; } @@ -197,14 +218,14 @@ public ReadOnlySpan Save() { using (var ms = new MemoryStream()) { - // variation heights - foreach (var x in BuildingVariationHeights) + // part heights + foreach (var x in BuildingPartHeights) { ms.WriteByte(x); } - // variation animations - foreach (var x in BuildingVariationAnimations) + // part animations + foreach (var x in BuildingPartAnimations) { ms.WriteByte(x.NumFrames); ms.WriteByte(x.AnimationSpeed); @@ -227,7 +248,7 @@ public ReadOnlySpan Save() ms.WriteByte(0xFF); // variation parts - foreach (var x in BuildingVariationParts) + foreach (var x in BuildingParts) { ms.Write(x); ms.WriteByte(0xFF); @@ -265,5 +286,63 @@ public ReadOnlySpan Save() return ms.ToArray(); } } + + public bool Validate() + { + if (var_1E == 0) + { + return false; + } + if (var_1F == 0 || var_1F > 31) + { + return false; + } + + if (MaxNumBuildings < MinNumBuildings) + { + return false; + } + + if (TotalOfTypeInScenario == 0 || TotalOfTypeInScenario > 32) + { + return false; + } + + // 230/256 = ~90% + if (-ClearCostFactor > CostFactor * 230 / 256) + { + return false; + } + + if (var_E8 > 8) + { + return false; + } + switch (var_E9) + { + case 1: + case 2: + case 4: + break; + default: + return false; + } + + if (var_EA != 0xFF && var_EA > 7) + { + return false; + } + + if (var_EC > 8) + { + return false; + } + + if (InitialProductionRate[0].Min > 100) + { + return false; + } + return InitialProductionRate[1].Min <= 100; + } } } diff --git a/Core/Objects/Industry/IndustryObjectProductionRateRange.cs b/Core/Objects/Industry/IndustryObjectProductionRateRange.cs index 52483812..fe782639 100644 --- a/Core/Objects/Industry/IndustryObjectProductionRateRange.cs +++ b/Core/Objects/Industry/IndustryObjectProductionRateRange.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace Core.Objects @@ -9,5 +9,8 @@ namespace Core.Objects public record IndustryObjectProductionRateRange( [property: LocoStructOffset(0x00)] uint16_t Min, [property: LocoStructOffset(0x02)] uint16_t Max - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Industry/IndustryObjectUnk38.cs b/Core/Objects/Industry/IndustryObjectUnk38.cs index 4e1fe82f..1ec18639 100644 --- a/Core/Objects/Industry/IndustryObjectUnk38.cs +++ b/Core/Objects/Industry/IndustryObjectUnk38.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace Core.Objects @@ -9,5 +9,8 @@ namespace Core.Objects public record IndustryObjectUnk38( [property: LocoStructOffset(0x00)] uint8_t var_00, [property: LocoStructOffset(0x01)] uint8_t var_01 - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/InterfaceSkinObject.cs b/Core/Objects/InterfaceSkinObject.cs index 40e64bd6..704420f6 100644 --- a/Core/Objects/InterfaceSkinObject.cs +++ b/Core/Objects/InterfaceSkinObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Types; @@ -30,10 +30,11 @@ public record InterfaceSkinObject( [property: LocoStructOffset(0x15)] Colour Colour_15, [property: LocoStructOffset(0x16)] Colour Colour_16, [property: LocoStructOffset(0x17)] Colour Colour_17 - ) : ILocoStruct, IImageTableStrings + ) : ILocoStruct, ILocoImageTableNames { public bool TryGetImageName(int id, out string? value) => ImageIdNameMap.TryGetValue(id, out value); + public bool Validate() => true; public static Dictionary ImageIdNameMap = new() { @@ -508,5 +509,6 @@ public bool TryGetImageName(int id, out string? value) { 468, "toolbar_menu_map_south" }, { 469, "toolbar_menu_map_east" }, }; + } } diff --git a/Core/Objects/LandObject.cs b/Core/Objects/LandObject.cs index 75fe578d..485b21d1 100644 --- a/Core/Objects/LandObject.cs +++ b/Core/Objects/LandObject.cs @@ -72,5 +72,27 @@ public ReadOnlySpan Save() return data; } + + public bool Validate() + { + if (CostIndex > 32) + { + return false; + } + if (CostFactor <= 0) + { + return false; + } + if (var_03 < 1) + { + return false; + } + if (var_03 > 8) + { + return false; + } + + return (var_04 == 1 || var_04 == 2 || var_04 == 4); + } } } diff --git a/Core/Objects/LevelCrossingObject.cs b/Core/Objects/LevelCrossingObject.cs index 36502f48..81a31137 100644 --- a/Core/Objects/LevelCrossingObject.cs +++ b/Core/Objects/LevelCrossingObject.cs @@ -1,4 +1,4 @@ - + using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -20,5 +20,24 @@ public record LevelCrossingObject( [property: LocoStructOffset(0x0A), LocoArrayLength(0x0C - 0x0A)] uint8_t[] pad_0A, [property: LocoStructOffset(0x0C)] uint16_t DesignedYear, [property: LocoStructOffset(0x0E), Browsable(false)] image_id Image - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() + { + if (-SellCostFactor > CostFactor) + { + return false; + } + if (CostFactor <= 0) + { + return false; + } + + return ClosingFrames switch + { + 1 or 2 or 4 or 8 or 16 or 32 => true, + _ => false, + }; + } + } } diff --git a/Core/Objects/RegionObject.cs b/Core/Objects/RegionObject.cs index 259aac59..fd5ad0a6 100644 --- a/Core/Objects/RegionObject.cs +++ b/Core/Objects/RegionObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Headers; @@ -60,5 +60,7 @@ public ReadOnlySpan Save() return data; } + + public bool Validate() => true; } } diff --git a/Core/Objects/RoadExtraObject.cs b/Core/Objects/RoadExtraObject.cs index ea9be815..05e93c2a 100644 --- a/Core/Objects/RoadExtraObject.cs +++ b/Core/Objects/RoadExtraObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -17,5 +17,26 @@ public record RoadExtraObject( [property: LocoStructOffset(0x08)] int16_t SellCostFactor, [property: LocoStructOffset(0x0A), Browsable(false)] image_id Image, [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() + { + if (PaintStyle >= 2) + { + return false; + } + + // This check missing from vanilla + if (CostIndex >= 32) + { + return false; + } + + if (-SellCostFactor > BuildCostFactor) + { + return false; + } + return BuildCostFactor > 0; + } + } } diff --git a/Core/Objects/RoadObject.cs b/Core/Objects/RoadObject.cs index 252eda9d..d2142c40 100644 --- a/Core/Objects/RoadObject.cs +++ b/Core/Objects/RoadObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Headers; @@ -114,5 +114,39 @@ public ReadOnlySpan Save() return headers.SelectMany(h => h.Write().ToArray()).ToArray(); } + + public bool Validate() + { + // check missing in vanilla + if (CostIndex >= 32) + { + return false; + } + if (-SellCostFactor > BuildCostFactor) + { + return false; + } + if (BuildCostFactor <= 0) + { + return false; + } + if (TunnelCostFactor <= 0) + { + return false; + } + if (NumBridges > 7) + { + return false; + } + if (NumMods > 2) + { + return false; + } + if (Flags.HasFlag(RoadObjectFlags.unk_03)) + { + return NumMods == 0; + } + return true; + } } } diff --git a/Core/Objects/RoadStationObject.cs b/Core/Objects/RoadStationObject.cs index cb527c26..7223a4dd 100644 --- a/Core/Objects/RoadStationObject.cs +++ b/Core/Objects/RoadStationObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Headers; @@ -115,5 +115,34 @@ public ReadOnlySpan Save() return ms.ToArray(); } } + + public bool Validate() + { + if (CostIndex >= 32) + { + return false; + } + if (-SellCostFactor > BuildCostFactor) + { + return false; + } + if (BuildCostFactor <= 0) + { + return false; + } + if (PaintStyle >= 1) + { + return false; + } + if (NumCompatible > 7) + { + return false; + } + if (Flags.HasFlag(RoadStationObjectFlags.Passenger) && Flags.HasFlag(RoadStationObjectFlags.Freight)) + { + return false; + } + return true; + } } } diff --git a/Core/Objects/ScaffoldingObject.cs b/Core/Objects/ScaffoldingObject.cs index e1f52bfd..4b56aca4 100644 --- a/Core/Objects/ScaffoldingObject.cs +++ b/Core/Objects/ScaffoldingObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -13,6 +13,9 @@ public record ScaffoldingObject( [property: LocoStructOffset(0x02), Browsable(false)] image_id Image, [property: LocoStructOffset(0x06), LocoArrayLength(3)] uint16_t[] SegmentHeights, [property: LocoStructOffset(0x0C), LocoArrayLength(3)] uint16_t[] RoofHeights - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/ScenarioTextObject.cs b/Core/Objects/ScenarioTextObject.cs index 5da17bbf..bc8425e9 100644 --- a/Core/Objects/ScenarioTextObject.cs +++ b/Core/Objects/ScenarioTextObject.cs @@ -1,4 +1,4 @@ - + using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -13,5 +13,8 @@ public record ScenarioTextObject( [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, [property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id Details, [property: LocoStructOffset(0x04), LocoArrayLength(0x6 - 0x4)] uint8_t pad_04 - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/SnowObject.cs b/Core/Objects/SnowObject.cs index 80461fbc..748e8962 100644 --- a/Core/Objects/SnowObject.cs +++ b/Core/Objects/SnowObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -11,5 +11,8 @@ namespace OpenLoco.ObjectEditor.Objects public record SnowObject( [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, [property: LocoStructOffset(0x02), Browsable(false)] image_id Image - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Sound/SoundObject.cs b/Core/Objects/Sound/SoundObject.cs index 08998f49..5370a908 100644 --- a/Core/Objects/Sound/SoundObject.cs +++ b/Core/Objects/Sound/SoundObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -64,5 +64,7 @@ public ReadOnlySpan Save() return ms.ToArray(); } } + + public bool Validate() => true; } } diff --git a/Core/Objects/Sound/SoundObjectData.cs b/Core/Objects/Sound/SoundObjectData.cs index 29bf4934..eeeac199 100644 --- a/Core/Objects/Sound/SoundObjectData.cs +++ b/Core/Objects/Sound/SoundObjectData.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace Core.Objects.Sound @@ -10,5 +10,11 @@ public record SoundObjectData( [property: LocoStructOffset(0x04)] int32_t Offset, [property: LocoStructOffset(0x08)] uint32_t Length, [property: LocoStructOffset(0x0C)] WaveFormatEx PcmHeader - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() + { + return Offset >= 0; + } + } } diff --git a/Core/Objects/Sound/WaveFormatEx.cs b/Core/Objects/Sound/WaveFormatEx.cs index f5f262d0..d1869127 100644 --- a/Core/Objects/Sound/WaveFormatEx.cs +++ b/Core/Objects/Sound/WaveFormatEx.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace Core.Objects.Sound @@ -13,5 +13,8 @@ public record WaveFormatEx( [property: LocoStructOffset(0x0B)] int16_t BlockAlign, [property: LocoStructOffset(0x0D)] int16_t BitsPerSample, [property: LocoStructOffset(0x010)] int16_t CBSize - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/SteamObject.cs b/Core/Objects/SteamObject.cs index d6e780b6..20988cb5 100644 --- a/Core/Objects/SteamObject.cs +++ b/Core/Objects/SteamObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Headers; @@ -24,6 +24,7 @@ public record ImageAndHeight( { public static ImageAndHeight Read(ReadOnlySpan data) => new(data[0], data[1]); + public bool Validate() => true; } [TypeConverter(typeof(ExpandableObjectConverter))] @@ -96,5 +97,6 @@ public ReadOnlySpan Save() .Concat(new byte[] { 0xFF }) .Concat(SoundEffects.SelectMany(sfx => sfx.Write().ToArray())) .ToArray(); + public bool Validate() => true; } } diff --git a/Core/Objects/StreetLightObject.cs b/Core/Objects/StreetLightObject.cs index fe761b24..3cab2e5f 100644 --- a/Core/Objects/StreetLightObject.cs +++ b/Core/Objects/StreetLightObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -14,5 +14,7 @@ public record StreetLightObject( ) : ILocoStruct { public const int DesignedYearLength = 3; + + public bool Validate() => true; } } diff --git a/Core/Objects/TownNamesObject.cs b/Core/Objects/TownNamesObject.cs index 9e74a004..38a3b1c7 100644 --- a/Core/Objects/TownNamesObject.cs +++ b/Core/Objects/TownNamesObject.cs @@ -1,4 +1,4 @@ - + using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -11,7 +11,10 @@ public record TownNamesUnk( [property: LocoStructOffset(0x00)] uint8_t Count, [property: LocoStructOffset(0x01)] uint8_t Fill, [property: LocoStructOffset(0x02)] uint16_t Offset - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x1A)] @@ -36,5 +39,6 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) public ReadOnlySpan Save() => tempUnkVariableData; + public bool Validate() => true; } } diff --git a/Core/Objects/TrackExtraObject.cs b/Core/Objects/TrackExtraObject.cs index cb073892..7a7a967b 100644 --- a/Core/Objects/TrackExtraObject.cs +++ b/Core/Objects/TrackExtraObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Types; @@ -18,11 +18,31 @@ public record TrackExtraObject( [property: LocoStructOffset(0x08)] int16_t SellCostFactor, [property: LocoStructOffset(0x0A), Browsable(false)] image_id Image, [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E) - : ILocoStruct, IImageTableStrings + : ILocoStruct, ILocoImageTableNames { public bool TryGetImageName(int id, out string? value) => ImageIdNameMap.TryGetValue(id - 8, out value); + public bool Validate() + { + if (PaintStyle >= 2) + { + return false; + } + + // This check missing from vanilla + if (CostIndex > 32) + { + return false; + } + + if (-SellCostFactor > BuildCostFactor) + { + return false; + } + return BuildCostFactor > 0; + } + // taken from OpenLoco TrackExtraObject.h public static Dictionary ImageIdNameMap = new() { diff --git a/Core/Objects/TrackObject.cs b/Core/Objects/TrackObject.cs index 49eb070a..a269c9d8 100644 --- a/Core/Objects/TrackObject.cs +++ b/Core/Objects/TrackObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Headers; @@ -62,7 +62,7 @@ public record TrackObject( [property: LocoStructOffset(0x2D), LocoArrayLength(TrackObject.MaxStations), Browsable(false)] object_id[] _Stations, // 0x2D [property: LocoStructOffset(0x34)] uint8_t DisplayOffset, [property: LocoStructOffset(0x35), Browsable(false)] uint8_t pad_35 - ) : ILocoStruct, ILocoStructVariableData, IImageTableStrings + ) : ILocoStruct, ILocoStructVariableData, ILocoImageTableNames { public List Compatible { get; set; } = []; public List Mods { get; set; } = []; @@ -123,6 +123,43 @@ public ReadOnlySpan Save() public bool TryGetImageName(int id, out string? value) => ImageIdNameMap.TryGetValue(id, out value); + public bool Validate() + { + if (var_06 >= 3) + { + return false; + } + + // vanilla missed this check + if (CostIndex > 32) + { + return false; + } + + if (-SellCostFactor > BuildCostFactor) + { + return false; + } + if (BuildCostFactor <= 0) + { + return false; + } + if (TunnelCostFactor <= 0) + { + return false; + } + if (TrackPieces.HasFlag(TrackObjectPieceFlags.Diagonal | TrackObjectPieceFlags.LargeCurve) + && TrackPieces.HasFlag(TrackObjectPieceFlags.OneSided | TrackObjectPieceFlags.VerySmallCurve)) + { + return false; + } + if (NumBridges > 7) + { + return false; + } + return NumStations <= 7; + } + // taken from OpenLoco TrackObject.h public static Dictionary ImageIdNameMap = new() { @@ -541,6 +578,7 @@ public bool TryGetImageName(int id, out string? value) { 411, "rightCurveVerySmall0RailNW" }, }; + // ai generated - nice idea, maybe implement? //public static TrackObject FromDatFile(DatFile datFile, int index) //{ @@ -556,4 +594,4 @@ public bool TryGetImageName(int id, out string? value) // return trackObject; //} } -} \ No newline at end of file +} diff --git a/Core/Objects/TrainSignalObject.cs b/Core/Objects/TrainSignalObject.cs index b1e6854b..73f0d537 100644 --- a/Core/Objects/TrainSignalObject.cs +++ b/Core/Objects/TrainSignalObject.cs @@ -34,7 +34,7 @@ public record TrainSignalObject( [property: LocoStructOffset(0x13), LocoArrayLength(TrainSignalObject.ModsLength), Browsable(false)] object_id[] ModHeaderIds, [property: LocoStructOffset(0x1A)] uint16_t DesignedYear, [property: LocoStructOffset(0x1C)] uint16_t ObsoleteYear - ) : ILocoStruct, ILocoStructVariableData, IImageTableStrings + ) : ILocoStruct, ILocoStructVariableData, ILocoImageTableNames { public const int ModsLength = 7; @@ -61,5 +61,47 @@ public bool TryGetImageName(int id, out string? value) { 96, "greenLights" }, { 104, "greenLights2" }, }; + + public bool Validate() + { + // animationSpeed must be 1 less than a power of 2 (its a mask) + switch (AnimationSpeed) + { + case 0: + case 1: + case 3: + case 7: + case 15: + break; + default: + return false; + } + + switch (NumFrames) + { + case 4: + case 7: + case 10: + break; + default: + return false; + } + + if (CostIndex > 32) + { + return false; + } + + if (-SellCostFactor > CostFactor) + { + return false; + } + + if (NumCompatible > 7) + { + return false; + } + return true; + } } } diff --git a/Core/Objects/TrainStationObject.cs b/Core/Objects/TrainStationObject.cs index 70ebde4d..bc1584be 100644 --- a/Core/Objects/TrainStationObject.cs +++ b/Core/Objects/TrainStationObject.cs @@ -20,8 +20,8 @@ public enum TrainStationObjectFlags : uint8_t [LocoStringTable("Name")] public record TrainStationObject( [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [property: LocoStructOffset(0x02)] uint8_t PaintStyle, - [property: LocoStructOffset(0x03)] uint8_t var_03, + [property: LocoStructOffset(0x02)] uint8_t DrawStyle, + [property: LocoStructOffset(0x03)] uint8_t Height, [property: LocoStructOffset(0x04)] uint16_t TrackPieces, [property: LocoStructOffset(0x06)] int16_t BuildCostFactor, [property: LocoStructOffset(0x08)] int16_t SellCostFactor, @@ -126,5 +126,26 @@ public ReadOnlySpan Save() return ms.ToArray(); } } + + public bool Validate() + { + if (CostIndex >= 32) + { + return false; + } + if (-SellCostFactor > BuildCostFactor) + { + return false; + } + if (BuildCostFactor <= 0) + { + return false; + } + if (DrawStyle >= 1) + { + return false; + } + return NumCompatible <= 7; + } } } diff --git a/Core/Objects/TreeObject.cs b/Core/Objects/TreeObject.cs index 12b38112..475e9314 100644 --- a/Core/Objects/TreeObject.cs +++ b/Core/Objects/TreeObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -43,5 +43,41 @@ public record TreeObject( [property: LocoStructOffset(0x44)] uint32_t Colours, [property: LocoStructOffset(0x48)] int16_t Rating, [property: LocoStructOffset(0x4A)] int16_t DemolishRatingReduction - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() + { + if (CostIndex > 32) + { + return false; + } + + // 230/256 = ~90% + if (-ClearCostFactor > BuildCostFactor * 230 / 256) + { + return false; + } + + switch (NumRotations) + { + default: + return false; + case 1: + case 2: + case 4: + break; + } + if (Growth < 1 || Growth > 8) + { + return false; + } + + if (Height < Clearance) + { + return false; + } + + return var_05 >= var_04; + } + } } diff --git a/Core/Objects/TunnelObject.cs b/Core/Objects/TunnelObject.cs index 7ac41d41..c39882b9 100644 --- a/Core/Objects/TunnelObject.cs +++ b/Core/Objects/TunnelObject.cs @@ -1,4 +1,4 @@ - + using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -12,5 +12,8 @@ namespace OpenLoco.ObjectEditor.Objects public record TunnelObject( [property: LocoStructOffset(0x00), Browsable(false)] string_id Name, [property: LocoStructOffset(0x02), Browsable(false)] image_id Image - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Vehicle/BodySprite.cs b/Core/Objects/Vehicle/BodySprite.cs index eb22d699..c2e461a7 100644 --- a/Core/Objects/Vehicle/BodySprite.cs +++ b/Core/Objects/Vehicle/BodySprite.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace OpenLoco.ObjectEditor.Objects @@ -49,5 +49,7 @@ public record BodySprite( public Dictionary> ImageIds = []; public int NumImages { get; set; } + + public bool Validate() => true; } } diff --git a/Core/Objects/Vehicle/BogieSprite.cs b/Core/Objects/Vehicle/BogieSprite.cs index 682c48b2..763ef046 100644 --- a/Core/Objects/Vehicle/BogieSprite.cs +++ b/Core/Objects/Vehicle/BogieSprite.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace OpenLoco.ObjectEditor.Objects @@ -23,5 +23,7 @@ public record BogieSprite( public Dictionary> ImageIds = []; public int NumImages { get; set; } + + public bool Validate() => true; } } diff --git a/Core/Objects/Vehicle/Engine1Sound.cs b/Core/Objects/Vehicle/Engine1Sound.cs index ba247cdd..6e0af048 100644 --- a/Core/Objects/Vehicle/Engine1Sound.cs +++ b/Core/Objects/Vehicle/Engine1Sound.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace OpenLoco.ObjectEditor.Objects @@ -18,5 +18,8 @@ public record Engine1Sound( [property: LocoStructOffset(0x0E)] uint8_t VolumeIncreaseStep, [property: LocoStructOffset(0x0F)] uint8_t VolumeDecreaseStep, [property: LocoStructOffset(0x10)] uint8_t SpeedFreqFactor - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Vehicle/Engine2Sound.cs b/Core/Objects/Vehicle/Engine2Sound.cs index f4718fef..fa0f0821 100644 --- a/Core/Objects/Vehicle/Engine2Sound.cs +++ b/Core/Objects/Vehicle/Engine2Sound.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace OpenLoco.ObjectEditor.Objects @@ -23,5 +23,8 @@ public record Engine2Sound( [property: LocoStructOffset(0x18)] uint8_t VolumeIncreaseStep, [property: LocoStructOffset(0x19)] uint8_t VolumeDecreaseStep, [property: LocoStructOffset(0x1A)] uint8_t SpeedFreqFactor - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Vehicle/FrictionSound.cs b/Core/Objects/Vehicle/FrictionSound.cs index 34cce78d..2e94218b 100644 --- a/Core/Objects/Vehicle/FrictionSound.cs +++ b/Core/Objects/Vehicle/FrictionSound.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace OpenLoco.ObjectEditor.Objects @@ -13,5 +13,8 @@ public record FrictionSound( [property: LocoStructOffset(0x08)] uint8_t SpeedVolumeFactor, [property: LocoStructOffset(0x09)] uint8_t BaseVolume, [property: LocoStructOffset(0x0A)] uint8_t MaxVolume - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Vehicle/SimpleAnimation.cs b/Core/Objects/Vehicle/SimpleAnimation.cs index b10f1597..7d23c587 100644 --- a/Core/Objects/Vehicle/SimpleAnimation.cs +++ b/Core/Objects/Vehicle/SimpleAnimation.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace OpenLoco.ObjectEditor.Objects @@ -9,5 +9,8 @@ public record SimpleAnimation( [property: LocoStructOffset(0x00), Browsable(false)] object_id ObjectId, [property: LocoStructOffset(0x01)] uint8_t Height, [property: LocoStructOffset(0x02)] SimpleAnimationType Type - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/Vehicle/VehicleObject.cs b/Core/Objects/Vehicle/VehicleObject.cs index 1595e59d..76ab7685 100644 --- a/Core/Objects/Vehicle/VehicleObject.cs +++ b/Core/Objects/Vehicle/VehicleObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Headers; @@ -492,5 +492,128 @@ static uint8_t getYawAccuracySloped(uint8_t numFrames) 16 => 2, _ => 3, }; + + public bool Validate() + { + if (CostIndex > 32) + { + return false; + } + if (RunCostIndex > 32) + { + return false; + } + + if (CostFactor <= 0) + { + return false; + } + if (RunCostFactor < 0) + { + return false; + } + + if (Flags.HasFlag(VehicleObjectFlags.unk_09)) + { + if (NumTrackExtras != 0) + { + return false; + } + if (Flags.HasFlag(VehicleObjectFlags.RackRail)) + { + return false; + } + } + + if (NumTrackExtras > 4) + { + return false; + } + + if (NumSimultaneousCargoTypes > 2) + { + return false; + } + + if (NumCompatibleVehicles > 8) + { + return false; + } + + if (RackSpeed > Speed) + { + return false; + } + + foreach (var bodySprite in BodySprites) + { + if (!bodySprite.Flags.HasFlag(BodySpriteFlags.HasSprites)) + { + 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 < 1 || bodySprite.NumCargoLoadFrames > 5) + { + return false; + } + switch (bodySprite.NumRollFrames) + { + case 1: + case 3: + break; + default: + return false; + } + } + + foreach (var bogieSprite in BogieSprites) + { + if (!bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasSprites)) + { + continue; + } + + switch (bogieSprite.RollStates) + { + case 1: + case 2: + case 4: + break; + default: + return false; + } + } + return true; + } } } diff --git a/Core/Objects/Vehicle/VehicleObjectUnk.cs b/Core/Objects/Vehicle/VehicleObjectUnk.cs index ca9af242..8e7e108a 100644 --- a/Core/Objects/Vehicle/VehicleObjectUnk.cs +++ b/Core/Objects/Vehicle/VehicleObjectUnk.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; namespace OpenLoco.ObjectEditor.Objects @@ -12,5 +12,8 @@ public record VehicleObjectUnk( [property: LocoStructOffset(0x03)] uint8_t BackBogieSpriteInd, // index of a bogieSprites struct [property: LocoStructOffset(0x04)] uint8_t BodySpriteInd, // index of a bogieSprites struct [property: LocoStructOffset(0x05)] uint8_t var_05 - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/WallObject.cs b/Core/Objects/WallObject.cs index 526b4f59..86c6b974 100644 --- a/Core/Objects/WallObject.cs +++ b/Core/Objects/WallObject.cs @@ -41,5 +41,8 @@ public record WallObject( [property: LocoStructOffset(0x07)] WallObjectFlags Flags, [property: LocoStructOffset(0x08)] uint8_t Height, [property: LocoStructOffset(0x09), LocoPropertyMaybeUnused] WallObjectFlags2 Flags2 - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Core/Objects/WaterObject.cs b/Core/Objects/WaterObject.cs index cf59dded..99407592 100644 --- a/Core/Objects/WaterObject.cs +++ b/Core/Objects/WaterObject.cs @@ -1,4 +1,4 @@ - + using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; @@ -17,5 +17,19 @@ public record WaterObject( [property: LocoStructOffset(0x05), LocoPropertyMaybeUnused] uint8_t var_05, [property: LocoStructOffset(0x06), Browsable(false)] image_id Image, [property: LocoStructOffset(0x0A), Browsable(false)] image_id MapPixelImage - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() + { + if (CostIndex > 32) + { + return false; + } + if (CostFactor <= 0) + { + return false; + } + return true; + } + } } diff --git a/Core/Types/G1Dat.cs b/Core/Types/G1Dat.cs index 3c5c15e4..cf5dedcc 100644 --- a/Core/Types/G1Dat.cs +++ b/Core/Types/G1Dat.cs @@ -7,7 +7,7 @@ namespace OpenLoco.ObjectEditor.DatFileParsing [TypeConverter(typeof(ExpandableObjectConverter))] public record G1Dat( G1Header G1Header, - List G1Elements) : IImageTableStrings + List G1Elements) : ILocoImageTableNames { public bool TryGetImageName(int id, out string? value) => ImageIdNameMap.TryGetValue(id, out value); diff --git a/Core/Types/G1Element32.cs b/Core/Types/G1Element32.cs index af5d29f9..3d87f2f6 100644 --- a/Core/Types/G1Element32.cs +++ b/Core/Types/G1Element32.cs @@ -56,5 +56,7 @@ public ReadOnlySpan Write() return span; } + + public bool Validate() => true; } } diff --git a/Core/Types/G1Header.cs b/Core/Types/G1Header.cs index abb728bb..75293531 100644 --- a/Core/Types/G1Header.cs +++ b/Core/Types/G1Header.cs @@ -13,5 +13,7 @@ public record G1Header( { public static int StructLength => 0x08; public byte[] ImageData = []; + + public bool Validate() => true; } } diff --git a/Core/Types/IImageTableStrings.cs b/Core/Types/IImageTableStrings.cs deleted file mode 100644 index 12621814..00000000 --- a/Core/Types/IImageTableStrings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace OpenLoco.ObjectEditor.Types -{ - public interface IImageTableStrings - { - public bool TryGetImageName(int id, out string? value); - } -} diff --git a/Core/Types/ILocoImageTableNames.cs b/Core/Types/ILocoImageTableNames.cs new file mode 100644 index 00000000..5297a9a9 --- /dev/null +++ b/Core/Types/ILocoImageTableNames.cs @@ -0,0 +1,7 @@ +namespace OpenLoco.ObjectEditor.Types +{ + public interface ILocoImageTableNames + { + public bool TryGetImageName(int id, out string? value); + } +} diff --git a/Core/Types/ILocoStruct.cs b/Core/Types/ILocoStruct.cs index 20840cf5..ed1e826e 100644 --- a/Core/Types/ILocoStruct.cs +++ b/Core/Types/ILocoStruct.cs @@ -1,8 +1,10 @@ -using System.ComponentModel; +using System.ComponentModel; namespace OpenLoco.ObjectEditor.DatFileParsing { [TypeConverter(typeof(ExpandableObjectConverter))] public interface ILocoStruct - { } + { + bool Validate(); + } } diff --git a/Core/Types/ILocoStructPostLoad.cs b/Core/Types/ILocoStructPostLoad.cs new file mode 100644 index 00000000..cd6e1eb2 --- /dev/null +++ b/Core/Types/ILocoStructPostLoad.cs @@ -0,0 +1,10 @@ +using System.ComponentModel; + +namespace OpenLoco.ObjectEditor.DatFileParsing +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + public interface ILocoStructPostLoad + { + void PostLoad(); + } +} diff --git a/Core/Types/ILocoStructVariableData.cs b/Core/Types/ILocoStructVariableData.cs index 73409eb3..d9eb8f3c 100644 --- a/Core/Types/ILocoStructVariableData.cs +++ b/Core/Types/ILocoStructVariableData.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace OpenLoco.ObjectEditor.DatFileParsing { @@ -9,12 +9,4 @@ public interface ILocoStructVariableData ReadOnlySpan Save(); } - - [TypeConverter(typeof(ExpandableObjectConverter))] - public interface ILocoStructPostLoad - { - void PostLoad(); - - //ReadOnlySpan Save(); - } } diff --git a/Core/Types/LocoObject.cs b/Core/Types/LocoObject.cs index 997832dd..ba856dc1 100644 --- a/Core/Types/LocoObject.cs +++ b/Core/Types/LocoObject.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.DatFileParsing; using OpenLoco.ObjectEditor.Headers; diff --git a/Core/Types/S5Header.cs b/Core/Types/S5Header.cs index aa33606e..1460a99f 100644 --- a/Core/Types/S5Header.cs +++ b/Core/Types/S5Header.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using OpenLoco.ObjectEditor.Data; using OpenLoco.ObjectEditor.DatFileParsing; using Zenith.Core; @@ -55,6 +55,6 @@ public ReadOnlySpan Write() return span; } - public static S5Header NullHeader = new(0, string.Empty, 0); + public static S5Header NullHeader = new(0xFFFFFFFF, " ", 0); } } diff --git a/Core/Types/Typedefs.cs b/Core/Types/Typedefs.cs index b4945f0b..b5a84d36 100644 --- a/Core/Types/Typedefs.cs +++ b/Core/Types/Typedefs.cs @@ -1,4 +1,4 @@ -global using uint8_t = System.Byte; +global using uint8_t = System.Byte; global using int8_t = System.SByte; global using uint16_t = System.UInt16; global using int16_t = System.Int16; @@ -21,5 +21,8 @@ namespace OpenLoco.ObjectEditor.Types public record Pos2( [property: LocoStructOffset(0x00)] int16_t X, [property: LocoStructOffset(0x02)] int16_t Y - ) : ILocoStruct; + ) : ILocoStruct + { + public bool Validate() => true; + } } diff --git a/Gui/MainForm.cs b/Gui/MainForm.cs index d3ffbcfa..5d35b990 100644 --- a/Gui/MainForm.cs +++ b/Gui/MainForm.cs @@ -525,7 +525,7 @@ void LoadDataDump(string path, bool isG1 = false) { try { - LoadDataDumpCore(path, isG1); + //LoadDataDumpCore(path, isG1); } catch (Exception ex) { @@ -746,15 +746,15 @@ public void ExportImages() public string GetImageName(IUiObject? uiObj, int counter) { - IImageTableStrings? its = null; + ILocoImageTableNames? its = null; var objectName = string.Empty; - if (uiObj is UiLocoObject uiLocoObj && uiLocoObj.LocoObject != null && uiLocoObj.LocoObject.Object is IImageTableStrings itss) + if (uiObj is UiLocoObject uiLocoObj && uiLocoObj.LocoObject != null && uiLocoObj.LocoObject.Object is ILocoImageTableNames itss) { its = itss; objectName = uiLocoObj.DatFileInfo.S5Header.Name; } - else if (uiObj is UiG1 uiG1 && uiG1.G1 is IImageTableStrings itsg) + else if (uiObj is UiG1 uiG1 && uiG1.G1 is ILocoImageTableNames itsg) { its = itsg; objectName = "g1.dat"; @@ -764,7 +764,7 @@ public string GetImageName(IUiObject? uiObj, int counter) { if (!its.TryGetImageName(counter, out var value) || value == null) { - logger.Warning($"Object {objectName} does not have an image for id {counter}"); + logger.Debug($"Object {objectName} does not have an image for id {counter}"); return $"{counter}-{objectName}"; } @@ -1436,6 +1436,16 @@ void btnSave_Click(object sender, EventArgs e) return; } + if (currentUIObject is UiLocoObject obji && obji.LocoObject != null) + { + var validation = obji.LocoObject.Object.Validate(); + if (!validation) + { + logger.Error($"Object failed validation checks; cannot save"); + return; + } + } + saveFileDialog1.InitialDirectory = model.Settings.ObjDataDirectory; saveFileDialog1.DefaultExt = "dat"; saveFileDialog1.Filter = "Locomotion DAT files (.dat)|*.dat"; diff --git a/Tests/LoadSaveTests.cs b/Tests/LoadSaveTests.cs index 66f9fdf4..a851f5f2 100644 --- a/Tests/LoadSaveTests.cs +++ b/Tests/LoadSaveTests.cs @@ -249,7 +249,7 @@ void assertFunc(ILocoObject obj, CompetitorObject struc) => Assert.Multiple(() = { Assert.That(struc.var_04, Is.EqualTo(6672), nameof(struc.var_04)); Assert.That(struc.var_08, Is.EqualTo(2053), nameof(struc.var_08)); - Assert.That(struc.Emotions, Is.EqualTo(255), nameof(struc.Emotions)); + Assert.That(struc.Emotions, Is.EqualTo(511), nameof(struc.Emotions)); Assert.That(struc.Images, Is.EquivalentTo(Array.CreateInstance(typeof(byte), 9)), nameof(struc.Images)); Assert.That(struc.Intelligence, Is.EqualTo(7), nameof(struc.Intelligence)); Assert.That(struc.Aggressiveness, Is.EqualTo(5), nameof(struc.Aggressiveness)); @@ -309,46 +309,103 @@ void assertFunc(ILocoObject obj, HillShapesObject struc) => Assert.Multiple(() = LoadSaveGenericTest(objectName, assertFunc); } - [TestCase("BREWERY.DAT")] + [TestCase("CHEMWORK.DAT")] public void IndustryObject(string objectName) { void assertFunc(ILocoObject obj, IndustryObject struc) => Assert.Multiple(() => { Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name)); - // AnimationSequences + Assert.That(struc.AnimationSequences, Is.All.EqualTo(new byte[0])); Assert.That(struc.AvailableColours, Is.EqualTo(4), nameof(struc.AvailableColours)); // Buildings - Assert.That(struc.BuildingSizeFlags, Is.EqualTo(1), nameof(struc.BuildingSizeFlags)); + Assert.That(struc.BuildingSizeFlags, Is.EqualTo(7), nameof(struc.BuildingSizeFlags)); Assert.That(struc._BuildingWall, Is.EqualTo(0), nameof(struc._BuildingWall)); Assert.That(struc._BuildingWallEntrance, Is.EqualTo(0), nameof(struc._BuildingWallEntrance)); - // BuildingVariationAnimations - // BuildingVariationHeights - // BuildingVariationParts + // BuildingPartHeights + Assert.That(struc.BuildingPartHeights, Is.EqualTo(new List() { 0, 56, 0, 66, 0, 122, 0, 48, 0, 36 })); + // BuildingPartAnimations + Assert.That(struc.BuildingPartAnimations, Has.Count.EqualTo(10)); + Assert.That(struc.BuildingPartAnimations[0].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[0].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[1].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[1].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[2].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[2].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[3].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[3].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[4].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[4].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[5].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[5].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[6].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[6].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[7].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[7].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[8].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[8].AnimationSpeed, Is.EqualTo(0)); + Assert.That(struc.BuildingPartAnimations[9].NumFrames, Is.EqualTo(1)); + Assert.That(struc.BuildingPartAnimations[9].AnimationSpeed, Is.EqualTo(0)); + // BuildingParts + Assert.That(struc.BuildingParts, Has.Count.EqualTo(5)); + Assert.That(struc.BuildingParts[0], Has.Length.EqualTo(2)); + Assert.That(struc.BuildingParts[0][0], Is.EqualTo(0)); + Assert.That(struc.BuildingParts[0][1], Is.EqualTo(1)); + Assert.That(struc.BuildingParts[1], Has.Length.EqualTo(2)); + Assert.That(struc.BuildingParts[1][0], Is.EqualTo(2)); + Assert.That(struc.BuildingParts[1][1], Is.EqualTo(3)); + Assert.That(struc.BuildingParts[2], Has.Length.EqualTo(2)); + Assert.That(struc.BuildingParts[2][0], Is.EqualTo(4)); + Assert.That(struc.BuildingParts[2][1], Is.EqualTo(5)); + Assert.That(struc.BuildingParts[3], Has.Length.EqualTo(2)); + Assert.That(struc.BuildingParts[3][0], Is.EqualTo(6)); + Assert.That(struc.BuildingParts[3][1], Is.EqualTo(7)); + Assert.That(struc.BuildingParts[4], Has.Length.EqualTo(2)); + Assert.That(struc.BuildingParts[4][0], Is.EqualTo(8)); + Assert.That(struc.BuildingParts[4][1], Is.EqualTo(9)); + // Rest of object Assert.That(struc.ClearCostFactor, Is.EqualTo(240), nameof(struc.ClearCostFactor)); - Assert.That(struc.CostFactor, Is.EqualTo(320), nameof(struc.CostFactor)); + Assert.That(struc.CostFactor, Is.EqualTo(400), nameof(struc.CostFactor)); Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear)); - Assert.That(struc.Flags, Is.EqualTo(IndustryObjectFlags.BuiltOnLowGround | IndustryObjectFlags.BuiltNearWater | IndustryObjectFlags.BuiltNearTown | IndustryObjectFlags.CanBeFoundedByPlayer | IndustryObjectFlags.BuiltNearDesert), nameof(struc.Flags)); - //Assert.That(struc.InitialProductionRate, Is.EqualTo(1), nameof(struc.InitialProductionRate)); - Assert.That(struc.MaxNumBuildings, Is.EqualTo(8), nameof(struc.MaxNumBuildings)); - Assert.That(struc.MinNumBuildings, Is.EqualTo(4), nameof(struc.MinNumBuildings)); - Assert.That(struc.NumBuildingAnimations, Is.EqualTo(4), nameof(struc.NumBuildingAnimations)); - Assert.That(struc.NumBuildingVariations, Is.EqualTo(2), nameof(struc.NumBuildingVariations)); + Assert.That(struc.Flags, Is.EqualTo(IndustryObjectFlags.BuiltOnLowGround | IndustryObjectFlags.BuiltAwayFromTown | IndustryObjectFlags.unk18 | IndustryObjectFlags.unk19), nameof(struc.Flags)); + Assert.That(struc.InitialProductionRate[0].Min, Is.EqualTo(8)); + Assert.That(struc.InitialProductionRate[0].Max, Is.EqualTo(12)); + Assert.That(struc.InitialProductionRate[1].Min, Is.EqualTo(0)); + Assert.That(struc.InitialProductionRate[1].Max, Is.EqualTo(0)); + Assert.That(struc.MaxNumBuildings, Is.EqualTo(11), nameof(struc.MaxNumBuildings)); + Assert.That(struc.MinNumBuildings, Is.EqualTo(9), nameof(struc.MinNumBuildings)); + Assert.That(struc.var_1E, Is.EqualTo(10), nameof(struc.var_1E)); + Assert.That(struc.var_1F, Is.EqualTo(5), nameof(struc.var_1F)); Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); - Assert.That(struc.pad_E3, Is.EqualTo(4), nameof(struc.pad_E3)); + Assert.That(struc.pad_E3, Is.EqualTo(16), nameof(struc.pad_E3)); // ProducedCargo + Assert.That(struc.ProducedCargo, Has.Count.EqualTo(1)); + Assert.That(struc.ProducedCargo[0].Name, Is.EqualTo("CHEMICAL")); + Assert.That(struc.ProducedCargo[0].ObjectType, Is.EqualTo(ObjectType.Cargo)); // RequiredCargo + Assert.That(struc.RequiredCargo, Has.Count.EqualTo(0)); + // Rest of object Assert.That(struc.ScaffoldingColour, Is.EqualTo(Colour.grey), nameof(struc.ScaffoldingColour)); - Assert.That(struc.ScaffoldingSegmentType, Is.EqualTo(0), nameof(struc.ScaffoldingSegmentType)); - Assert.That(struc.TotalOfTypeInScenario, Is.EqualTo(2), nameof(struc.TotalOfTypeInScenario)); + Assert.That(struc.ScaffoldingSegmentType, Is.EqualTo(2), nameof(struc.ScaffoldingSegmentType)); + Assert.That(struc.TotalOfTypeInScenario, Is.EqualTo(3), nameof(struc.TotalOfTypeInScenario)); + Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E)); + Assert.That(struc.var_12, Is.EqualTo(0), nameof(struc.var_12)); + Assert.That(struc.var_16, Is.EqualTo(40), nameof(struc.var_16)); + Assert.That(struc.var_1A, Is.EqualTo(21), nameof(struc.var_1A)); Assert.That(struc.var_E8, Is.EqualTo(1), nameof(struc.var_E8)); Assert.That(struc.var_E9, Is.EqualTo(1), nameof(struc.var_E9)); Assert.That(struc.var_EA, Is.EqualTo(0), nameof(struc.var_EA)); Assert.That(struc.var_EB, Is.EqualTo(0), nameof(struc.var_EB)); Assert.That(struc.var_EC, Is.EqualTo(0), nameof(struc.var_EC)); Assert.That(struc.var_F3, Is.EqualTo(1), nameof(struc.var_F3)); + // Walls + Assert.That(struc.WallTypes, Has.Count.EqualTo(0)); // WallTypes + Assert.That(struc.BuildingWall.Name, Is.EqualTo("SECFENCE")); + Assert.That(struc.BuildingWall.ObjectType, Is.EqualTo(ObjectType.Wall)); + Assert.That(struc.BuildingWallEntrance.Name, Is.EqualTo("SECFENCG")); + Assert.That(struc.BuildingWallEntrance.ObjectType, Is.EqualTo(ObjectType.Wall)); }); LoadSaveGenericTest(objectName, assertFunc); } @@ -733,14 +790,14 @@ void assertFunc(ILocoObject obj, TrainStationObject struc) => Assert.Multiple(() // Compatible Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); Assert.That(struc.DesignedYear, Is.EqualTo(1960), nameof(struc.DesignedYear)); + Assert.That(struc.DrawStyle, Is.EqualTo(0), nameof(struc.DrawStyle)); Assert.That(struc.Flags, Is.EqualTo(TrainStationObjectFlags.None), nameof(struc.Flags)); // ManualPower + Assert.That(struc.Height, Is.EqualTo(0), nameof(struc.Height)); Assert.That(struc.NumCompatible, Is.EqualTo(0), nameof(struc.NumCompatible)); Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear)); - Assert.That(struc.PaintStyle, Is.EqualTo(0), nameof(struc.PaintStyle)); Assert.That(struc.SellCostFactor, Is.EqualTo(-7), nameof(struc.SellCostFactor)); Assert.That(struc.TrackPieces, Is.EqualTo(0), nameof(struc.TrackPieces)); - Assert.That(struc.var_03, Is.EqualTo(0), nameof(struc.var_03)); Assert.That(struc.var_0B, Is.EqualTo(2), nameof(struc.var_0B)); Assert.That(struc.var_0D, Is.EqualTo(0), nameof(struc.var_0D)); });