From 01cc27532ad70a8f6be52a16e2609efe1a14ea7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 13:53:20 +0000 Subject: [PATCH 1/8] Initial plan From 3e59d1896207712ac3e6068a97166d0418afac58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 13:59:09 +0000 Subject: [PATCH 2/8] Add Economy class with inflation calculation logic and tests Co-authored-by: LeftofZen <7483209+LeftofZen@users.noreply.github.com> --- Common/Economy.cs | 76 ++++++++++++++++++++++++++++++++++ Tests/EconomyTests.cs | 96 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 Common/Economy.cs create mode 100644 Tests/EconomyTests.cs diff --git a/Common/Economy.cs b/Common/Economy.cs new file mode 100644 index 00000000..6128ddad --- /dev/null +++ b/Common/Economy.cs @@ -0,0 +1,76 @@ +namespace Common; + +/// +/// Provides economy-related calculations for Locomotion, including inflation and cost calculations. +/// Based on OpenLoco's Economy.cpp: https://github.com/OpenLoco/OpenLoco/blob/master/src/OpenLoco/src/Economy/Economy.cpp +/// +public static class Economy +{ + // Inflation factors from OpenLoco (kInflationFactors) + private static readonly uint[] InflationFactors = + [ + 20, 20, 20, 20, 23, 20, 23, 23, + 20, 17, 17, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20 + ]; + + /// + /// Calculates the currency multiplication factors for all 32 cost indices for a given year. + /// + /// The year to calculate inflation for (clamped to 1900-2030 range) + /// An array of 32 currency multiplication factors + public static uint[] CalculateCurrencyMultiplicationFactors(int year) + { + var factors = new uint[32]; + + // Initialize all factors to 1024 (base value) + for (var i = 0; i < 32; i++) + { + factors[i] = 1024; + } + + // OpenLoco allows 1800 as the minimum year, whereas Locomotion uses 1900. + // Treat years before 1900 as though they were 1900 to not change vanilla scenarios. + var baseYear = Math.Clamp(year, 1900, 2030) - 1900; + var monthCount = baseYear * 12; + + // Apply inflation for each month + for (var month = 0; month < monthCount; month++) + { + for (var i = 0; i < 32; i++) + { + factors[i] += (uint)((ulong)InflationFactors[i] * factors[i] >> 12); + } + } + + return factors; + } + + /// + /// Calculates the inflation-adjusted cost for a given cost factor and cost index. + /// + /// The base cost factor from the object + /// The cost index (0-31) + /// The year to calculate the cost for + /// The divisor to apply (default is 10, which is common for most objects) + /// The inflation-adjusted cost + public static int GetInflationAdjustedCost(short costFactor, byte costIndex, int year, byte divisor = 10) + { + if (costIndex >= 32) + { + throw new ArgumentOutOfRangeException(nameof(costIndex), "Cost index must be between 0 and 31"); + } + + if (divisor >= 63) + { + throw new ArgumentOutOfRangeException(nameof(divisor), "Divisor must be less than 63"); + } + + var factors = CalculateCurrencyMultiplicationFactors(year); + var val = costFactor * (long)factors[costIndex]; + var result = val / (1L << divisor); + + return (int)result; + } +} diff --git a/Tests/EconomyTests.cs b/Tests/EconomyTests.cs new file mode 100644 index 00000000..cb96268c --- /dev/null +++ b/Tests/EconomyTests.cs @@ -0,0 +1,96 @@ +using NUnit.Framework; + +namespace Common.Tests; + +[TestFixture] +public class EconomyTests +{ + [Test] + public void CalculateCurrencyMultiplicationFactors_Year1900_ReturnsBaseFactors() + { + // Year 1900 should return base factors (1024 for all indices) + var factors = Economy.CalculateCurrencyMultiplicationFactors(1900); + + Assert.That(factors, Has.Length.EqualTo(32)); + foreach (var factor in factors) + { + Assert.That(factor, Is.EqualTo(1024)); + } + } + + [Test] + public void CalculateCurrencyMultiplicationFactors_YearBefore1900_ReturnsSameAs1900() + { + // Years before 1900 should be treated as 1900 + var factors1800 = Economy.CalculateCurrencyMultiplicationFactors(1800); + var factors1900 = Economy.CalculateCurrencyMultiplicationFactors(1900); + + Assert.That(factors1800, Is.EqualTo(factors1900)); + } + + [Test] + public void CalculateCurrencyMultiplicationFactors_Year1901_HasInflation() + { + // Year 1901 should have inflation applied (12 months) + var factors = Economy.CalculateCurrencyMultiplicationFactors(1901); + + Assert.That(factors, Has.Length.EqualTo(32)); + // All factors should be greater than base value due to inflation + foreach (var factor in factors) + { + Assert.That(factor, Is.GreaterThan(1024)); + } + } + + [Test] + public void CalculateCurrencyMultiplicationFactors_Year2030_ClampsCorrectly() + { + // Year 2030 is the max, so 2031 should be treated as 2030 + var factors2030 = Economy.CalculateCurrencyMultiplicationFactors(2030); + var factors2031 = Economy.CalculateCurrencyMultiplicationFactors(2031); + + Assert.That(factors2031, Is.EqualTo(factors2030)); + } + + [Test] + public void GetInflationAdjustedCost_Year1900_ReturnsBaseCost() + { + // For year 1900, with factor 1024 and divisor 10, we should get: + // cost = (100 * 1024) / (1 << 10) = 102400 / 1024 = 100 + var cost = Economy.GetInflationAdjustedCost(100, 0, 1900, 10); + Assert.That(cost, Is.EqualTo(100)); + } + + [Test] + public void GetInflationAdjustedCost_Year1901_HasHigherCost() + { + // For year 1901, cost should be higher than year 1900 due to inflation + var cost1900 = Economy.GetInflationAdjustedCost(100, 0, 1900, 10); + var cost1901 = Economy.GetInflationAdjustedCost(100, 0, 1901, 10); + + Assert.That(cost1901, Is.GreaterThan(cost1900)); + } + + [Test] + public void GetInflationAdjustedCost_NegativeFactor_ReturnsNegativeCost() + { + // Sell costs use negative factors + var cost = Economy.GetInflationAdjustedCost(-50, 0, 1900, 10); + Assert.That(cost, Is.LessThan(0)); + Assert.That(cost, Is.EqualTo(-50)); + } + + [Test] + public void GetInflationAdjustedCost_InvalidCostIndex_ThrowsException() + { + Assert.Throws(() => + Economy.GetInflationAdjustedCost(100, 32, 1900, 10)); + } + + [Test] + public void GetInflationAdjustedCost_InvalidDivisor_ThrowsException() + { + Assert.Throws(() => + Economy.GetInflationAdjustedCost(100, 0, 1900, 63)); + } +} From b603f70d120bc571b3bea02153cec80be4ae5407 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:01:59 +0000 Subject: [PATCH 3/8] Add inflation year setting and effective cost display for Track and Bridge objects Co-authored-by: LeftofZen <7483209+LeftofZen@users.noreply.github.com> --- Gui/EditorSettings.cs | 1 + Gui/GlobalSettings.cs | 10 +++++++ Gui/Models/ObjectEditorModel.cs | 1 + .../EditorSettingsWindowViewModel.cs | 7 +++++ .../LocoTypes/Objects/BridgeViewModel.cs | 30 +++++++++++++++++++ .../LocoTypes/Objects/TrackViewModel.cs | 30 +++++++++++++++++++ 6 files changed, 79 insertions(+) create mode 100644 Gui/GlobalSettings.cs diff --git a/Gui/EditorSettings.cs b/Gui/EditorSettings.cs index f93bf1d3..83b4fa04 100644 --- a/Gui/EditorSettings.cs +++ b/Gui/EditorSettings.cs @@ -32,6 +32,7 @@ public HashSet ObjDataDirectories public bool EnableOGValidation { get; set; } public bool ShowLogsOnError { get; set; } public bool AutoObjectDiscoveryAndUpload { get; set; } + public int InflationYear { get; set; } = 1950; public bool UseHttps { get; set; } public string ServerAddressHttp { get; set; } = "http://openloco.leftofzen.dev/"; diff --git a/Gui/GlobalSettings.cs b/Gui/GlobalSettings.cs new file mode 100644 index 00000000..de0bec33 --- /dev/null +++ b/Gui/GlobalSettings.cs @@ -0,0 +1,10 @@ +namespace Gui; + +/// +/// Provides global access to editor settings for use in ViewModels. +/// This is primarily used for read-only access to settings like the inflation year. +/// +public static class GlobalSettings +{ + public static EditorSettings? CurrentSettings { get; set; } +} diff --git a/Gui/Models/ObjectEditorModel.cs b/Gui/Models/ObjectEditorModel.cs index 34890c03..1425d7dd 100644 --- a/Gui/Models/ObjectEditorModel.cs +++ b/Gui/Models/ObjectEditorModel.cs @@ -124,6 +124,7 @@ async Task WriteLogsToFileAsync() void LoadSettings() { Settings = EditorSettings.Load(SettingsFile, Logger); + GlobalSettings.CurrentSettings = Settings; if (Settings.Validate(Logger)) { diff --git a/Gui/ViewModels/EditorSettingsWindowViewModel.cs b/Gui/ViewModels/EditorSettingsWindowViewModel.cs index ea195682..8baa8274 100644 --- a/Gui/ViewModels/EditorSettingsWindowViewModel.cs +++ b/Gui/ViewModels/EditorSettingsWindowViewModel.cs @@ -43,6 +43,13 @@ public bool ShowLogsOnError set => Model.ShowLogsOnError = value; } + [Category("Misc"), DisplayName("Inflation Year"), Description("The in-game year to use when calculating inflation-adjusted costs for objects. Defaults to 1950.")] + public int InflationYear + { + get => Model.InflationYear; + set => Model.InflationYear = value; + } + #region Object Folders const string GameObjectFolderCategory = "Folders OpenLoco can use objects from"; diff --git a/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs index 5a860eab..00dbaaab 100644 --- a/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs @@ -92,6 +92,36 @@ public int16_t SellCostFactor set => Model.SellCostFactor = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Base Cost"), Description("The inflation-adjusted base cost for the year specified in settings")] + public int EffectiveBaseCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BaseCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Height Cost"), Description("The inflation-adjusted height cost factor for the year specified in settings")] + public int EffectiveHeightCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.HeightCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + [Category("Compatible")] public BindingList CompatibleTrackObjects { get; init; } = new(model.CompatibleTrackObjects); diff --git a/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs index caef060f..b01b200f 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs @@ -71,6 +71,36 @@ public uint8_t CostIndex set => Model.CostIndex = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Tunnel Cost"), Description("The inflation-adjusted tunnel cost for the year specified in settings")] + public int EffectiveTunnelCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.TunnelCostFactor, Model.CostIndex, year); + } + } + [Category("")] public uint8_t var_06 { From bff4e516a874f5e7e35837a4bdb38c0327e816d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:06:23 +0000 Subject: [PATCH 4/8] Add effective cost properties to all remaining object ViewModels Co-authored-by: LeftofZen <7483209+LeftofZen@users.noreply.github.com> --- .../LocoTypes/Objects/AirportViewModel.cs | 20 +++++++++++++ .../LocoTypes/Objects/DockViewModel.cs | 20 +++++++++++++ .../LocoTypes/Objects/IndustryViewModel.cs | 20 +++++++++++++ .../LocoTypes/Objects/RoadExtraViewModel.cs | 24 +++++++++++++++ .../LocoTypes/Objects/RoadStationViewModel.cs | 20 +++++++++++++ .../LocoTypes/Objects/RoadViewModel.cs | 30 +++++++++++++++++++ .../LocoTypes/Objects/TrackExtraViewModel.cs | 24 +++++++++++++++ .../LocoTypes/Objects/TrackSignalViewModel.cs | 20 +++++++++++++ .../Objects/TrackStationViewModel.cs | 20 +++++++++++++ .../LocoTypes/Objects/TreeViewModel.cs | 20 +++++++++++++ .../LocoTypes/Objects/VehicleViewModel.cs | 20 +++++++++++++ 11 files changed, 238 insertions(+) diff --git a/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs index b8672ce6..f78eed02 100644 --- a/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs @@ -82,6 +82,26 @@ public int16_t SellCostFactor set => Model.SellCostFactor = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + [Category("Building")] [Length(1, AirportObjectLoader.Constants.BuildingVariationCount)] public BindingList> BuildingVariations { get; init; } = new(model.BuildingComponents.BuildingVariations.Select(x => x.ToBindingList()).ToBindingList()); diff --git a/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs index 9671277a..9c163b0a 100644 --- a/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs @@ -59,6 +59,26 @@ public int16_t SellCostFactor set => Model.SellCostFactor = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + [Category("Building")] [Length(1, DockObjectLoader.Constants.BuildingVariationCount)] public BindingList> BuildingVariations { get; init; } = new(model.BuildingComponents.BuildingVariations.Select(x => x.ToBindingList()).ToBindingList()); diff --git a/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs index fe3e2bf2..6595e863 100644 --- a/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs @@ -93,6 +93,26 @@ public int16_t SellCostFactor set => Model.SellCostFactor = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + [Category("Farm")] public uint8_t FarmTileNumImageAngles { diff --git a/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs index ec90d80c..bbfa5a04 100644 --- a/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs @@ -1,6 +1,7 @@ using Definitions.ObjectModels.Objects.Road; using Definitions.ObjectModels.Objects.RoadExtra; using PropertyModels.ComponentModel.DataAnnotations; +using System.ComponentModel; namespace Gui.ViewModels; @@ -20,21 +21,44 @@ public uint8_t PaintStyle set => Model.PaintStyle = value; } + [Category("Cost")] public uint8_t CostIndex { get => Model.CostIndex; set => Model.CostIndex = 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"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } } diff --git a/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs index b1d62cb7..7c9e25cf 100644 --- a/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs @@ -70,6 +70,26 @@ public uint8_t CostIndex set => Model.CostIndex = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + [Category("Cargo")] public ObjectModelHeader? CargoType { diff --git a/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs index 2cdf9604..0d99ca84 100644 --- a/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs @@ -75,6 +75,36 @@ public uint8_t CostIndex set => Model.CostIndex = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Tunnel Cost"), Description("The inflation-adjusted tunnel cost for the year specified in settings")] + public int EffectiveTunnelCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.TunnelCostFactor, Model.CostIndex, year); + } + } + [Category("Compatible Objects")] public ObjectModelHeader Tunnel { diff --git a/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs index fba89e15..b9aa8e05 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs @@ -1,6 +1,7 @@ using Definitions.ObjectModels.Objects.Track; using Definitions.ObjectModels.Objects.TrackExtra; using PropertyModels.ComponentModel.DataAnnotations; +using System.ComponentModel; namespace Gui.ViewModels; @@ -20,21 +21,44 @@ public uint8_t PaintStyle set => Model.PaintStyle = value; } + [Category("Cost")] public uint8_t CostIndex { get => Model.CostIndex; set => Model.CostIndex = 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"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } } diff --git a/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs index 2e5d1f50..e67cb3d9 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs @@ -50,6 +50,26 @@ public int16_t SellCostFactor set => Model.SellCostFactor = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + [Category("Stats")] public uint16_t DesignedYear { diff --git a/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs index 8e22626c..38822449 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs @@ -69,6 +69,26 @@ public uint8_t CostIndex set => Model.CostIndex = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] + public int EffectiveSellCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); + } + } + [Category("")] public uint8_t var_0B { diff --git a/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs index a657bdf5..847f9c68 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs @@ -65,6 +65,26 @@ public int16_t ClearCostFactor set => Model.ClearCostFactor = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Clear Cost"), Description("The inflation-adjusted clear cost for the year specified in settings")] + public int EffectiveClearCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(Model.ClearCostFactor, Model.CostIndex, year); + } + } + [Category("Building")] public uint8_t InitialHeight { diff --git a/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs index ac353d5f..e470996b 100644 --- a/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs @@ -277,6 +277,26 @@ public int16_t RunCostFactor set => model.RunCostFactor = value; } + [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted purchase cost for the year specified in settings")] + public int EffectiveBuildCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(model.CostFactor, model.CostIndex, year); + } + } + + [Category("Cost"), ReadOnly(true), DisplayName("Effective Run Cost"), Description("The inflation-adjusted running cost for the year specified in settings")] + public int EffectiveRunCost + { + get + { + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + return Common.Economy.GetInflationAdjustedCost(model.RunCostFactor, model.RunCostIndex, year); + } + } + [Category("Sprites")] public CompanyColourType SpecialColourSchemeIndex { From 5831480c37a74f14341bda9319a5cb4432d9fc84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:07:53 +0000 Subject: [PATCH 5/8] Add comprehensive tests for effective cost calculations in ViewModels Co-authored-by: LeftofZen <7483209+LeftofZen@users.noreply.github.com> --- Tests/EffectiveCostTests.cs | 106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 Tests/EffectiveCostTests.cs diff --git a/Tests/EffectiveCostTests.cs b/Tests/EffectiveCostTests.cs new file mode 100644 index 00000000..bcea4794 --- /dev/null +++ b/Tests/EffectiveCostTests.cs @@ -0,0 +1,106 @@ +using Definitions.ObjectModels.Objects.Track; +using Definitions.ObjectModels.Objects.Bridge; +using Gui; +using Gui.ViewModels; +using NUnit.Framework; + +namespace Gui.Tests; + +[TestFixture] +public class EffectiveCostTests +{ + [SetUp] + public void Setup() + { + // Set up global settings + GlobalSettings.CurrentSettings = new EditorSettings + { + InflationYear = 1950 + }; + } + + [Test] + public void TrackViewModel_CalculatesEffectiveCosts() + { + var trackObject = new TrackObject + { + BuildCostFactor = 100, + SellCostFactor = -50, + TunnelCostFactor = 200, + CostIndex = 0 + }; + + var viewModel = new TrackViewModel(trackObject); + + // For year 1950, with factor 1024 and divisor 10, we should get: (100 * X) / 1024 + // where X is the inflation-adjusted currency multiplication factor + Assert.That(viewModel.EffectiveBuildCost, Is.GreaterThan(0)); + Assert.That(viewModel.EffectiveSellCost, Is.LessThan(0)); + Assert.That(viewModel.EffectiveTunnelCost, Is.GreaterThan(0)); + + // Build cost should be proportional to the factors + Assert.That(viewModel.EffectiveTunnelCost, Is.GreaterThan(viewModel.EffectiveBuildCost)); + } + + [Test] + public void BridgeViewModel_CalculatesEffectiveCosts() + { + var bridgeObject = new BridgeObject + { + BaseCostFactor = 150, + HeightCostFactor = 50, + SellCostFactor = -75, + CostIndex = 1 + }; + + var viewModel = new BridgeViewModel(bridgeObject); + + Assert.That(viewModel.EffectiveBaseCost, Is.GreaterThan(0)); + Assert.That(viewModel.EffectiveHeightCost, Is.GreaterThan(0)); + Assert.That(viewModel.EffectiveSellCost, Is.LessThan(0)); + + // Base cost should be greater than height cost + Assert.That(viewModel.EffectiveBaseCost, Is.GreaterThan(viewModel.EffectiveHeightCost)); + } + + [Test] + public void InflationYear_AffectsCosts() + { + var trackObject = new TrackObject + { + BuildCostFactor = 100, + CostIndex = 0 + }; + + // Test with year 1900 (no inflation) + GlobalSettings.CurrentSettings!.InflationYear = 1900; + var viewModel1900 = new TrackViewModel(trackObject); + var cost1900 = viewModel1900.EffectiveBuildCost; + + // Test with year 2000 (with inflation) + GlobalSettings.CurrentSettings!.InflationYear = 2000; + var viewModel2000 = new TrackViewModel(trackObject); + var cost2000 = viewModel2000.EffectiveBuildCost; + + // Cost in 2000 should be higher due to inflation + Assert.That(cost2000, Is.GreaterThan(cost1900)); + } + + [Test] + public void GlobalSettings_DefaultYear_WorksCorrectly() + { + // Test that if GlobalSettings is null, default year is used + GlobalSettings.CurrentSettings = null; + + var trackObject = new TrackObject + { + BuildCostFactor = 100, + CostIndex = 0 + }; + + var viewModel = new TrackViewModel(trackObject); + + // Should not throw and should return a valid cost + Assert.That(viewModel.EffectiveBuildCost, Is.GreaterThan(0)); + } +} From b9f4c6561fe2063b3780300823888831caababb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Dec 2025 01:28:01 +0000 Subject: [PATCH 6/8] Implement custom PropertyGrid cell editor for currency properties with inline inflation adjustment - Created CurrencyAttribute to mark cost factor properties - Created CurrencyEditorViewModel and CurrencyView for inline editing - Created CurrencyCellEditFactory in ExtendedPropertyGrid - Updated all 13 object ViewModels to use Currency attribute - Removed separate read-only effective cost properties - Cost properties now show: Factor input, Year stepper, and calculated effective cost inline Co-authored-by: LeftofZen <7483209+LeftofZen@users.noreply.github.com> --- Gui/Attributes/CurrencyAttribute.cs | 17 +++ Gui/ViewModels/CurrencyEditorViewModel.cs | 52 +++++++++ .../LocoTypes/Objects/AirportViewModel.cs | 33 +----- .../LocoTypes/Objects/BridgeViewModel.cs | 37 +----- .../LocoTypes/Objects/DockViewModel.cs | 33 +----- .../LocoTypes/Objects/IndustryViewModel.cs | 33 +----- .../LocoTypes/Objects/RoadExtraViewModel.cs | 33 +----- .../LocoTypes/Objects/RoadStationViewModel.cs | 40 +------ .../LocoTypes/Objects/RoadViewModel.cs | 56 +-------- .../LocoTypes/Objects/TrackExtraViewModel.cs | 33 +----- .../LocoTypes/Objects/TrackSignalViewModel.cs | 33 +----- .../Objects/TrackStationViewModel.cs | 40 +------ .../LocoTypes/Objects/TrackViewModel.cs | 37 +----- .../LocoTypes/Objects/TreeViewModel.cs | 33 +----- .../LocoTypes/Objects/VehicleViewModel.cs | 19 +--- Gui/Views/CurrencyView.axaml | 44 ++++++++ Gui/Views/CurrencyView.axaml.cs | 5 + Gui/Views/ExtendedPropertyGrid.cs | 87 ++++++++++++++ Tests/EffectiveCostTests.cs | 106 ------------------ 19 files changed, 224 insertions(+), 547 deletions(-) create mode 100644 Gui/Attributes/CurrencyAttribute.cs create mode 100644 Gui/ViewModels/CurrencyEditorViewModel.cs create mode 100644 Gui/Views/CurrencyView.axaml create mode 100644 Gui/Views/CurrencyView.axaml.cs delete mode 100644 Tests/EffectiveCostTests.cs diff --git a/Gui/Attributes/CurrencyAttribute.cs b/Gui/Attributes/CurrencyAttribute.cs new file mode 100644 index 00000000..a32ecb9e --- /dev/null +++ b/Gui/Attributes/CurrencyAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace Gui.Attributes; + +/// +/// Marks a property as a currency value that should display with inflation adjustment. +/// The property must be of type int16_t and have a corresponding CostIndex property. +/// +[AttributeUsage(AttributeTargets.Property)] +public class CurrencyAttribute : Attribute +{ + /// + /// The name of the property that contains the CostIndex for this currency value. + /// Defaults to "CostIndex". + /// + public string CostIndexPropertyName { get; set; } = "CostIndex"; +} diff --git a/Gui/ViewModels/CurrencyEditorViewModel.cs b/Gui/ViewModels/CurrencyEditorViewModel.cs new file mode 100644 index 00000000..bb4be485 --- /dev/null +++ b/Gui/ViewModels/CurrencyEditorViewModel.cs @@ -0,0 +1,52 @@ +using ReactiveUI; + +namespace Gui.ViewModels; + +public class CurrencyEditorViewModel : ReactiveObject +{ + private short _costFactor; + private byte _costIndex; + private int _year = 1950; + + public short CostFactor + { + get => _costFactor; + set + { + this.RaiseAndSetIfChanged(ref _costFactor, value); + this.RaisePropertyChanged(nameof(EffectiveCost)); + } + } + + public byte CostIndex + { + get => _costIndex; + set + { + this.RaiseAndSetIfChanged(ref _costIndex, value); + this.RaisePropertyChanged(nameof(EffectiveCost)); + } + } + + public int Year + { + get => _year; + set + { + this.RaiseAndSetIfChanged(ref _year, value); + this.RaisePropertyChanged(nameof(EffectiveCost)); + } + } + + public int EffectiveCost + { + get + { + if (_costIndex >= 32) + { + return 0; + } + return Common.Economy.GetInflationAdjustedCost(_costFactor, _costIndex, _year); + } + } +} diff --git a/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs index f78eed02..c551e810 100644 --- a/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs @@ -1,6 +1,7 @@ using Dat.Loaders; using Definitions.ObjectModels.Objects.Airport; using Definitions.ObjectModels.Objects.Common; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using PropertyModels.Extensions; using System.ComponentModel; @@ -68,39 +69,7 @@ public uint8_t CostIndex set => Model.CostIndex = 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"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } [Category("Building")] [Length(1, AirportObjectLoader.Constants.BuildingVariationCount)] diff --git a/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs index 00dbaaab..506ad2ab 100644 --- a/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs @@ -1,5 +1,6 @@ using Definitions.ObjectModels.Objects.Bridge; using Definitions.ObjectModels.Types; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; @@ -71,57 +72,27 @@ public uint8_t CostIndex set => Model.CostIndex = value; } - [Category("Cost")] + [Category("Cost"), Currency] public int16_t BaseCostFactor { get => Model.BaseCostFactor; set => Model.BaseCostFactor = value; } - [Category("Cost")] + [Category("Cost"), Currency] public int16_t HeightCostFactor { get => Model.HeightCostFactor; set => Model.HeightCostFactor = value; } - [Category("Cost")] + [Category("Cost"), Currency] public int16_t SellCostFactor { get => Model.SellCostFactor; set => Model.SellCostFactor = value; } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Base Cost"), Description("The inflation-adjusted base cost for the year specified in settings")] - public int EffectiveBaseCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BaseCostFactor, Model.CostIndex, year); - } - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Height Cost"), Description("The inflation-adjusted height cost factor for the year specified in settings")] - public int EffectiveHeightCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.HeightCostFactor, Model.CostIndex, year); - } - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } - [Category("Compatible")] public BindingList CompatibleTrackObjects { get; init; } = new(model.CompatibleTrackObjects); diff --git a/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs index 9c163b0a..32e0116b 100644 --- a/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs @@ -2,6 +2,7 @@ using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Objects.Dock; using Definitions.ObjectModels.Types; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using PropertyModels.Extensions; using System.ComponentModel; @@ -45,39 +46,7 @@ public uint8_t CostIndex set => Model.CostIndex = 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"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } [Category("Building")] [Length(1, DockObjectLoader.Constants.BuildingVariationCount)] diff --git a/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs index 6595e863..49989129 100644 --- a/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs @@ -3,6 +3,7 @@ using Definitions.ObjectModels.Objects.Common; using Definitions.ObjectModels.Objects.Industry; using Definitions.ObjectModels.Types; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using PropertyModels.Extensions; using System.ComponentModel; @@ -79,39 +80,7 @@ public uint8_t CostIndex set => Model.CostIndex = 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"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } [Category("Farm")] public uint8_t FarmTileNumImageAngles diff --git a/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs index bbfa5a04..0403b065 100644 --- a/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/RoadExtraViewModel.cs @@ -1,5 +1,6 @@ using Definitions.ObjectModels.Objects.Road; using Definitions.ObjectModels.Objects.RoadExtra; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; @@ -28,37 +29,5 @@ public uint8_t CostIndex set => Model.CostIndex = 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"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } } diff --git a/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs index 7c9e25cf..b4ca113e 100644 --- a/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/RoadStationViewModel.cs @@ -2,6 +2,7 @@ using Definitions.ObjectModels.Objects.RoadStation; using Definitions.ObjectModels.Objects.Shared; using Definitions.ObjectModels.Types; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; @@ -49,46 +50,7 @@ public RoadStationObjectFlags Flags set => Model.Flags = 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; - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } [Category("Cargo")] public ObjectModelHeader? CargoType diff --git a/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs index 0d99ca84..d7e2ed53 100644 --- a/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/RoadViewModel.cs @@ -1,5 +1,6 @@ using Definitions.ObjectModels.Objects.Road; using Definitions.ObjectModels.Types; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; @@ -47,63 +48,8 @@ public TownSize TargetTownSize set => Model.TargetTownSize = 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 int16_t TunnelCostFactor - { - get => Model.TunnelCostFactor; - set => Model.TunnelCostFactor = value; - } - - [Category("Cost")] - public uint8_t CostIndex - { - get => Model.CostIndex; - set => Model.CostIndex = value; - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Tunnel Cost"), Description("The inflation-adjusted tunnel cost for the year specified in settings")] - public int EffectiveTunnelCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.TunnelCostFactor, Model.CostIndex, year); - } - } [Category("Compatible Objects")] public ObjectModelHeader Tunnel diff --git a/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs index b9aa8e05..91f60102 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TrackExtraViewModel.cs @@ -1,5 +1,6 @@ using Definitions.ObjectModels.Objects.Track; using Definitions.ObjectModels.Objects.TrackExtra; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; @@ -28,37 +29,5 @@ public uint8_t CostIndex set => Model.CostIndex = 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"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } } diff --git a/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs index e67cb3d9..52ab4771 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TrackSignalViewModel.cs @@ -1,6 +1,7 @@ using Dat.Loaders; using Definitions.ObjectModels.Objects.TrackSignal; using Definitions.ObjectModels.Types; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; using System.ComponentModel.DataAnnotations; @@ -36,39 +37,7 @@ public uint8_t CostIndex set => Model.CostIndex = 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"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } [Category("Stats")] public uint16_t DesignedYear diff --git a/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs index 38822449..17931cae 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TrackStationViewModel.cs @@ -2,6 +2,7 @@ using Definitions.ObjectModels.Objects.Track; using Definitions.ObjectModels.Objects.TrackStation; using Definitions.ObjectModels.Types; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; @@ -48,46 +49,7 @@ public TrackStationObjectFlags Flags set => Model.Flags = 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; - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } [Category("")] public uint8_t var_0B diff --git a/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs index b01b200f..1eebb2f5 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TrackViewModel.cs @@ -1,5 +1,6 @@ using Definitions.ObjectModels.Objects.Track; using Definitions.ObjectModels.Types; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; using TrackObject = Definitions.ObjectModels.Objects.Track.TrackObject; @@ -43,21 +44,21 @@ public uint8_t VehicleDisplayListVerticalOffset set => Model.VehicleDisplayListVerticalOffset = value; } - [Category("Cost")] + [Category("Cost"), Currency] public int16_t BuildCostFactor { get => Model.BuildCostFactor; set => Model.BuildCostFactor = value; } - [Category("Cost")] + [Category("Cost"), Currency] public int16_t SellCostFactor { get => Model.SellCostFactor; set => Model.SellCostFactor = value; } - [Category("Cost")] + [Category("Cost"), Currency] public int16_t TunnelCostFactor { get => Model.TunnelCostFactor; @@ -71,36 +72,6 @@ public uint8_t CostIndex set => Model.CostIndex = value; } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Sell Cost"), Description("The inflation-adjusted sell cost for the year specified in settings")] - public int EffectiveSellCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.SellCostFactor, Model.CostIndex, year); - } - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Tunnel Cost"), Description("The inflation-adjusted tunnel cost for the year specified in settings")] - public int EffectiveTunnelCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.TunnelCostFactor, Model.CostIndex, year); - } - } - [Category("")] public uint8_t var_06 { diff --git a/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs index 847f9c68..ea8bffba 100644 --- a/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/TreeViewModel.cs @@ -1,4 +1,5 @@ using Definitions.ObjectModels.Objects.Tree; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using System.ComponentModel; @@ -51,39 +52,7 @@ public uint8_t CostIndex set => Model.CostIndex = value; } - [Category("Cost")] - public int16_t BuildCostFactor - { - get => Model.BuildCostFactor; - set => Model.BuildCostFactor = value; - } - - [Category("Cost")] - public int16_t ClearCostFactor - { - get => Model.ClearCostFactor; - set => Model.ClearCostFactor = value; - } - - [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted build cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.BuildCostFactor, Model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Clear Cost"), Description("The inflation-adjusted clear cost for the year specified in settings")] - public int EffectiveClearCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(Model.ClearCostFactor, Model.CostIndex, year); - } - } [Category("Building")] public uint8_t InitialHeight diff --git a/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs b/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs index e470996b..02ecbc47 100644 --- a/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs +++ b/Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs @@ -4,6 +4,7 @@ using Definitions.ObjectModels.Types; using DynamicData.Binding; using PropertyModels.ComponentModel; +using Gui.Attributes; using PropertyModels.ComponentModel.DataAnnotations; using PropertyModels.Extensions; using ReactiveUI; @@ -277,25 +278,7 @@ public int16_t RunCostFactor set => model.RunCostFactor = value; } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Build Cost"), Description("The inflation-adjusted purchase cost for the year specified in settings")] - public int EffectiveBuildCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(model.CostFactor, model.CostIndex, year); - } - } - [Category("Cost"), ReadOnly(true), DisplayName("Effective Run Cost"), Description("The inflation-adjusted running cost for the year specified in settings")] - public int EffectiveRunCost - { - get - { - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; - return Common.Economy.GetInflationAdjustedCost(model.RunCostFactor, model.RunCostIndex, year); - } - } [Category("Sprites")] public CompanyColourType SpecialColourSchemeIndex diff --git a/Gui/Views/CurrencyView.axaml b/Gui/Views/CurrencyView.axaml new file mode 100644 index 00000000..9792d397 --- /dev/null +++ b/Gui/Views/CurrencyView.axaml @@ -0,0 +1,44 @@ + + + + + + + diff --git a/Gui/Views/CurrencyView.axaml.cs b/Gui/Views/CurrencyView.axaml.cs new file mode 100644 index 00000000..ad9bf1ad --- /dev/null +++ b/Gui/Views/CurrencyView.axaml.cs @@ -0,0 +1,5 @@ +using Avalonia.Controls.Primitives; + +namespace Gui.Views; + +public class CurrencyView : TemplatedControl; diff --git a/Gui/Views/ExtendedPropertyGrid.cs b/Gui/Views/ExtendedPropertyGrid.cs index 10a12f8f..e13beef7 100644 --- a/Gui/Views/ExtendedPropertyGrid.cs +++ b/Gui/Views/ExtendedPropertyGrid.cs @@ -2,7 +2,10 @@ using Avalonia.PropertyGrid.Controls; using Avalonia.PropertyGrid.Controls.Factories; using Definitions.ObjectModels.Types; +using Gui.Attributes; using Gui.ViewModels; +using System.ComponentModel; +using System.Linq; namespace Gui.Views; @@ -11,6 +14,7 @@ public class ExtendedPropertyGrid : PropertyGrid public ExtendedPropertyGrid() { Factories.AddFactory(new Pos3CellEditFactory()); + Factories.AddFactory(new CurrencyCellEditFactory()); } } @@ -61,3 +65,86 @@ public override bool HandlePropertyChanged(PropertyCellContext context) return false; } } + +internal class CurrencyCellEditFactory : AbstractCellEditFactory +{ + public override bool Accept(object accessToken) => accessToken is ExtendedPropertyGrid; + + public override Control? HandleNewProperty(PropertyCellContext context) + { + var propertyDescriptor = context.Property; + var target = context.Target; + + // Check if property has CurrencyAttribute + var currencyAttr = propertyDescriptor.Attributes.OfType().FirstOrDefault(); + if (currencyAttr == null) + { + return null; + } + + // Property must be int16_t (short) + if (propertyDescriptor.PropertyType != typeof(short)) + { + return null; + } + + var control = new CurrencyView(); + return control; + } + + public override bool HandlePropertyChanged(PropertyCellContext context) + { + var propertyDescriptor = context.Property; + var target = context.Target; + var control = context.CellEdit!; + + var currencyAttr = propertyDescriptor.Attributes.OfType().FirstOrDefault(); + if (currencyAttr == null || propertyDescriptor.PropertyType != typeof(short)) + { + return false; + } + + ValidateProperty(control, propertyDescriptor, target); + + if (control is CurrencyView cv) + { + var costFactor = (short)propertyDescriptor.GetValue(target)!; + + // Get CostIndex from the target object + var costIndexProperty = TypeDescriptor.GetProperties(target)[currencyAttr.CostIndexPropertyName]; + var costIndex = costIndexProperty != null ? (byte)costIndexProperty.GetValue(target)! : (byte)0; + + // Get the year from GlobalSettings or use default + var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + + var model = new CurrencyEditorViewModel + { + CostFactor = costFactor, + CostIndex = costIndex, + Year = year + }; + + cv.DataContext = model; + + model.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(CurrencyEditorViewModel.CostFactor)) + { + SetAndRaise(context, control, model.CostFactor); + } + else if (e.PropertyName == nameof(CurrencyEditorViewModel.Year)) + { + // Update global settings with new year + if (GlobalSettings.CurrentSettings != null) + { + GlobalSettings.CurrentSettings.InflationYear = model.Year; + } + } + }; + + return true; + } + + return false; + } +} diff --git a/Tests/EffectiveCostTests.cs b/Tests/EffectiveCostTests.cs deleted file mode 100644 index bcea4794..00000000 --- a/Tests/EffectiveCostTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Definitions.ObjectModels.Objects.Track; -using Definitions.ObjectModels.Objects.Bridge; -using Gui; -using Gui.ViewModels; -using NUnit.Framework; - -namespace Gui.Tests; - -[TestFixture] -public class EffectiveCostTests -{ - [SetUp] - public void Setup() - { - // Set up global settings - GlobalSettings.CurrentSettings = new EditorSettings - { - InflationYear = 1950 - }; - } - - [Test] - public void TrackViewModel_CalculatesEffectiveCosts() - { - var trackObject = new TrackObject - { - BuildCostFactor = 100, - SellCostFactor = -50, - TunnelCostFactor = 200, - CostIndex = 0 - }; - - var viewModel = new TrackViewModel(trackObject); - - // For year 1950, with factor 1024 and divisor 10, we should get: (100 * X) / 1024 - // where X is the inflation-adjusted currency multiplication factor - Assert.That(viewModel.EffectiveBuildCost, Is.GreaterThan(0)); - Assert.That(viewModel.EffectiveSellCost, Is.LessThan(0)); - Assert.That(viewModel.EffectiveTunnelCost, Is.GreaterThan(0)); - - // Build cost should be proportional to the factors - Assert.That(viewModel.EffectiveTunnelCost, Is.GreaterThan(viewModel.EffectiveBuildCost)); - } - - [Test] - public void BridgeViewModel_CalculatesEffectiveCosts() - { - var bridgeObject = new BridgeObject - { - BaseCostFactor = 150, - HeightCostFactor = 50, - SellCostFactor = -75, - CostIndex = 1 - }; - - var viewModel = new BridgeViewModel(bridgeObject); - - Assert.That(viewModel.EffectiveBaseCost, Is.GreaterThan(0)); - Assert.That(viewModel.EffectiveHeightCost, Is.GreaterThan(0)); - Assert.That(viewModel.EffectiveSellCost, Is.LessThan(0)); - - // Base cost should be greater than height cost - Assert.That(viewModel.EffectiveBaseCost, Is.GreaterThan(viewModel.EffectiveHeightCost)); - } - - [Test] - public void InflationYear_AffectsCosts() - { - var trackObject = new TrackObject - { - BuildCostFactor = 100, - CostIndex = 0 - }; - - // Test with year 1900 (no inflation) - GlobalSettings.CurrentSettings!.InflationYear = 1900; - var viewModel1900 = new TrackViewModel(trackObject); - var cost1900 = viewModel1900.EffectiveBuildCost; - - // Test with year 2000 (with inflation) - GlobalSettings.CurrentSettings!.InflationYear = 2000; - var viewModel2000 = new TrackViewModel(trackObject); - var cost2000 = viewModel2000.EffectiveBuildCost; - - // Cost in 2000 should be higher due to inflation - Assert.That(cost2000, Is.GreaterThan(cost1900)); - } - - [Test] - public void GlobalSettings_DefaultYear_WorksCorrectly() - { - // Test that if GlobalSettings is null, default year is used - GlobalSettings.CurrentSettings = null; - - var trackObject = new TrackObject - { - BuildCostFactor = 100, - CostIndex = 0 - }; - - var viewModel = new TrackViewModel(trackObject); - - // Should not throw and should return a valid cost - Assert.That(viewModel.EffectiveBuildCost, Is.GreaterThan(0)); - } -} From 5cec6f047082c27e370636097587faa2b0378ecd Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Sun, 28 Dec 2025 16:41:09 +1100 Subject: [PATCH 7/8] improve cell editor, default values, attribute and more --- {Common => Definitions}/Economy.cs | 6 +- Gui/App.axaml | 1 + ...bute.cs => InflatableCurrencyAttribute.cs} | 5 +- Gui/EditorSettings.cs | 1 - Gui/GlobalSettings.cs | 10 --- Gui/Gui.csproj | 4 ++ Gui/Models/ObjectEditorModel.cs | 1 - ...odel.cs => InflatableCurrencyViewModel.cs} | 16 ++--- .../LocoTypes/Objects/AirportViewModel.cs | 20 +++++- .../LocoTypes/Objects/BridgeViewModel.cs | 15 +++-- .../LocoTypes/Objects/DockViewModel.cs | 20 +++++- .../LocoTypes/Objects/IndustryViewModel.cs | 20 +++++- .../LocoTypes/Objects/RoadExtraViewModel.cs | 20 +++++- .../LocoTypes/Objects/RoadStationViewModel.cs | 25 ++++++++ .../LocoTypes/Objects/RoadViewModel.cs | 32 ++++++++++ .../LocoTypes/Objects/TrackExtraViewModel.cs | 20 +++++- .../LocoTypes/Objects/TrackSignalViewModel.cs | 20 +++++- .../Objects/TrackStationViewModel.cs | 25 ++++++++ .../LocoTypes/Objects/TrackViewModel.cs | 27 +++++--- .../LocoTypes/Objects/TreeViewModel.cs | 20 +++++- .../LocoTypes/Objects/VehicleViewModel.cs | 18 ++++-- .../LocoTypes/Objects/WaterViewModel.cs | 21 +++++-- Gui/Views/CurrencyView.axaml | 44 ------------- Gui/Views/ExtendedPropertyGrid.cs | 61 ++++++++----------- Gui/Views/InflatableCurrencyView.axaml | 47 ++++++++++++++ ...iew.axaml.cs => InflatableCurrencyView.cs} | 2 +- Gui/Views/Pos3View.axaml | 7 +-- Tests/EconomyTests.cs | 4 +- 28 files changed, 367 insertions(+), 145 deletions(-) rename {Common => Definitions}/Economy.cs (99%) rename Gui/Attributes/{CurrencyAttribute.cs => InflatableCurrencyAttribute.cs} (61%) delete mode 100644 Gui/GlobalSettings.cs rename Gui/ViewModels/{CurrencyEditorViewModel.cs => InflatableCurrencyViewModel.cs} (51%) delete mode 100644 Gui/Views/CurrencyView.axaml create mode 100644 Gui/Views/InflatableCurrencyView.axaml rename Gui/Views/{CurrencyView.axaml.cs => InflatableCurrencyView.cs} (51%) diff --git a/Common/Economy.cs b/Definitions/Economy.cs similarity index 99% rename from Common/Economy.cs rename to Definitions/Economy.cs index 6128ddad..96f28466 100644 --- a/Common/Economy.cs +++ b/Definitions/Economy.cs @@ -1,4 +1,4 @@ -namespace Common; +namespace Definitions; /// /// Provides economy-related calculations for Locomotion, including inflation and cost calculations. @@ -23,7 +23,7 @@ public static class Economy public static uint[] CalculateCurrencyMultiplicationFactors(int year) { var factors = new uint[32]; - + // Initialize all factors to 1024 (base value) for (var i = 0; i < 32; i++) { @@ -70,7 +70,7 @@ public static int GetInflationAdjustedCost(short costFactor, byte costIndex, int var factors = CalculateCurrencyMultiplicationFactors(year); var val = costFactor * (long)factors[costIndex]; var result = val / (1L << divisor); - + return (int)result; } } diff --git a/Gui/App.axaml b/Gui/App.axaml index c80484d2..f686c07f 100644 --- a/Gui/App.axaml +++ b/Gui/App.axaml @@ -22,6 +22,7 @@ + - diff --git a/Gui/Views/ExtendedPropertyGrid.cs b/Gui/Views/ExtendedPropertyGrid.cs index e13beef7..b5b9dcd5 100644 --- a/Gui/Views/ExtendedPropertyGrid.cs +++ b/Gui/Views/ExtendedPropertyGrid.cs @@ -14,13 +14,14 @@ public class ExtendedPropertyGrid : PropertyGrid public ExtendedPropertyGrid() { Factories.AddFactory(new Pos3CellEditFactory()); - Factories.AddFactory(new CurrencyCellEditFactory()); + Factories.AddFactory(new InflatableCurrencyCellEditFactory()); } } internal class Pos3CellEditFactory : AbstractCellEditFactory { - public override bool Accept(object accessToken) => accessToken is ExtendedPropertyGrid; + public override bool Accept(object accessToken) + => accessToken is ExtendedPropertyGrid; public override Control? HandleNewProperty(PropertyCellContext context) { @@ -32,9 +33,7 @@ internal class Pos3CellEditFactory : AbstractCellEditFactory return null; } - var control = new Pos3View(); - - return control; + return new Pos3View(); } public override bool HandlePropertyChanged(PropertyCellContext context) @@ -50,15 +49,14 @@ public override bool HandlePropertyChanged(PropertyCellContext context) ValidateProperty(control, propertyDescriptor, target); - if (control is Pos3View vv) + if (control is Pos3View pv) { var pos = (Pos3)propertyDescriptor.GetValue(target)!; var model = new Pos3ViewModel { Pos = pos }; - vv.DataContext = model; + pv.DataContext = model; model.PropertyChanged += (s, e) => SetAndRaise(context, control, model.Pos); - return true; } @@ -66,30 +64,28 @@ public override bool HandlePropertyChanged(PropertyCellContext context) } } -internal class CurrencyCellEditFactory : AbstractCellEditFactory +internal class InflatableCurrencyCellEditFactory : AbstractCellEditFactory { - public override bool Accept(object accessToken) => accessToken is ExtendedPropertyGrid; + public override bool Accept(object accessToken) + => accessToken is ExtendedPropertyGrid; public override Control? HandleNewProperty(PropertyCellContext context) { var propertyDescriptor = context.Property; var target = context.Target; - // Check if property has CurrencyAttribute - var currencyAttr = propertyDescriptor.Attributes.OfType().FirstOrDefault(); + var currencyAttr = propertyDescriptor.Attributes.OfType().FirstOrDefault(); if (currencyAttr == null) { return null; } - // Property must be int16_t (short) if (propertyDescriptor.PropertyType != typeof(short)) { return null; } - var control = new CurrencyView(); - return control; + return new InflatableCurrencyView(); } public override bool HandlePropertyChanged(PropertyCellContext context) @@ -98,7 +94,7 @@ public override bool HandlePropertyChanged(PropertyCellContext context) var target = context.Target; var control = context.CellEdit!; - var currencyAttr = propertyDescriptor.Attributes.OfType().FirstOrDefault(); + var currencyAttr = propertyDescriptor.Attributes.OfType().FirstOrDefault(); if (currencyAttr == null || propertyDescriptor.PropertyType != typeof(short)) { return false; @@ -106,7 +102,7 @@ public override bool HandlePropertyChanged(PropertyCellContext context) ValidateProperty(control, propertyDescriptor, target); - if (control is CurrencyView cv) + if (control is InflatableCurrencyView cv) { var costFactor = (short)propertyDescriptor.GetValue(target)!; @@ -114,10 +110,18 @@ public override bool HandlePropertyChanged(PropertyCellContext context) var costIndexProperty = TypeDescriptor.GetProperties(target)[currencyAttr.CostIndexPropertyName]; var costIndex = costIndexProperty != null ? (byte)costIndexProperty.GetValue(target)! : (byte)0; - // Get the year from GlobalSettings or use default - var year = GlobalSettings.CurrentSettings?.InflationYear ?? 1950; + var designedYearProperty = TypeDescriptor.GetProperties(target)[currencyAttr.DesignedYearPropertyName]; + var designedYear = designedYearProperty != null ? (uint16_t)designedYearProperty.GetValue(target)! : (uint16_t)1950; + + var currVm = (InflatableCurrencyViewModel)cv?.DataContext; + var year = currVm?.Year ?? designedYear; + // objects can actually set any year as designed year, even 0, so lets sanitize it + if (year < 1800) + { + year = 1950; + } - var model = new CurrencyEditorViewModel + var model = new InflatableCurrencyViewModel { CostFactor = costFactor, CostIndex = costIndex, @@ -126,22 +130,7 @@ public override bool HandlePropertyChanged(PropertyCellContext context) cv.DataContext = model; - model.PropertyChanged += (s, e) => - { - if (e.PropertyName == nameof(CurrencyEditorViewModel.CostFactor)) - { - SetAndRaise(context, control, model.CostFactor); - } - else if (e.PropertyName == nameof(CurrencyEditorViewModel.Year)) - { - // Update global settings with new year - if (GlobalSettings.CurrentSettings != null) - { - GlobalSettings.CurrentSettings.InflationYear = model.Year; - } - } - }; - + model.PropertyChanged += (s, e) => SetAndRaise(context, control, model.CostFactor); return true; } diff --git a/Gui/Views/InflatableCurrencyView.axaml b/Gui/Views/InflatableCurrencyView.axaml new file mode 100644 index 00000000..df60b966 --- /dev/null +++ b/Gui/Views/InflatableCurrencyView.axaml @@ -0,0 +1,47 @@ + + + + + + + diff --git a/Gui/Views/CurrencyView.axaml.cs b/Gui/Views/InflatableCurrencyView.cs similarity index 51% rename from Gui/Views/CurrencyView.axaml.cs rename to Gui/Views/InflatableCurrencyView.cs index ad9bf1ad..498bcd3f 100644 --- a/Gui/Views/CurrencyView.axaml.cs +++ b/Gui/Views/InflatableCurrencyView.cs @@ -2,4 +2,4 @@ namespace Gui.Views; -public class CurrencyView : TemplatedControl; +public class InflatableCurrencyView : TemplatedControl; diff --git a/Gui/Views/Pos3View.axaml b/Gui/Views/Pos3View.axaml index f4772b4a..df7d8ddc 100644 --- a/Gui/Views/Pos3View.axaml +++ b/Gui/Views/Pos3View.axaml @@ -8,22 +8,21 @@