Skip to content

Commit db36335

Browse files
authored
Enable first version of building component viewer (#218)
* cleanup viewer * dynamic updating of building component viewer when the model changes
1 parent eddfdf6 commit db36335

File tree

12 files changed

+168
-88
lines changed

12 files changed

+168
-88
lines changed

Definitions/ObjectModels/Objects/Airport/AirportObject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class AirportObject : ILocoStruct, IHasBuildingComponents
1616

1717
public uint8_t var_07 { get; set; }
1818
public AirportObjectFlags Flags { get; set; }
19-
public BuildingComponentsModel BuildingComponents { get; set; } = new();
19+
public BuildingComponents BuildingComponents { get; set; } = new();
2020
public List<AirportBuilding> BuildingPositions { get; set; } = [];
2121
public uint32_t LargeTiles { get; set; }
2222
public int8_t MinX { get; set; }

Definitions/ObjectModels/Objects/Building/BuildingObject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Definitions.ObjectModels.Objects.Building;
77

88
public class BuildingObject : ILocoStruct, IHasBuildingComponents
99
{
10-
public BuildingComponentsModel BuildingComponents { get; set; } = new();
10+
public BuildingComponents BuildingComponents { get; set; } = new();
1111
public uint32_t Colours { get; set; }
1212
public uint16_t DesignedYear { get; set; }
1313
public uint16_t ObsoleteYear { get; set; }

Definitions/ObjectModels/Objects/Common/BuildingComponentsModel.cs renamed to Definitions/ObjectModels/Objects/Common/BuildingComponents.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
using Definitions.ObjectModels.Validation;
2+
using System.ComponentModel;
23
using System.ComponentModel.DataAnnotations;
34

45
namespace Definitions.ObjectModels.Objects.Common;
56

67
public interface IHasBuildingComponents
78
{
8-
BuildingComponentsModel BuildingComponents { get; set; }
9+
BuildingComponents BuildingComponents { get; set; }
910
}
1011

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

48+
foreach (var bv in BuildingVariations)
49+
{
50+
foreach (var bvl in bv)
51+
{
52+
if (bvl >= BuildingHeights.Count)
53+
{
54+
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)]);
55+
}
56+
}
57+
}
58+
4659
yield break;
4760
}
4861
}

Definitions/ObjectModels/Objects/Dock/DockObject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class DockObject : ILocoStruct, IHasBuildingComponents
1111
public uint8_t CostIndex { get; set; }
1212
public uint8_t var_07 { get; set; } // probably padding, not used in the game
1313
public DockObjectFlags Flags { get; set; }
14-
public BuildingComponentsModel BuildingComponents { get; set; } = new();
14+
public BuildingComponents BuildingComponents { get; set; } = new();
1515
public uint16_t DesignedYear { get; set; }
1616
public uint16_t ObsoleteYear { get; set; }
1717
public Pos2 BoatPosition { get; set; }

Definitions/ObjectModels/Objects/Industry/IndustryObject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Definitions.ObjectModels.Objects.Industry;
88
public class IndustryObject : ILocoStruct, IHasBuildingComponents
99
{
1010
public uint32_t FarmImagesPerGrowthStage { get; set; }
11-
public BuildingComponentsModel BuildingComponents { get; set; } = new();
11+
public BuildingComponents BuildingComponents { get; set; } = new();
1212
[Length(4, 4)]
1313
public List<List<uint8_t>> AnimationSequences { get; set; } = []; // Access with getAnimationSequence helper method
1414
public List<IndustryObjectUnk38> var_38 { get; set; } = []; // Access with getUnk38 helper method

Gui/ViewModels/Graphics/ImageTableViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public class ImageTableViewModel : ReactiveObject, IExtraContentViewModel
9494

9595
ImageTable Model { get; init; }
9696

97-
public ImageTableViewModel(ImageTable imageTable, ILogger logger, BuildingComponentsModel? buildingComponents = null)
97+
public ImageTableViewModel(ImageTable imageTable, ILogger logger, BuildingComponents? buildingComponents = null)
9898
{
9999
ArgumentNullException.ThrowIfNull(imageTable);
100100

Gui/ViewModels/LocoTypes/ObjectEditorViewModel.cs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -256,22 +256,13 @@ public override void Load()
256256
else
257257
{
258258
CurrentObject.LocoObject.ImageTable?.PaletteMap = Model.PaletteMap;
259-
260-
// temporary hack to show building components
261-
if (CurrentObject.LocoObject.ObjectType == Definitions.ObjectModels.Types.ObjectType.Building)
259+
if (CurrentObject.LocoObject.ImageTable == null)
262260
{
263-
ExtraContentViewModel = new ImageTableViewModel(CurrentObject.LocoObject.ImageTable, Model.Logger, (CurrentObject.LocoObject.Object as IHasBuildingComponents)?.BuildingComponents);
261+
logger.Info($"{CurrentFile.DisplayName} has no image table");
264262
}
265263
else
266264
{
267-
if (CurrentObject.LocoObject.ImageTable == null)
268-
{
269-
logger.Info($"{CurrentFile.DisplayName} has no image table");
270-
}
271-
else
272-
{
273-
ExtraContentViewModel = new ImageTableViewModel(CurrentObject.LocoObject.ImageTable, Model.Logger, null);
274-
}
265+
ExtraContentViewModel = new ImageTableViewModel(CurrentObject.LocoObject.ImageTable, Model.Logger, (CurrentObject.LocoObject.Object as IHasBuildingComponents)?.BuildingComponents);
275266
}
276267
}
277268
}
@@ -394,7 +385,7 @@ void SaveCore(string filename, SaveParameters saveParameters)
394385
logger.Info($"Saving {CurrentObject.DatInfo.S5Header.Name} to {filename}");
395386
StringTableViewModel?.WriteTableBackToObject();
396387

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

400391
// this is hacky but it should work

Gui/ViewModels/LocoTypes/Objects/Building/BuildingComponentsViewModel.cs

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@ public class BuildingComponentsViewModel : ReactiveObject
2525
[Reactive] public int MaxWidth { get; set; }
2626
[Reactive] public int MaxHeight { get; set; }
2727

28-
//[Reactive]
29-
//public BuildingComponentsModel BuildingComponentsModel { get; set; }
28+
[Reactive]
29+
public BuildingComponents BuildingComponentsModel { get; set; }
3030

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

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

37-
//[Reactive]
38-
//public List<List<uint8_t>> BuildingVariations { get; set; } = [];
37+
[Reactive]
38+
public List<List<uint8_t>> BuildingVariations { get; set; } = [];
3939

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

4646
public BuildingComponentsViewModel()
4747
{
48-
//_ = this.WhenAnyValue(x => x.BuildingVariations)
49-
// .Subscribe(_ => this.RaisePropertyChanged(nameof(BuildingVariationViewModels)));
48+
_ = this.WhenAnyValue(x => x.BuildingVariations)
49+
.Subscribe(_ => this.RaisePropertyChanged(nameof(BuildingVariationViewModels)));
5050

5151
_ = this.WhenAnyValue(x => x.VerticalLayerSpacing)
5252
.Subscribe(ApplyOffsetToAllLayers);
53+
54+
_ = MessageBus.Current.Listen<BuildingComponents>().Subscribe(UpdateBuildingComponents);
5355
}
5456

55-
public BuildingComponentsViewModel(BuildingComponentsModel buildingComponents, ImageTable imageTable) : this()
57+
public BuildingComponentsViewModel(BuildingComponents buildingComponents, ImageTable imageTable) : this()
5658
{
5759
ArgumentNullException.ThrowIfNull(buildingComponents);
5860
ArgumentNullException.ThrowIfNull(imageTable);
5961

60-
//_ = this.WhenAnyValue(x => x.BuildingVariationViewModels)
61-
// .Where(x => x != null && ImageTable != null)
62-
// .Subscribe(_ => RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations));
63-
6462
ImageTable = imageTable;
63+
UpdateBuildingComponents(buildingComponents);
64+
}
65+
66+
void UpdateBuildingComponents(BuildingComponents buildingComponents)
67+
{
68+
_ = this.WhenAnyValue(x => x.BuildingVariationViewModels)
69+
.Where(x => x != null && ImageTable != null)
70+
.Subscribe(_ => RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations, buildingComponents.BuildingHeights));
71+
6572
BuildingHeights = new ObservableCollection<uint8_t>(buildingComponents.BuildingHeights);
6673
BuildingAnimations = new ObservableCollection<BuildingPartAnimation>(buildingComponents.BuildingAnimations);
74+
BuildingVariations = buildingComponents.BuildingVariations;
75+
BuildingComponentsModel = buildingComponents;
6776

68-
//RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations);
69-
70-
//BuildingVariations = buildingComponents.BuildingVariations;
71-
//BuildingComponentsModel = buildingComponents;
77+
RecomputeBuildingVariationViewModels(buildingComponents.BuildingVariations, buildingComponents.BuildingHeights);
7278
}
7379

74-
protected void RecomputeBuildingVariationViewModels(List<List<uint8_t>> buildingVariations)
80+
protected void RecomputeBuildingVariationViewModels(List<List<uint8_t>> buildingVariations, List<byte> buildingHeights)
7581
{
7682
var layers = ImageTable.Groups.ConvertAll(x => x.GraphicsElements);
7783

7884
BuildingVariationViewModels.Clear();
7985

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

8389
var x = 0;
8490
foreach (var variation in buildingVariations)
@@ -99,18 +105,21 @@ protected void RecomputeBuildingVariationViewModels(List<List<uint8_t>> building
99105
var cumulativeOffset = 0;
100106
foreach (var variationItem in variation)
101107
{
102-
var layer = layers[variationItem];
103-
var bl = new BuildingLayerViewModel
108+
if (layers.Count > variationItem)
104109
{
105-
XBase = layer[i].XOffset, // + (MaxWidth / 2),
106-
YBase = layer[i].YOffset - cumulativeOffset + MaxHeight * 0.80,
107-
DisplayedImage = layer[i].Image.ToAvaloniaBitmap(),
108-
XOffset = 0,
109-
YOffset = 0,
110-
};
111-
112-
cumulativeOffset += BuildingHeights[variationItem];
113-
bs.Layers.Add(bl);
110+
var layer = layers[variationItem];
111+
var bl = new BuildingLayerViewModel
112+
{
113+
XBase = layer[i].XOffset + (MaxWidth / 2),
114+
YBase = layer[i].YOffset - cumulativeOffset + MaxHeight * 0.80,
115+
DisplayedImage = layer[i].Image.ToAvaloniaBitmap(),
116+
XOffset = 0,
117+
YOffset = 0,
118+
};
119+
120+
cumulativeOffset += buildingHeights[variationItem];
121+
bs.Layers.Add(bl);
122+
}
114123
}
115124

116125
bv.Directions.Add(bs); // [i] = bs;

Gui/ViewModels/LocoTypes/Objects/Building/BuildingViewModel.cs

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,69 @@
55
using Definitions.ObjectModels.Types;
66
using PropertyModels.ComponentModel.DataAnnotations;
77
using PropertyModels.Extensions;
8+
using ReactiveUI;
89
using System.ComponentModel;
910
using System.ComponentModel.DataAnnotations;
1011
using System.Linq;
1112

1213
namespace Gui.ViewModels.LocoTypes.Objects.Building;
1314

14-
public class BuildingViewModel(BuildingObject model)
15-
: LocoObjectViewModel<BuildingObject>(model)
15+
public class BuildingViewModel : LocoObjectViewModel<BuildingObject>
1616
{
17+
public BuildingViewModel(BuildingObject model) : base(model)
18+
{
19+
ProducedCargo = new(model.ProducedCargo);
20+
RequiredCargo = new(model.RequiredCargo);
21+
ProducedQuantity = new(model.ProducedQuantity);
22+
23+
BuildingVariations = new(model.BuildingComponents.BuildingVariations.Select(x => x.ToBindingList()).ToBindingList());
24+
BuildingHeights = model.BuildingComponents.BuildingHeights.ToBindingList();
25+
BuildingAnimations = model.BuildingComponents.BuildingAnimations.ToBindingList();
26+
27+
ElevatorSequence1 = model.ElevatorHeightSequences.Count > 0 ? new(model.ElevatorHeightSequences[0]) : null;
28+
ElevatorSequence2 = model.ElevatorHeightSequences.Count > 1 ? new(model.ElevatorHeightSequences[1]) : null;
29+
ElevatorSequence3 = model.ElevatorHeightSequences.Count > 2 ? new(model.ElevatorHeightSequences[2]) : null;
30+
ElevatorSequence4 = model.ElevatorHeightSequences.Count > 3 ? new(model.ElevatorHeightSequences[3]) : null;
31+
32+
// Subscribe to BuildingVariations changes (including nested lists)
33+
BuildingVariations.ListChanged += OnBuildingComponentChanged;
34+
foreach (var variation in BuildingVariations)
35+
{
36+
variation.ListChanged += OnBuildingComponentChanged;
37+
}
38+
39+
// Subscribe to BuildingHeights changes
40+
BuildingHeights.ListChanged += OnBuildingComponentChanged;
41+
42+
// Subscribe to BuildingAnimations changes
43+
BuildingAnimations.ListChanged += OnBuildingComponentChanged;
44+
}
45+
public override void CopyBackToModel()
46+
{
47+
Model.BuildingComponents.BuildingVariations = [.. BuildingVariations.Select(x => x.ToList())];
48+
Model.BuildingComponents.BuildingHeights = [.. BuildingHeights];
49+
Model.BuildingComponents.BuildingAnimations = [.. BuildingAnimations];
50+
}
51+
52+
void OnBuildingComponentChanged(object? sender, ListChangedEventArgs e)
53+
{
54+
// When a new nested list is added to BuildingVariations, subscribe to it
55+
if (sender == BuildingVariations && e.ListChangedType == ListChangedType.ItemAdded)
56+
{
57+
BuildingVariations[e.NewIndex].ListChanged += OnBuildingComponentChanged;
58+
}
59+
60+
// 'live' updates are not needed
61+
//CopyBackToModel();
62+
63+
MessageBus.Current.SendMessage(new BuildingComponents()
64+
{
65+
BuildingAnimations = [.. BuildingAnimations],
66+
BuildingHeights = [.. BuildingHeights],
67+
BuildingVariations = [.. BuildingVariations.Select(x => x.ToList())]
68+
});
69+
}
70+
1771
[EnumProhibitValues<BuildingObjectFlags>(BuildingObjectFlags.None)]
1872
public BuildingObjectFlags Flags
1973
{
@@ -87,33 +141,40 @@ public uint16_t SellCostFactor
87141
set => Model.SellCostFactor = value;
88142
}
89143

90-
[Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<ObjectModelHeader> ProducedCargo { get; set; } = new(model.ProducedCargo);
91-
[Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<ObjectModelHeader> RequiredCargo { get; set; } = new(model.RequiredCargo);
92-
[Category("Production"), Length(1, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<uint8_t> ProducedQuantity { get; set; } = new(model.ProducedQuantity);
144+
[Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<ObjectModelHeader> ProducedCargo { get; set; }
145+
[Category("Production"), Length(0, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<ObjectModelHeader> RequiredCargo { get; set; }
146+
[Category("Production"), Length(1, BuildingObjectLoader.Constants.MaxProducedCargoType)] public BindingList<uint8_t> ProducedQuantity { get; set; }
147+
148+
//[Category("Building")]
149+
//public BuildingComponents BuildingComponents
150+
//{
151+
// get => Model.BuildingComponents;
152+
// set => Model.BuildingComponents = value;
153+
//}
93154

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

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

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

103164
// 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
104165
//[Reactive, Category("Building"), Length(1, BuildingObject.MaxElevatorHeightSequences), Browsable(false)] public BindingList<BindingList<uint8_t>> ElevatorHeightSequences { get; set; } // NumElevatorSequences
105166

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

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

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

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

118179
[Category("<unknown>")]
119180
public uint8_t var_A6

Gui/ViewModels/LocoTypes/Objects/Building/DesignBuildingComponentsViewModel.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ public DesignBuildingComponentsViewModel()
7979
[0, 1],
8080
[0, 1, 1],
8181
];
82+
List<uint8_t> buildingHeights = [16, 16];
8283

83-
RecomputeBuildingVariationViewModels(buildingVariations);
84+
RecomputeBuildingVariationViewModels(buildingVariations, buildingHeights);
8485
}
8586
}

0 commit comments

Comments
 (0)