55using DynamicData . Binding ;
66using PropertyModels . ComponentModel ;
77using PropertyModels . ComponentModel . DataAnnotations ;
8+ using PropertyModels . Extensions ;
89using ReactiveUI ;
910using ReactiveUI . Fody . Helpers ;
1011using System ;
12+ using System . Collections . Generic ;
13+ using System . Collections . ObjectModel ;
14+ using System . Collections . Specialized ;
1115using System . ComponentModel ;
1216using System . ComponentModel . DataAnnotations ;
1317using 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