Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Definitions/ObjectModels/Objects/Airport/AirportObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class AirportObject : ILocoStruct, IHasBuildingComponents

public uint8_t var_07 { get; set; }
public AirportObjectFlags Flags { get; set; }
public BuildingComponentsModel BuildingComponents { get; set; } = new();
public BuildingComponents BuildingComponents { get; set; } = new();
public List<AirportBuilding> BuildingPositions { get; set; } = [];
public uint32_t LargeTiles { get; set; }
public int8_t MinX { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Definitions.ObjectModels.Objects.Building;

public class BuildingObject : ILocoStruct, IHasBuildingComponents
{
public BuildingComponentsModel BuildingComponents { get; set; } = new();
public BuildingComponents BuildingComponents { get; set; } = new();
public uint32_t Colours { get; set; }
public uint16_t DesignedYear { get; set; }
public uint16_t ObsoleteYear { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
using Definitions.ObjectModels.Validation;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Definitions.ObjectModels.Objects.Common;

public interface IHasBuildingComponents
{
BuildingComponentsModel BuildingComponents { get; set; }
BuildingComponents BuildingComponents { get; set; }
}

public class BuildingComponentsModel : ILocoStruct
[TypeConverter(typeof(ExpandableObjectConverter))]
public class BuildingComponents : ILocoStruct
{
[Length(1, 63)]
[CountEqualTo(nameof(BuildingAnimations))]
Expand Down Expand Up @@ -43,6 +45,17 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
yield return new ValidationResult($"{nameof(BuildingVariations)} must contain between 1 and 31 entries.", [nameof(BuildingVariations)]);
}

foreach (var bv in BuildingVariations)
{
foreach (var bvl in bv)
{
if (bvl >= BuildingHeights.Count)
{
yield return new ValidationResult($"A building variation layer index ({bvl}) is out of range. It must be less than the number of building heights ({BuildingHeights.Count}).", [nameof(BuildingVariations)]);
}
}
}

yield break;
}
}
2 changes: 1 addition & 1 deletion Definitions/ObjectModels/Objects/Dock/DockObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class DockObject : ILocoStruct, IHasBuildingComponents
public uint8_t CostIndex { get; set; }
public uint8_t var_07 { get; set; } // probably padding, not used in the game
public DockObjectFlags Flags { get; set; }
public BuildingComponentsModel BuildingComponents { get; set; } = new();
public BuildingComponents BuildingComponents { get; set; } = new();
public uint16_t DesignedYear { get; set; }
public uint16_t ObsoleteYear { get; set; }
public Pos2 BoatPosition { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Definitions.ObjectModels.Objects.Industry;
public class IndustryObject : ILocoStruct, IHasBuildingComponents
{
public uint32_t FarmImagesPerGrowthStage { get; set; }
public BuildingComponentsModel BuildingComponents { get; set; } = new();
public BuildingComponents BuildingComponents { get; set; } = new();
[Length(4, 4)]
public List<List<uint8_t>> AnimationSequences { get; set; } = []; // Access with getAnimationSequence helper method
public List<IndustryObjectUnk38> var_38 { get; set; } = []; // Access with getUnk38 helper method
Expand Down
2 changes: 1 addition & 1 deletion Gui/ViewModels/Graphics/ImageTableViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public class ImageTableViewModel : ReactiveObject, IExtraContentViewModel

ImageTable Model { get; init; }

public ImageTableViewModel(ImageTable imageTable, ILogger logger, BuildingComponentsModel? buildingComponents = null)
public ImageTableViewModel(ImageTable imageTable, ILogger logger, BuildingComponents? buildingComponents = null)
{
ArgumentNullException.ThrowIfNull(imageTable);

Expand Down
17 changes: 4 additions & 13 deletions Gui/ViewModels/LocoTypes/ObjectEditorViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,22 +256,13 @@ public override void Load()
else
{
CurrentObject.LocoObject.ImageTable?.PaletteMap = Model.PaletteMap;

// temporary hack to show building components
if (CurrentObject.LocoObject.ObjectType == Definitions.ObjectModels.Types.ObjectType.Building)
if (CurrentObject.LocoObject.ImageTable == null)
{
ExtraContentViewModel = new ImageTableViewModel(CurrentObject.LocoObject.ImageTable, Model.Logger, (CurrentObject.LocoObject.Object as IHasBuildingComponents)?.BuildingComponents);
logger.Info($"{CurrentFile.DisplayName} has no image table");
}
else
{
if (CurrentObject.LocoObject.ImageTable == null)
{
logger.Info($"{CurrentFile.DisplayName} has no image table");
}
else
{
ExtraContentViewModel = new ImageTableViewModel(CurrentObject.LocoObject.ImageTable, Model.Logger, null);
}
ExtraContentViewModel = new ImageTableViewModel(CurrentObject.LocoObject.ImageTable, Model.Logger, (CurrentObject.LocoObject.Object as IHasBuildingComponents)?.BuildingComponents);
}
}
}
Expand Down Expand Up @@ -394,7 +385,7 @@ void SaveCore(string filename, SaveParameters saveParameters)
logger.Info($"Saving {CurrentObject.DatInfo.S5Header.Name} to {filename}");
StringTableViewModel?.WriteTableBackToObject();

// VM should auto-copy back now for everything but VehicleObject
// VM should auto-copy back now for everything but VehicleObject and BuildingObject
CurrentObjectViewModel.CopyBackToModel();

// this is hacky but it should work
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ public class BuildingComponentsViewModel : ReactiveObject
[Reactive] public int MaxWidth { get; set; }
[Reactive] public int MaxHeight { get; set; }

//[Reactive]
//public BuildingComponentsModel BuildingComponentsModel { get; set; }
[Reactive]
public BuildingComponents BuildingComponentsModel { get; set; }

[Reactive, Browsable(false)]
public ObservableCollection<uint8_t> BuildingHeights { get; set; } = [];

[Reactive, Browsable(false)]
public ObservableCollection<BuildingPartAnimation> BuildingAnimations { get; set; } = [];

//[Reactive]
//public List<List<uint8_t>> BuildingVariations { get; set; } = [];
[Reactive]
public List<List<uint8_t>> BuildingVariations { get; set; } = [];

//[Browsable(false)]
[Reactive]
Expand All @@ -45,40 +45,46 @@ public class BuildingComponentsViewModel : ReactiveObject

public BuildingComponentsViewModel()
{
//_ = this.WhenAnyValue(x => x.BuildingVariations)
// .Subscribe(_ => this.RaisePropertyChanged(nameof(BuildingVariationViewModels)));
_ = this.WhenAnyValue(x => x.BuildingVariations)
.Subscribe(_ => this.RaisePropertyChanged(nameof(BuildingVariationViewModels)));

_ = this.WhenAnyValue(x => x.VerticalLayerSpacing)
.Subscribe(ApplyOffsetToAllLayers);

_ = MessageBus.Current.Listen<BuildingComponents>().Subscribe(UpdateBuildingComponents);
}

public BuildingComponentsViewModel(BuildingComponentsModel buildingComponents, ImageTable imageTable) : this()
public BuildingComponentsViewModel(BuildingComponents buildingComponents, ImageTable imageTable) : this()
{
ArgumentNullException.ThrowIfNull(buildingComponents);
ArgumentNullException.ThrowIfNull(imageTable);

//_ = this.WhenAnyValue(x => x.BuildingVariationViewModels)
// .Where(x => x != null && ImageTable != null)
// .Subscribe(_ => RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations));

ImageTable = imageTable;
UpdateBuildingComponents(buildingComponents);
}

void UpdateBuildingComponents(BuildingComponents buildingComponents)
{
_ = this.WhenAnyValue(x => x.BuildingVariationViewModels)
.Where(x => x != null && ImageTable != null)
.Subscribe(_ => RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations, buildingComponents.BuildingHeights));

BuildingHeights = new ObservableCollection<uint8_t>(buildingComponents.BuildingHeights);
BuildingAnimations = new ObservableCollection<BuildingPartAnimation>(buildingComponents.BuildingAnimations);
BuildingVariations = buildingComponents.BuildingVariations;
BuildingComponentsModel = buildingComponents;

//RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations);

//BuildingVariations = buildingComponents.BuildingVariations;
//BuildingComponentsModel = buildingComponents;
RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations, buildingComponents.BuildingHeights);
}

protected void RecomputeBuildingVariationViewModels(List<List<uint8_t>> buildingVariations)
protected void RecomputeBuildingVariationViewModels(List<List<uint8_t>> buildingVariations, List<byte> buildingHeights)
{
var layers = ImageTable.Groups.ConvertAll(x => x.GraphicsElements);

BuildingVariationViewModels.Clear();

MaxWidth = layers.Max(x => x.Max(y => y.Width)) + 16;
MaxHeight = (layers.Max(x => x.Max(y => y.Height)) * BuildingHeights.Count) + BuildingHeights.Sum(x => x) + buildingVariations.Max(x => x.Count) * VerticalLayerSpacing;
MaxHeight = (layers.Max(x => x.Max(y => y.Height)) * buildingHeights.Count) + buildingHeights.Sum(x => x) + buildingVariations.Max(x => x.Count) * (VerticalLayerSpacing * 2);

var x = 0;
foreach (var variation in buildingVariations)
Expand All @@ -99,18 +105,21 @@ protected void RecomputeBuildingVariationViewModels(List<List<uint8_t>> building
var cumulativeOffset = 0;
foreach (var variationItem in variation)
{
var layer = layers[variationItem];
var bl = new BuildingLayerViewModel
if (layers.Count > variationItem)
{
XBase = layer[i].XOffset, // + (MaxWidth / 2),
YBase = layer[i].YOffset - cumulativeOffset + MaxHeight * 0.80,
DisplayedImage = layer[i].Image.ToAvaloniaBitmap(),
XOffset = 0,
YOffset = 0,
};

cumulativeOffset += BuildingHeights[variationItem];
bs.Layers.Add(bl);
var layer = layers[variationItem];
var bl = new BuildingLayerViewModel
{
XBase = layer[i].XOffset + (MaxWidth / 2),
YBase = layer[i].YOffset - cumulativeOffset + MaxHeight * 0.80,
DisplayedImage = layer[i].Image.ToAvaloniaBitmap(),
XOffset = 0,
YOffset = 0,
};

cumulativeOffset += buildingHeights[variationItem];
bs.Layers.Add(bl);
}
}

bv.Directions.Add(bs); // [i] = bs;
Expand Down
85 changes: 73 additions & 12 deletions Gui/ViewModels/LocoTypes/Objects/Building/BuildingViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,69 @@
using Definitions.ObjectModels.Types;
using PropertyModels.ComponentModel.DataAnnotations;
using PropertyModels.Extensions;
using ReactiveUI;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace Gui.ViewModels.LocoTypes.Objects.Building;

public class BuildingViewModel(BuildingObject model)
: LocoObjectViewModel<BuildingObject>(model)
public class BuildingViewModel : LocoObjectViewModel<BuildingObject>
{
public BuildingViewModel(BuildingObject model) : base(model)
{
ProducedCargo = new(model.ProducedCargo);
RequiredCargo = new(model.RequiredCargo);
ProducedQuantity = new(model.ProducedQuantity);

BuildingVariations = new(model.BuildingComponents.BuildingVariations.Select(x => x.ToBindingList()).ToBindingList());
BuildingHeights = model.BuildingComponents.BuildingHeights.ToBindingList();
BuildingAnimations = model.BuildingComponents.BuildingAnimations.ToBindingList();

ElevatorSequence1 = model.ElevatorHeightSequences.Count > 0 ? new(model.ElevatorHeightSequences[0]) : null;
ElevatorSequence2 = model.ElevatorHeightSequences.Count > 1 ? new(model.ElevatorHeightSequences[1]) : null;
ElevatorSequence3 = model.ElevatorHeightSequences.Count > 2 ? new(model.ElevatorHeightSequences[2]) : null;
ElevatorSequence4 = model.ElevatorHeightSequences.Count > 3 ? new(model.ElevatorHeightSequences[3]) : null;

// Subscribe to BuildingVariations changes (including nested lists)
BuildingVariations.ListChanged += OnBuildingComponentChanged;
foreach (var variation in BuildingVariations)
{
variation.ListChanged += OnBuildingComponentChanged;
}

// Subscribe to BuildingHeights changes
BuildingHeights.ListChanged += OnBuildingComponentChanged;

// Subscribe to BuildingAnimations changes
BuildingAnimations.ListChanged += OnBuildingComponentChanged;
}
public override void CopyBackToModel()
{
Model.BuildingComponents.BuildingVariations = [.. BuildingVariations.Select(x => x.ToList())];
Model.BuildingComponents.BuildingHeights = [.. BuildingHeights];
Model.BuildingComponents.BuildingAnimations = [.. BuildingAnimations];
}

void OnBuildingComponentChanged(object? sender, ListChangedEventArgs e)
{
// When a new nested list is added to BuildingVariations, subscribe to it
if (sender == BuildingVariations && e.ListChangedType == ListChangedType.ItemAdded)
{
BuildingVariations[e.NewIndex].ListChanged += OnBuildingComponentChanged;
}

// 'live' updates are not needed
//CopyBackToModel();

MessageBus.Current.SendMessage(new BuildingComponents()
{
BuildingAnimations = [.. BuildingAnimations],
BuildingHeights = [.. BuildingHeights],
BuildingVariations = [.. BuildingVariations.Select(x => x.ToList())]
});
}

[EnumProhibitValues<BuildingObjectFlags>(BuildingObjectFlags.None)]
public BuildingObjectFlags Flags
{
Expand Down Expand Up @@ -87,33 +141,40 @@ public uint16_t SellCostFactor
set => Model.SellCostFactor = value;
}

[Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<ObjectModelHeader> ProducedCargo { get; set; } = new(model.ProducedCargo);
[Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<ObjectModelHeader> RequiredCargo { get; set; } = new(model.RequiredCargo);
[Category("Production"), Length(1, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<uint8_t> ProducedQuantity { get; set; } = new(model.ProducedQuantity);
[Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<ObjectModelHeader> ProducedCargo { get; set; }
[Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<ObjectModelHeader> RequiredCargo { get; set; }
[Category("Production"), Length(1, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<uint8_t> ProducedQuantity { get; set; }

//[Category("Building")]
//public BuildingComponents BuildingComponents
//{
// get => Model.BuildingComponents;
// set => Model.BuildingComponents = value;
//}

[Category("Building"), Length(1, BuildingObjectLoader.Constants.BuildingVariationCount)]
public BindingList<BindingList<uint8_t>> BuildingVariations { get; init; } = new(model.BuildingComponents.BuildingVariations.Select(x => x.ToBindingList()).ToBindingList());
public BindingList<BindingList<uint8_t>> BuildingVariations { get; init; }

[Category("Building"), Length(1, BuildingObjectLoader.Constants.BuildingHeightCount)]
public BindingList<uint8_t> BuildingHeights { get; init; } = model.BuildingComponents.BuildingHeights.ToBindingList();
public BindingList<uint8_t> BuildingHeights { get; init; }

[Category("Building"), Length(1, BuildingObjectLoader.Constants.BuildingAnimationCount)]
public BindingList<BuildingPartAnimation> BuildingAnimations { get; init; } = model.BuildingComponents.BuildingAnimations.ToBindingList();
public BindingList<BuildingPartAnimation> BuildingAnimations { get; init; }

// note: these height sequences are massive. BLDCTY28 has 2 sequences, 512 in length and 1024 in length. Avalonia PropertyGrid takes 30+ seconds to render this. todo: don't use property grid in future
//[Reactive, Category("Building"), Length(1, BuildingObject.MaxElevatorHeightSequences), Browsable(false)] public BindingList<BindingList<uint8_t>> ElevatorHeightSequences { get; set; } // NumElevatorSequences

[Category("Elevator"), Browsable(false)]
public BindingList<uint8_t>? ElevatorSequence1 { get; init; } = model.ElevatorHeightSequences.Count > 0 ? new(model.ElevatorHeightSequences[0]) : null;
public BindingList<uint8_t>? ElevatorSequence1 { get; init; }

[Category("Elevator"), Browsable(false)]
public BindingList<uint8_t>? ElevatorSequence2 { get; init; } = model.ElevatorHeightSequences.Count > 1 ? new(model.ElevatorHeightSequences[1]) : null;
public BindingList<uint8_t>? ElevatorSequence2 { get; init; }

[Category("Elevator"), Browsable(false)]
public BindingList<uint8_t>? ElevatorSequence3 { get; init; } = model.ElevatorHeightSequences.Count > 2 ? new(model.ElevatorHeightSequences[2]) : null;
public BindingList<uint8_t>? ElevatorSequence3 { get; init; }

[Category("Elevator"), Browsable(false)]
public BindingList<uint8_t>? ElevatorSequence4 { get; init; } = model.ElevatorHeightSequences.Count > 3 ? new(model.ElevatorHeightSequences[3]) : null;
public BindingList<uint8_t>? ElevatorSequence4 { get; init; }

[Category("<unknown>")]
public uint8_t var_A6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ public DesignBuildingComponentsViewModel()
[0, 1],
[0, 1, 1],
];
List<uint8_t> buildingHeights = [16, 16];

RecomputeBuildingVariationViewModels(buildingVariations);
RecomputeBuildingVariationViewModels(buildingVariations, buildingHeights);
}
}
Loading