Skip to content
Merged
2 changes: 1 addition & 1 deletion Dat/Loaders/SoundObjectLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static LocoObject Load(Stream stream)
private static void LoadVariable(LocoBinaryReader br, SoundObject model)
{
model.NumUnkStructs = br.ReadUInt32();
var pcmDataLength = br.ReadUInt32(); // unused
_ = br.ReadUInt32(); // unused
model.UnkData = br.ReadBytes((int)model.NumUnkStructs * Constants.NumUnkStructs);
model.SoundObjectData = new SoundObjectData
{
Expand Down
76 changes: 76 additions & 0 deletions Definitions/Economy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace Definitions;

/// <summary>
/// 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
/// </summary>
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
];

/// <summary>
/// Calculates the currency multiplication factors for all 32 cost indices for a given year.
/// </summary>
/// <param name="year">The year to calculate inflation for (clamped to 1900-2030 range)</param>
/// <returns>An array of 32 currency multiplication factors</returns>
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;
}

/// <summary>
/// Calculates the inflation-adjusted cost for a given cost factor and cost index.
/// </summary>
/// <param name="costFactor">The base cost factor from the object</param>
/// <param name="costIndex">The cost index (0-31)</param>
/// <param name="year">The year to calculate the cost for</param>
/// <param name="divisor">The divisor to apply (default is 10, which is common for most objects)</param>
/// <returns>The inflation-adjusted cost</returns>
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;
}
}
3 changes: 1 addition & 2 deletions Definitions/ObjectModels/Graphics/ImageTableGrouper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ public static ImageTable CreateImageTable(ILocoStruct obj, ObjectType objectType
ImageTableNamer.NameImages(obj, objectType, imageList);

var imageTable = new ImageTable();

try
{
imageTable.Groups = [.. CreateGroups(obj, objectType, imageList)];
}
catch (Exception ex)
catch (Exception)
{
imageTable.Groups = [new("<parsing-error>", [.. imageList])];
}
Expand Down
1 change: 1 addition & 0 deletions Gui/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.TreeDataGrid/Themes/Fluent.axaml" />
<StyleInclude Source="/Views/Pos3View.axaml"></StyleInclude>
<StyleInclude Source="/Views/InflatableCurrencyView.axaml"></StyleInclude>

<Style Selector="TabItem">
<Setter Property="FontSize" Value="16" />
Expand Down
23 changes: 23 additions & 0 deletions Gui/Attributes/InflatableCurrencyAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

namespace Gui.Attributes;

/// <summary>
/// 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.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class InflatableCurrencyAttribute(string CostIndexPropertyName, string? DesignedYearPropertyName = null) : Attribute
{
/// <summary>
/// The name of the property that contains the CostIndex for this currency value.
/// </summary>
public string CostIndexPropertyName { get; } = CostIndexPropertyName;

/// <summary>
/// The name of the property that contains the designed year associated with this currency value.
/// This is used as the default year value if it exists.
/// Defaults to <see langword="null"/>, meaning no designed year is used.
/// </summary>
public string DesignedYearPropertyName { get; } = DesignedYearPropertyName;
}
4 changes: 4 additions & 0 deletions Gui/Gui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
<DependentUpon>Pos3View.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Views\InflatableCurrencyView.axaml.cs">
<DependentUpon>InflatableCurrencyView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion Gui/Models/ObjectEditorModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ public async Task UploadDatToServer(ObjectIndexEntry dat)
}

// todo: do something with createdObject
var createdObject = await ObjectServiceClient.UploadDatFileAsync(dat.FileName, await File.ReadAllBytesAsync(filename), creationDate, modifiedDate);
_ = await ObjectServiceClient.UploadDatFileAsync(dat.FileName, await File.ReadAllBytesAsync(filename), creationDate, modifiedDate);

await Task.Delay(100); // wait 100ms, ie don't DoS the server
}
Expand Down
44 changes: 44 additions & 0 deletions Gui/ViewModels/InflatableCurrencyViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Definitions;
using ReactiveUI;

namespace Gui.ViewModels;

public class InflatableCurrencyViewModel : ReactiveObject
{
public const int DefaultYear = 1950;

public short CostFactor
{
get;
set
{
_ = this.RaiseAndSetIfChanged(ref field, value);
this.RaisePropertyChanged(nameof(InflationAdjustedCost));
}
}

public byte CostIndex
{
get;
set
{
_ = this.RaiseAndSetIfChanged(ref field, value);
this.RaisePropertyChanged(nameof(InflationAdjustedCost));
}
}

public int Year
{
get;
set
{
_ = this.RaiseAndSetIfChanged(ref field, value);
this.RaisePropertyChanged(nameof(InflationAdjustedCost));
}
} = DefaultYear;

public int InflationAdjustedCost
=> CostIndex >= 32
? 0
: Economy.GetInflationAdjustedCost(CostFactor, CostIndex, Year);
}
4 changes: 2 additions & 2 deletions Gui/ViewModels/LocoTypes/ObjectEditorViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ void ValidateForOG()
}

// split the CurrentFile path on "opengraphics" folder
var dir = Path.GetRelativePath(Model.Settings.ObjDataDirectory, CurrentFile.FileName);
_ = Path.GetRelativePath(Model.Settings.ObjDataDirectory, CurrentFile.FileName);
var parentDirName = Path.GetFileName(Path.GetDirectoryName(CurrentFile.FileName));

if (OriginalObjectFiles.Names.TryGetValue(parentDirName, out var fileInfo))
Expand Down Expand Up @@ -398,7 +398,7 @@ void SaveCore(string filename, SaveParameters saveParameters)
CurrentObjectViewModel.CopyBackToModel();

// this is hacky but it should work
if (ExtraContentViewModel is AudioViewModel avm && CurrentObject.LocoObject.Object is SoundObject so)
if (ExtraContentViewModel is AudioViewModel avm && CurrentObject.LocoObject.Object is SoundObject)
{
var datWav = avm.GetAsDatWav(LocoAudioType.SoundEffect);
if (datWav == null)
Expand Down
13 changes: 10 additions & 3 deletions Gui/ViewModels/LocoTypes/Objects/AirportViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Dat.Loaders;
using Definitions.ObjectModels.Objects.Airport;
using Definitions.ObjectModels.Objects.Common;
using Gui.Attributes;
using PropertyModels.ComponentModel.DataAnnotations;
using PropertyModels.Extensions;
using ReactiveUI;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
Expand Down Expand Up @@ -65,17 +67,22 @@ public uint32_t LargeTiles
public uint8_t CostIndex
{
get => Model.CostIndex;
set => Model.CostIndex = value;
set
{
Model.CostIndex = value;
this.RaisePropertyChanged(nameof(BuildCostFactor));
this.RaisePropertyChanged(nameof(SellCostFactor));
}
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t BuildCostFactor
{
get => Model.BuildCostFactor;
set => Model.BuildCostFactor = value;
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t SellCostFactor
{
get => Model.SellCostFactor;
Expand Down
16 changes: 12 additions & 4 deletions Gui/ViewModels/LocoTypes/Objects/BridgeViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Definitions.ObjectModels.Objects.Bridge;
using Definitions.ObjectModels.Types;
using Gui.Attributes;
using PropertyModels.ComponentModel.DataAnnotations;
using ReactiveUI;
using System.ComponentModel;

namespace Gui.ViewModels;
Expand Down Expand Up @@ -68,24 +70,30 @@ public uint16_t DesignedYear
public uint8_t CostIndex
{
get => Model.CostIndex;
set => Model.CostIndex = value;
set
{
Model.CostIndex = value;
this.RaisePropertyChanged(nameof(BaseCostFactor));
this.RaisePropertyChanged(nameof(HeightCostFactor));
this.RaisePropertyChanged(nameof(SellCostFactor));
}
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t BaseCostFactor
{
get => Model.BaseCostFactor;
set => Model.BaseCostFactor = value;
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t HeightCostFactor
{
get => Model.HeightCostFactor;
set => Model.HeightCostFactor = value;
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t SellCostFactor
{
get => Model.SellCostFactor;
Expand Down
13 changes: 10 additions & 3 deletions Gui/ViewModels/LocoTypes/Objects/DockViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
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 ReactiveUI;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
Expand Down Expand Up @@ -42,17 +44,22 @@ public Pos2 BoatPosition
public uint8_t CostIndex
{
get => Model.CostIndex;
set => Model.CostIndex = value;
set
{
Model.CostIndex = value;
this.RaisePropertyChanged(nameof(BuildCostFactor));
this.RaisePropertyChanged(nameof(SellCostFactor));
}
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t BuildCostFactor
{
get => Model.BuildCostFactor;
set => Model.BuildCostFactor = value;
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t SellCostFactor
{
get => Model.SellCostFactor;
Expand Down
13 changes: 10 additions & 3 deletions Gui/ViewModels/LocoTypes/Objects/IndustryViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
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 ReactiveUI;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
Expand Down Expand Up @@ -76,17 +78,22 @@ public uint8_t MonthlyClosureChance
public uint8_t CostIndex
{
get => Model.CostIndex;
set => Model.CostIndex = value;
set
{
Model.CostIndex = value;
this.RaisePropertyChanged(nameof(BuildCostFactor));
this.RaisePropertyChanged(nameof(SellCostFactor));
}
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t BuildCostFactor
{
get => Model.BuildCostFactor;
set => Model.BuildCostFactor = value;
}

[Category("Cost")]
[Category("Cost"), InflatableCurrency(nameof(CostIndex), nameof(DesignedYear))]
public int16_t SellCostFactor
{
get => Model.SellCostFactor;
Expand Down
Loading