Skip to content

Commit 5082f15

Browse files
committed
allow custom cargo categories in vehicle compatible cargo. also sync categories and offsets
1 parent c543812 commit 5082f15

2 files changed

Lines changed: 204 additions & 19 deletions

File tree

Dat/Loaders/Vehicle/VehicleObjectLoader.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,10 +323,8 @@ private static void SaveVariable(VehicleObject model, LocoBinaryWriter bw)
323323
bw.WriteEmptyBytes(1); // write a 0 for MaxCargo - this indicates no more cargo and we skip the rest
324324
continue;
325325
}
326-
else
327-
{
328-
bw.Write(model.MaxCargo[i]);
329-
}
326+
327+
bw.Write(model.MaxCargo[i]);
330328

331329
var compatibleCargoCategories = model.CompatibleCargoCategories.Fill(Constants.MaxCompatibleCargoCategories, []).ToArray();
332330
foreach (var cc in compatibleCargoCategories[i])

Gui/ViewModels/LocoTypes/Objects/VehicleViewModel.cs

Lines changed: 202 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
using DynamicData.Binding;
66
using PropertyModels.ComponentModel;
77
using PropertyModels.ComponentModel.DataAnnotations;
8+
using PropertyModels.Extensions;
89
using ReactiveUI;
910
using ReactiveUI.Fody.Helpers;
1011
using System;
12+
using System.Collections.Generic;
13+
using System.Collections.ObjectModel;
14+
using System.Collections.Specialized;
1115
using System.ComponentModel;
1216
using System.ComponentModel.DataAnnotations;
1317
using System.Linq;
@@ -27,8 +31,8 @@ public VehicleViewModel(VehicleObject model) : base(model)
2731
BodySprites = new(model.BodySprites);
2832
BogieSprites = new(model.BogieSprites);
2933
Animation = new(model.ParticleEmitters);
30-
CompatibleCargo1 = new(model.MaxCargo[0], new(model.CompatibleCargoCategories[0]));
31-
CompatibleCargo2 = new(model.MaxCargo[1], new(model.CompatibleCargoCategories[1]));
34+
CompatibleCargo1 = new(model.CompatibleCargoCategories[0], model.MaxCargo[0]);
35+
CompatibleCargo2 = new(model.CompatibleCargoCategories[1], model.MaxCargo[1]);
3236
CargoTypeSpriteOffsets = new([.. model.CargoTypeSpriteOffsets.Select(x => new CargoTypeSpriteOffset(x.Key, x.Value))]);
3337
StartSounds = new(model.StartSounds);
3438
var_135 = new(model.var_135);
@@ -77,6 +81,23 @@ public VehicleViewModel(VehicleObject model) : base(model)
7781
.Subscribe((_) => model.GearboxMotorSound = GearboxMotorSound);
7882

7983
#endregion
84+
85+
#region Cargo Category Synchronization
86+
87+
CompatibleCargo1.CargoCategories.CollectionChanged += OnCompatibleCargo1Changed;
88+
CompatibleCargo2.CargoCategories.CollectionChanged += OnCompatibleCargo2Changed;
89+
90+
// Subscribe to existing items
91+
foreach (var item in CompatibleCargo1.CargoCategories)
92+
{
93+
item.PropertyChanged += OnCargoCategoryPropertyChanged;
94+
}
95+
foreach (var item in CompatibleCargo2.CargoCategories)
96+
{
97+
item.PropertyChanged += OnCargoCategoryPropertyChanged;
98+
}
99+
100+
#endregion
80101
}
81102

82103
[Category("Stats")]
@@ -270,7 +291,7 @@ public CompanyColourType SpecialColourSchemeIndex
270291
[Category("Cargo")]
271292
[Length(0, 32)]
272293
[Description("This is a dictionary. For every cargo defined in both CompatibleCargoCategories, an entry must exist in this dictionary.")]
273-
public BindingList<CargoTypeSpriteOffset> CargoTypeSpriteOffsets { get; init; }
294+
public ObservableCollection<CargoTypeSpriteOffset> CargoTypeSpriteOffsets { get; init; }
274295

275296
[Category("Sound")]
276297
[ConditionTarget]
@@ -315,39 +336,205 @@ bool IsDrivingSoundTypeSet
315336
[Category("<unknown>")]
316337
public BindingList<uint8_t> var_135 { get; init; }
317338

339+
void OnCompatibleCargo1Changed(object? sender, NotifyCollectionChangedEventArgs e)
340+
{
341+
HandleCargoCollectionPropertySubscriptions(e);
342+
SynchronizeCargoTypeSpriteOffsets(e);
343+
}
344+
345+
void OnCompatibleCargo2Changed(object? sender, NotifyCollectionChangedEventArgs e)
346+
{
347+
HandleCargoCollectionPropertySubscriptions(e);
348+
SynchronizeCargoTypeSpriteOffsets(e);
349+
}
350+
351+
void HandleCargoCollectionPropertySubscriptions(NotifyCollectionChangedEventArgs e)
352+
{
353+
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
354+
{
355+
foreach (CargoCategoryViewModel item in e.NewItems)
356+
{
357+
item.PropertyChanged += OnCargoCategoryPropertyChanged;
358+
}
359+
}
360+
else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
361+
{
362+
foreach (CargoCategoryViewModel item in e.OldItems)
363+
{
364+
item.PropertyChanged -= OnCargoCategoryPropertyChanged;
365+
}
366+
}
367+
else if (e.Action == NotifyCollectionChangedAction.Replace)
368+
{
369+
if (e.OldItems != null)
370+
{
371+
foreach (CargoCategoryViewModel item in e.OldItems)
372+
{
373+
item.PropertyChanged -= OnCargoCategoryPropertyChanged;
374+
}
375+
}
376+
if (e.NewItems != null)
377+
{
378+
foreach (CargoCategoryViewModel item in e.NewItems)
379+
{
380+
item.PropertyChanged += OnCargoCategoryPropertyChanged;
381+
}
382+
}
383+
}
384+
}
385+
386+
void OnCargoCategoryPropertyChanged(object? sender, PropertyChangedEventArgs e)
387+
{
388+
if (e.PropertyName == nameof(CargoCategoryViewModel.Category) && sender is CargoCategoryViewModel changedItem)
389+
{
390+
SynchronizeCategoryChange(changedItem);
391+
}
392+
}
393+
394+
void SynchronizeCategoryChange(CargoCategoryViewModel changedItem)
395+
{
396+
// Find if there's an existing offset entry that needs updating
397+
var existingOffset = CargoTypeSpriteOffsets.FirstOrDefault(x => x.CargoCategoryViewModel.Category == changedItem.Category);
398+
if (existingOffset == null)
399+
{
400+
CargoTypeSpriteOffsets.Add(new CargoTypeSpriteOffset(changedItem.Category, 0));
401+
}
402+
403+
// remove anything from CargoTypeSpriteOffsets that has a Category no longer present in either CompatibleCargo1 or CompatibleCargo2
404+
var categoriesInUse = CompatibleCargo1.CargoCategories.Select(x => x.Category)
405+
.Concat(CompatibleCargo2.CargoCategories.Select(x => x.Category))
406+
.Distinct();
407+
408+
var offsetsToRemove = CargoTypeSpriteOffsets.Where(x => !categoriesInUse.Contains(x.CargoCategoryViewModel.Category)).ToList();
409+
foreach (var offset in offsetsToRemove)
410+
{
411+
CargoTypeSpriteOffsets.Remove(offset);
412+
}
413+
}
414+
415+
void SynchronizeCargoTypeSpriteOffsets(NotifyCollectionChangedEventArgs e)
416+
{
417+
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
418+
{
419+
foreach (CargoCategoryViewModel item in e.NewItems)
420+
{
421+
var existingOffset = CargoTypeSpriteOffsets.FirstOrDefault(x => x.CargoCategoryViewModel.Category == item.Category);
422+
if (existingOffset == null)
423+
{
424+
CargoTypeSpriteOffsets.Add(new CargoTypeSpriteOffset(item.Category, 0));
425+
}
426+
}
427+
}
428+
else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
429+
{
430+
foreach (CargoCategoryViewModel item in e.OldItems)
431+
{
432+
var offsetToRemove = CargoTypeSpriteOffsets.FirstOrDefault(x => x.CargoCategoryViewModel.Category == item.Category);
433+
if (offsetToRemove != null)
434+
{
435+
var isStillUsed = CompatibleCargo1.CargoCategories.Any(x => x.Category == item.Category)
436+
|| CompatibleCargo2.CargoCategories.Any(x => x.Category == item.Category);
437+
438+
if (!isStillUsed)
439+
{
440+
CargoTypeSpriteOffsets.Remove(offsetToRemove);
441+
}
442+
}
443+
}
444+
}
445+
}
446+
318447
public override void CopyBackToModel()
319448
{
320449
// this should be done with the reactive properties, but for now we'll leave it like this
321450
Model.MaxCargo = [CompatibleCargo1.MaxCargo, CompatibleCargo2.MaxCargo];
322451
Model.CompatibleCargoCategories =
323452
[
324-
[.. CompatibleCargo1.CompatibleCargoCategories.ToArray()],
325-
[.. CompatibleCargo2.CompatibleCargoCategories.ToArray()]
453+
[.. CompatibleCargo1.CargoCategories.Select(x => x.Category).ToArray()],
454+
[.. CompatibleCargo2.CargoCategories.Select(x => x.Category).ToArray()]
326455
];
327456

328-
Model.NumSimultaneousCargoTypes += (byte)(CompatibleCargo1.CompatibleCargoCategories.Count > 0 ? 1 : 0);
329-
Model.NumSimultaneousCargoTypes += (byte)(CompatibleCargo2.CompatibleCargoCategories.Count > 0 ? 1 : 0);
457+
Model.NumSimultaneousCargoTypes += (byte)(CompatibleCargo1.CargoCategories.Count > 0 ? 1 : 0);
458+
Model.NumSimultaneousCargoTypes += (byte)(CompatibleCargo2.CargoCategories.Count > 0 ? 1 : 0);
330459

331460
foreach (var ctso in CargoTypeSpriteOffsets)
332461
{
333-
Model.CargoTypeSpriteOffsets[ctso.CargoCategory] = ctso.Offset;
462+
Model.CargoTypeSpriteOffsets[ctso.CargoCategoryViewModel.Category] = ctso.Offset;
463+
}
464+
}
465+
}
466+
467+
// todo: use this in CargoObject
468+
[TypeConverter(typeof(ExpandableObjectConverter))]
469+
public class CargoCategoryViewModel : ReactiveUI.ReactiveObject
470+
{
471+
public CargoCategory Category
472+
{
473+
get;
474+
set
475+
{
476+
if (field != value)
477+
{
478+
field = value;
479+
this.RaisePropertyChanged(nameof(Category));
480+
this.RaisePropertyChanged(nameof(Override));
481+
}
334482
}
335483
}
484+
485+
public uint16_t Override
486+
{
487+
get => (uint16_t)Category;
488+
set
489+
{
490+
if ((uint16_t)Category != value)
491+
{
492+
Category = (CargoCategory)value;
493+
this.RaisePropertyChanged(nameof(Category));
494+
this.RaisePropertyChanged(nameof(Override));
495+
}
496+
}
497+
}
498+
499+
public CargoCategoryViewModel(CargoCategory cargoCategory)
500+
=> Category = cargoCategory;
501+
502+
public CargoCategoryViewModel()
503+
: this(CargoCategory.NULL)
504+
{ }
336505
}
337506

338507
[TypeConverter(typeof(ExpandableObjectConverter))]
339-
public class CargoTypeSpriteOffset(CargoCategory CargoCategory, uint8_t Offset)
508+
public class CargoTypeSpriteOffset : ReactiveUI.ReactiveObject
340509
{
341-
public CargoTypeSpriteOffset() : this(CargoCategory.NULL, 0)
510+
public CargoTypeSpriteOffset(CargoCategory cargoCategory, uint8_t offset)
511+
{
512+
CargoCategoryViewModel = new CargoCategoryViewModel(cargoCategory);
513+
Offset = offset;
514+
}
515+
516+
public CargoTypeSpriteOffset()
517+
: this(CargoCategory.NULL, 0)
342518
{ }
343519

344-
public CargoCategory CargoCategory { get; set; } = CargoCategory;
345-
public byte Offset { get; set; } = Offset;
520+
public CargoCategoryViewModel CargoCategoryViewModel { get; set; }
521+
522+
public byte Offset { get; set; }
346523
}
347524

348525
[TypeConverter(typeof(ExpandableObjectConverter))]
349-
public class CompatibleCargo(uint8_t MaxCargo, BindingList<CargoCategory> CompatibleCargoCategories)
526+
public class CompatibleCargo : ReactiveUI.ReactiveObject
350527
{
351-
public byte MaxCargo { get; set; } = MaxCargo;
352-
public BindingList<CargoCategory> CompatibleCargoCategories { get; init; } = CompatibleCargoCategories;
528+
public byte MaxCargo { get; set; }
529+
public ObservableCollection<CargoCategoryViewModel> CargoCategories { get; init; }
530+
531+
public CompatibleCargo(IEnumerable<CargoCategory> compatibleCargoCategories, uint8_t maxCargo)
532+
{
533+
MaxCargo = maxCargo;
534+
CargoCategories = new(compatibleCargoCategories.Select(x => new CargoCategoryViewModel(x)));
535+
}
536+
537+
public CompatibleCargo()
538+
: this([], 0)
539+
{ }
353540
}

0 commit comments

Comments
 (0)