diff --git a/Common/IEnumerableExtensions.cs b/Common/IEnumerableExtensions.cs index 6dc8f13d..01b29a80 100644 --- a/Common/IEnumerableExtensions.cs +++ b/Common/IEnumerableExtensions.cs @@ -17,4 +17,18 @@ public static IEnumerable Fill(this IEnumerable source, int minLength, yield return fillValue; } } + + public static IEnumerable PickEach(this IEnumerable source, int period, int offset = 0) + { + var i = 0; + foreach (var item in source) + { + if (i % period == offset) + { + yield return item; + } + + i++; + } + } } diff --git a/Dat/Loaders/LevelCrossingObjectLoader.cs b/Dat/Loaders/LevelCrossingObjectLoader.cs index cffd8d19..e2a2f7bc 100644 --- a/Dat/Loaders/LevelCrossingObjectLoader.cs +++ b/Dat/Loaders/LevelCrossingObjectLoader.cs @@ -28,10 +28,10 @@ public static LocoObject Load(Stream stream) model.BuildCostFactor = br.ReadInt16(); model.SellCostFactor = br.ReadInt16(); model.CostIndex = br.ReadByte(); - model.AnimationSpeed = br.ReadByte(); - model.ClosingFrames = br.ReadByte(); - model.ClosedFrames = br.ReadByte(); - model.var_0A = br.ReadByte(); // something like IdleAnimationFrames or something + model.ClosedAnimationDelay = br.ReadByte(); + model.ClosedAnimationFrameCount = br.ReadByte(); + model.TransitionAnimationFrameCount = br.ReadByte(); + model.TransitionAnimationDelayBitmask = br.ReadByte(); br.SkipByte(); // 0x0B is padding byte model.DesignedYear = br.ReadUInt16(); br.SkipImageId(); @@ -66,10 +66,10 @@ public static void Save(Stream stream, LocoObject obj) bw.Write(model.BuildCostFactor); bw.Write(model.SellCostFactor); bw.Write(model.CostIndex); - bw.Write(model.AnimationSpeed); - bw.Write(model.ClosingFrames); - bw.Write(model.ClosedFrames); - bw.Write(model.var_0A); // something like IdleAnimationFrames + bw.Write(model.ClosedAnimationDelay); + bw.Write(model.ClosedAnimationFrameCount); + bw.Write(model.TransitionAnimationFrameCount); + bw.Write(model.TransitionAnimationDelayBitmask); bw.WriteEmptyBytes(1); // 0x0B is padding byte bw.Write(model.DesignedYear); bw.WriteEmptyImageId(); // Image offset, not part of object definition diff --git a/Definitions/DTO/Mappers/Objects/DtoObjectLevelCrossingMapper.cs b/Definitions/DTO/Mappers/Objects/DtoObjectLevelCrossingMapper.cs index 2474357f..ea96704d 100644 --- a/Definitions/DTO/Mappers/Objects/DtoObjectLevelCrossingMapper.cs +++ b/Definitions/DTO/Mappers/Objects/DtoObjectLevelCrossingMapper.cs @@ -9,9 +9,9 @@ public static class DtoObjectLevelCrossingMapper CostFactor = tblobjectlevelcrossing.CostFactor, SellCostFactor = tblobjectlevelcrossing.SellCostFactor, CostIndex = tblobjectlevelcrossing.CostIndex, - AnimationSpeed = tblobjectlevelcrossing.AnimationSpeed, - ClosingFrames = tblobjectlevelcrossing.ClosingFrames, - ClosedFrames = tblobjectlevelcrossing.ClosedFrames, + AnimationSpeed = tblobjectlevelcrossing.ClosedAnimationDelay, + ClosingFrames = tblobjectlevelcrossing.ClosedAnimationFrameCount, + ClosedFrames = tblobjectlevelcrossing.TransitionAnimationFrameCount, DesignedYear = tblobjectlevelcrossing.DesignedYear, Id = tblobjectlevelcrossing.Id, }; @@ -22,9 +22,9 @@ public static class DtoObjectLevelCrossingMapper CostFactor = model.CostFactor, SellCostFactor = model.SellCostFactor, CostIndex = model.CostIndex, - AnimationSpeed = model.AnimationSpeed, - ClosingFrames = model.ClosingFrames, - ClosedFrames = model.ClosedFrames, + ClosedAnimationDelay = model.AnimationSpeed, + ClosedAnimationFrameCount = model.ClosingFrames, + TransitionAnimationFrameCount = model.ClosedFrames, DesignedYear = model.DesignedYear, Id = model.Id, }; diff --git a/Definitions/Database/DataTables/Objects/TblObjectLevelCrossing.cs b/Definitions/Database/DataTables/Objects/TblObjectLevelCrossing.cs index e8a49ea5..426d6e8f 100644 --- a/Definitions/Database/DataTables/Objects/TblObjectLevelCrossing.cs +++ b/Definitions/Database/DataTables/Objects/TblObjectLevelCrossing.cs @@ -4,26 +4,26 @@ namespace Definitions.Database; public class TblObjectLevelCrossing : DbSubObject, IConvertibleToTable { + public uint16_t DesignedYear { get; set; } public int16_t CostFactor { get; set; } public int16_t SellCostFactor { get; set; } public uint8_t CostIndex { get; set; } - public uint8_t AnimationSpeed { get; set; } - public uint8_t ClosingFrames { get; set; } - public uint8_t ClosedFrames { get; set; } - public uint16_t DesignedYear { get; set; } - - //public uint8_t var_0A { get; set; } // something like IdleAnimationFrames or something + public uint8_t ClosedAnimationDelay { get; set; } + public uint8_t ClosedAnimationFrameCount { get; set; } + public uint8_t TransitionAnimationFrameCount { get; set; } + public uint8_t TransitionAnimationDelayBitmask { get; set; } public static TblObjectLevelCrossing FromObject(TblObject tbl, LevelCrossingObject obj) => new() { Parent = tbl, + DesignedYear = obj.DesignedYear, CostFactor = obj.BuildCostFactor, SellCostFactor = obj.SellCostFactor, CostIndex = obj.CostIndex, - AnimationSpeed = obj.AnimationSpeed, - ClosingFrames = obj.ClosingFrames, - ClosedFrames = obj.ClosedFrames, - DesignedYear = obj.DesignedYear, + ClosedAnimationDelay = obj.ClosedAnimationDelay, + ClosedAnimationFrameCount = obj.ClosedAnimationFrameCount, + TransitionAnimationFrameCount = obj.TransitionAnimationFrameCount, + TransitionAnimationDelayBitmask = obj.TransitionAnimationDelayBitmask, }; } diff --git a/Definitions/ObjectModels/Graphics/ImageTableGrouper.cs b/Definitions/ObjectModels/Graphics/ImageTableGrouper.cs index 3996baea..0413a9e4 100644 --- a/Definitions/ObjectModels/Graphics/ImageTableGrouper.cs +++ b/Definitions/ObjectModels/Graphics/ImageTableGrouper.cs @@ -1,6 +1,8 @@ +using Common; using Common.Json; using Common.Logging; using Definitions.ObjectModels.Objects.Competitor; +using Definitions.ObjectModels.Objects.LevelCrossing; using Definitions.ObjectModels.Objects.Vehicle; using Definitions.ObjectModels.Types; using Microsoft.Extensions.Logging; @@ -71,7 +73,7 @@ private static IEnumerable CreateGroups(ILocoStruct obj, Object case ObjectType.TrackSignal: return [new("", [.. imageList])]; case ObjectType.LevelCrossing: - return [new("", [.. imageList])]; + return CreateLevelCrossingGroups2((LevelCrossingObject)obj, imageList); case ObjectType.StreetLight: return CreateGroupsFromConfig(ObjectType.StreetLight, imageList); case ObjectType.Tunnel: @@ -248,6 +250,28 @@ public static void LoadGroupConfigurationJson(ILogger logger, string json) private static IReadOnlyDictionary GroupConfigurations = new Dictionary(); + private static IEnumerable CreateLevelCrossingGroups2(LevelCrossingObject model, List imageList) + { + for (var i = 0; i < 8; ++i) + { + yield return new($"{GetDirection(i)} side {i % 2}", [.. imageList.PickEach(8, i)]); + } + + static string GetDirection(int i) + => i switch + { + 0 => "SW", + 1 => "SW", + 2 => "NE", + 3 => "NE", + 4 => "SE", + 5 => "SE", + 6 => "NW", + 7 => "NW", + _ => $"direction {i}", + }; + } + private static IEnumerable CreateCompetitorGroups(CompetitorObject model, List imageList) { var offset = 0; diff --git a/Definitions/ObjectModels/Objects/LevelCrossing/LevelCrossingObject.cs b/Definitions/ObjectModels/Objects/LevelCrossing/LevelCrossingObject.cs index 7d5ae3d7..4aa95490 100644 --- a/Definitions/ObjectModels/Objects/LevelCrossing/LevelCrossingObject.cs +++ b/Definitions/ObjectModels/Objects/LevelCrossing/LevelCrossingObject.cs @@ -4,14 +4,16 @@ namespace Definitions.ObjectModels.Objects.LevelCrossing; public class LevelCrossingObject : ILocoStruct { + public uint16_t DesignedYear { get; set; } + public int16_t BuildCostFactor { get; set; } public int16_t SellCostFactor { get; set; } public uint8_t CostIndex { get; set; } - public uint8_t AnimationSpeed { get; set; } - public uint8_t ClosingFrames { get; set; } - public uint8_t ClosedFrames { get; set; } - public uint16_t DesignedYear { get; set; } - public uint8_t var_0A { get; set; } // something like IdleAnimationFrames or something + + public uint8_t ClosedAnimationDelay { get; set; } + public uint8_t ClosedAnimationFrameCount { get; set; } + public uint8_t TransitionAnimationFrameCount { get; set; } + public uint8_t TransitionAnimationDelayBitmask { get; set; } // something like IdleAnimationFrames or something public IEnumerable Validate(ValidationContext validationContext) { @@ -30,9 +32,9 @@ public IEnumerable Validate(ValidationContext validationContex yield return new ValidationResult($"-{nameof(SellCostFactor)} must be less than or equal to {nameof(BuildCostFactor)}. {nameof(BuildCostFactor)}={BuildCostFactor} {nameof(SellCostFactor)}={SellCostFactor}", [nameof(SellCostFactor), nameof(BuildCostFactor)]); } - if (ClosingFrames is not (1 or 2 or 4 or 8 or 16 or 32)) + if (ClosedAnimationFrameCount is not (1 or 2 or 4 or 8 or 16 or 32)) { - yield return new ValidationResult($"ClosingFrames must be a power of two between 1 and 32 (inclusive). {nameof(ClosingFrames)}={ClosingFrames}", [nameof(ClosingFrames)]); + yield return new ValidationResult($"ClosingFrames must be a power of two between 1 and 32 (inclusive). {nameof(ClosedAnimationFrameCount)}={ClosedAnimationFrameCount}", [nameof(ClosedAnimationFrameCount)]); } } } diff --git a/Gui/ViewModels/Loco/Objects/LevelCrossingViewModel.cs b/Gui/ViewModels/Loco/Objects/LevelCrossingViewModel.cs index a706d61c..39ac7071 100644 --- a/Gui/ViewModels/Loco/Objects/LevelCrossingViewModel.cs +++ b/Gui/ViewModels/Loco/Objects/LevelCrossingViewModel.cs @@ -1,56 +1,68 @@ using Definitions.ObjectModels.Objects.LevelCrossing; +using PropertyModels.Collections; +using System.ComponentModel; namespace Gui.ViewModels; public class LevelCrossingViewModel(LevelCrossingObject obj) : BaseViewModel(obj) { + public uint16_t DesignedYear + { + get => Model.DesignedYear; + set => Model.DesignedYear = value; + } + [Category("Cost")] public int16_t BuildCostFactor { get => Model.BuildCostFactor; set => Model.BuildCostFactor = value; } + [Category("Cost")] public int16_t SellCostFactor { get => Model.SellCostFactor; set => Model.SellCostFactor = value; } + [Category("Cost")] public uint8_t CostIndex { get => Model.CostIndex; set => Model.CostIndex = value; } - public uint8_t AnimationSpeed + [Category("Animation")] + [Description("The number of frames used in the animation of opening/closing the crossing.")] + public uint8_t TransitionAnimationFrameCount { - get => Model.AnimationSpeed; - set => Model.AnimationSpeed = value; + get => Model.TransitionAnimationFrameCount; + set => Model.TransitionAnimationFrameCount = value; } - public uint8_t ClosingFrames + [Category("Animation")] + [Description("Used a bitmask on `getScenarioTicks()` in OpenLoco. This value is the 'number of ticks to skip before advancing the transition animation'. A value of 0 means the animation advances every tick. 1 means every every 2 ticks. 3 means ever 4 ticks. 7 means every 8 ticks. 15 means every 16 ticks. etc.")] + public SelectableList TransitionAnimationDelayBitmask { - get => Model.ClosingFrames; - set => Model.ClosingFrames = value; + get => new([0, 1, 3, 7, 15, 31, 63, 127, 255]) { SelectedValue = Model.TransitionAnimationDelayBitmask }; + set => Model.TransitionAnimationDelayBitmask = value.SelectedValue; } - public uint8_t ClosedFrames + [Category("Animation")] + [Description("The number of frames used in the animation of a closed/blocked crossing. Must be one of [1, 2, 4, 8, 16, 32]")] + public SelectableList ClosedAnimationFrameCount { - get => Model.ClosedFrames; - set => Model.ClosedFrames = value; - } - - public uint16_t DesignedYear - { - get => Model.DesignedYear; - set => Model.DesignedYear = value; + get => new([1, 2, 4, 8, 16, 32]) { SelectedValue = Model.ClosedAnimationFrameCount }; + set => Model.ClosedAnimationFrameCount = value.SelectedValue; } - public uint8_t var_0A + [Category("Animation")] + [Description("The delay between animation frames for a closed/blocked crossing. Higher values mean slower animations.")] + public uint8_t ClosedAnimationDelay { - get => Model.var_0A; - set => Model.var_0A = value; + get => Model.ClosedAnimationDelay; + set => Model.ClosedAnimationDelay = value; } } diff --git a/Tests/LoadSaveTests.cs b/Tests/LoadSaveTests.cs index 574b34e2..3d9c3de3 100644 --- a/Tests/LoadSaveTests.cs +++ b/Tests/LoadSaveTests.cs @@ -731,11 +731,11 @@ static void assertFunc(LocoObject obj, LevelCrossingObject struc) Assert.That(struc.SellCostFactor, Is.EqualTo(-10), nameof(struc.SellCostFactor)); Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex)); - Assert.That(struc.AnimationSpeed, Is.EqualTo(3), nameof(struc.AnimationSpeed)); - Assert.That(struc.ClosingFrames, Is.EqualTo(4), nameof(struc.ClosingFrames)); - Assert.That(struc.ClosedFrames, Is.EqualTo(11), nameof(struc.ClosedFrames)); + Assert.That(struc.ClosedAnimationDelay, Is.EqualTo(3), nameof(struc.ClosedAnimationDelay)); + Assert.That(struc.ClosedAnimationFrameCount, Is.EqualTo(4), nameof(struc.ClosedAnimationFrameCount)); + Assert.That(struc.TransitionAnimationFrameCount, Is.EqualTo(11), nameof(struc.TransitionAnimationFrameCount)); - Assert.That(struc.var_0A, Is.EqualTo(3), nameof(struc.var_0A)); + Assert.That(struc.TransitionAnimationDelayBitmask, Is.EqualTo(3), nameof(struc.TransitionAnimationDelayBitmask)); Assert.That(struc.DesignedYear, Is.EqualTo(1955), nameof(struc.DesignedYear));