From 7f44bc2e1e97b4546d7de05946cd30e5409010ff Mon Sep 17 00:00:00 2001 From: montanon Date: Sun, 15 Mar 2026 01:09:42 -0300 Subject: [PATCH] Add Gen 5-6 subform dialogs 17 generation-specific subforms: Gen 5: Misc5, Pokedex5, DLC5, UnityTower5, CGearImage5 Gen 6: Trainer6, SecretBase6, PokedexXY, PokedexORAS, HallOfFame6, SuperTrain6, Link6, OPower6, Pokepuff6, BerryFieldXY, PokeBlockORAS, Roamer6 --- .../Subforms/CGearImage5ViewModel.cs | 29 ++ .../ViewModels/Subforms/DLC5ViewModel.cs | 164 +++++++ .../ViewModels/Subforms/Misc5ViewModel.cs | 425 ++++++++++++++++++ .../ViewModels/Subforms/Pokedex5ViewModel.cs | 208 +++++++++ .../Subforms/SAVBerryFieldXYViewModel.cs | 97 ++++ .../Subforms/SAVHallOfFame6ViewModel.cs | 229 ++++++++++ .../ViewModels/Subforms/SAVLink6ViewModel.cs | 163 +++++++ .../Subforms/SAVOPower6ViewModel.cs | 151 +++++++ .../Subforms/SAVPokeBlockORASViewModel.cs | 74 +++ .../Subforms/SAVPokedexORASViewModel.cs | 233 ++++++++++ .../Subforms/SAVPokedexXYViewModel.cs | 213 +++++++++ .../Subforms/SAVPokepuff6ViewModel.cs | 113 +++++ .../Subforms/SAVRoamer6ViewModel.cs | 68 +++ .../Subforms/SAVSecretBase6ViewModel.cs | 182 ++++++++ .../Subforms/SAVSuperTrain6ViewModel.cs | 162 +++++++ .../Subforms/SAVTrainer6ViewModel.cs | 208 +++++++++ .../Subforms/UnityTower5ViewModel.cs | 166 +++++++ .../Views/Subforms/CGearImage5View.axaml | 34 ++ .../Views/Subforms/CGearImage5View.axaml.cs | 16 + PKHeX.Avalonia/Views/Subforms/DLC5View.axaml | 94 ++++ .../Views/Subforms/DLC5View.axaml.cs | 24 + PKHeX.Avalonia/Views/Subforms/Misc5View.axaml | 181 ++++++++ .../Views/Subforms/Misc5View.axaml.cs | 24 + .../Views/Subforms/Pokedex5View.axaml | 60 +++ .../Views/Subforms/Pokedex5View.axaml.cs | 24 + .../Views/Subforms/SAVBerryFieldXYView.axaml | 77 ++++ .../Subforms/SAVBerryFieldXYView.axaml.cs | 16 + .../Views/Subforms/SAVHallOfFame6View.axaml | 128 ++++++ .../Subforms/SAVHallOfFame6View.axaml.cs | 24 + .../Views/Subforms/SAVLink6View.axaml | 74 +++ .../Views/Subforms/SAVLink6View.axaml.cs | 24 + .../Views/Subforms/SAVOPower6View.axaml | 79 ++++ .../Views/Subforms/SAVOPower6View.axaml.cs | 24 + .../Views/Subforms/SAVPokeBlockORASView.axaml | 39 ++ .../Subforms/SAVPokeBlockORASView.axaml.cs | 24 + .../Views/Subforms/SAVPokedexORASView.axaml | 93 ++++ .../Subforms/SAVPokedexORASView.axaml.cs | 24 + .../Views/Subforms/SAVPokedexXYView.axaml | 75 ++++ .../Views/Subforms/SAVPokedexXYView.axaml.cs | 24 + .../Views/Subforms/SAVPokepuff6View.axaml | 43 ++ .../Views/Subforms/SAVPokepuff6View.axaml.cs | 24 + .../Views/Subforms/SAVRoamer6View.axaml | 41 ++ .../Views/Subforms/SAVRoamer6View.axaml.cs | 24 + .../Views/Subforms/SAVSecretBase6View.axaml | 87 ++++ .../Subforms/SAVSecretBase6View.axaml.cs | 24 + .../Views/Subforms/SAVSuperTrain6View.axaml | 95 ++++ .../Subforms/SAVSuperTrain6View.axaml.cs | 24 + .../Views/Subforms/SAVTrainer6View.axaml | 123 +++++ .../Views/Subforms/SAVTrainer6View.axaml.cs | 24 + .../Views/Subforms/UnityTower5View.axaml | 68 +++ .../Views/Subforms/UnityTower5View.axaml.cs | 24 + 51 files changed, 4668 insertions(+) create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/CGearImage5ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/DLC5ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/Misc5ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/Pokedex5ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVBerryFieldXYViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVHallOfFame6ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVLink6ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVOPower6ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVPokeBlockORASViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVPokedexORASViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVPokedexXYViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVPokepuff6ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVRoamer6ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVSecretBase6ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVSuperTrain6ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/SAVTrainer6ViewModel.cs create mode 100644 PKHeX.Avalonia/ViewModels/Subforms/UnityTower5ViewModel.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/CGearImage5View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/CGearImage5View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/DLC5View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/DLC5View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/Misc5View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/Misc5View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/Pokedex5View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/Pokedex5View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVBerryFieldXYView.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVBerryFieldXYView.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVHallOfFame6View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVHallOfFame6View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVLink6View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVLink6View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVOPower6View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVOPower6View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVPokeBlockORASView.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVPokeBlockORASView.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVPokedexORASView.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVPokedexORASView.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVPokedexXYView.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVPokedexXYView.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVPokepuff6View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVPokepuff6View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVRoamer6View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVRoamer6View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVSecretBase6View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVSecretBase6View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVSuperTrain6View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVSuperTrain6View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVTrainer6View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/SAVTrainer6View.axaml.cs create mode 100644 PKHeX.Avalonia/Views/Subforms/UnityTower5View.axaml create mode 100644 PKHeX.Avalonia/Views/Subforms/UnityTower5View.axaml.cs diff --git a/PKHeX.Avalonia/ViewModels/Subforms/CGearImage5ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/CGearImage5ViewModel.cs new file mode 100644 index 000000000..68c9e958f --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/CGearImage5ViewModel.cs @@ -0,0 +1,29 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// ViewModel for the C-Gear skin image display (Gen 5). +/// Shows the current C-Gear background skin dimensions and status. +/// +public partial class CGearImage5ViewModel : SaveEditorViewModelBase +{ + [ObservableProperty] private bool _hasSkin; + [ObservableProperty] private string _skinStatus = "No C-Gear skin loaded"; + [ObservableProperty] private int _width; + [ObservableProperty] private int _height; + + public CGearImage5ViewModel(SAV5 sav) : base(sav) + { + var data = sav.CGearSkinData; + CGearBackground bg = sav is SAV5BW ? new CGearBackgroundBW(data) : new CGearBackgroundB2W2(data); + + _width = CGearBackground.Width; + _height = CGearBackground.Height; + _hasSkin = !bg.IsUninitialized; + _skinStatus = _hasSkin + ? $"C-Gear skin loaded ({_width}x{_height})" + : "No C-Gear skin loaded"; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/DLC5ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/DLC5ViewModel.cs new file mode 100644 index 000000000..07a3469a8 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/DLC5ViewModel.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for a Battle Video entry. +/// +public class BattleVideo5Entry +{ + public int Index { get; } + public string Label { get; } + + public BattleVideo5Entry(int index, string label) + { + Index = index; + Label = label; + } +} + +/// +/// Model for a PWT entry. +/// +public class PWT5Entry +{ + public int Index { get; } + public string Label { get; } + + public PWT5Entry(int index, string label) + { + Index = index; + Label = label; + } +} + +/// +/// Model for a Pokestar movie entry. +/// +public class Pokestar5Entry +{ + public int Index { get; } + public string Label { get; } + + public Pokestar5Entry(int index, string label) + { + Index = index; + Label = label; + } +} + +/// +/// ViewModel for the Gen 5 DLC content editor. +/// Manages C-Gear skins, Battle Videos, PWT, Pokestar Movies, Musical Shows, Memory Links, and Pokedex Skins. +/// +public partial class DLC5ViewModel : SaveEditorViewModelBase +{ + private readonly SAV5 SAV5; + + // C-Gear + [ObservableProperty] private bool _hasCGear; + [ObservableProperty] private string _cGearStatus = "Not loaded"; + + // Battle Videos + public ObservableCollection BattleVideos { get; } = []; + + [ObservableProperty] + private int _selectedBattleVideoIndex; + + // PWT (B2W2 only) + public bool ShowPWT { get; } + public ObservableCollection PWTEntries { get; } = []; + + [ObservableProperty] + private int _selectedPWTIndex; + + // Pokestar (B2W2 only) + public bool ShowPokestar { get; } + public ObservableCollection PokestarMovies { get; } = []; + + [ObservableProperty] + private int _selectedPokestarIndex; + + // Musical + [ObservableProperty] private string _musicalName = string.Empty; + + // Memory Link + [ObservableProperty] private string _link1Status = "Link 1"; + [ObservableProperty] private string _link2Status = "Link 2"; + + public DLC5ViewModel(SAV5 sav) : base(sav) + { + SAV5 = sav; + ShowPWT = sav is SAV5B2W2; + ShowPokestar = sav is SAV5B2W2; + + LoadCGear(); + LoadBattleVideos(); + LoadPWT(); + LoadPokestar(); + LoadMusical(); + } + + private void LoadCGear() + { + var data = SAV5.CGearSkinData; + var bg = SAV5 is SAV5BW ? (CGearBackground)new CGearBackgroundBW(data) : new CGearBackgroundB2W2(data); + _hasCGear = !bg.IsUninitialized; + _cGearStatus = _hasCGear ? "C-Gear skin loaded" : "No C-Gear skin"; + } + + private void LoadBattleVideos() + { + for (int i = 0; i < 4; i++) + { + var data = SAV5.GetBattleVideo(i); + var bvid = new BattleVideo5(data); + var name = bvid.IsUninitialized ? "Empty" : bvid.GetTrainerNames(); + BattleVideos.Add(new BattleVideo5Entry(i, $"{i:00} - {name}")); + } + } + + private void LoadPWT() + { + if (SAV5 is not SAV5B2W2 b2w2) + return; + for (int i = 0; i < SAV5B2W2.PWTCount; i++) + { + var data = b2w2.GetPWT(i); + var pwt = new WorldTournament5(data); + var name = pwt.Name; + if (string.IsNullOrWhiteSpace(name)) + name = "Empty"; + PWTEntries.Add(new PWT5Entry(i, $"{i + 1:00} - {name}")); + } + } + + private void LoadPokestar() + { + if (SAV5 is not SAV5B2W2 b2w2) + return; + for (int i = 0; i < SAV5B2W2.PokestarCount; i++) + { + var data = b2w2.GetPokestarMovie(i); + var movie = new PokestarMovie5(data); + PokestarMovies.Add(new Pokestar5Entry(i, $"{i + 1:00} - {movie.Name}")); + } + } + + private void LoadMusical() + { + _musicalName = SAV5.Musical.MusicalName; + } + + [RelayCommand] + private void Save() + { + SAV5.Musical.MusicalName = MusicalName; + SAV.State.Edited = true; + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Misc5ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Misc5ViewModel.cs new file mode 100644 index 000000000..6a8a48ba3 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/Misc5ViewModel.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for a fly destination entry (Gen 5). +/// +public partial class FlyDest5Model : ObservableObject +{ + public int FlagBit { get; } + public string Name { get; } + + [ObservableProperty] + private bool _unlocked; + + public FlyDest5Model(int flagBit, string name, bool unlocked) + { + FlagBit = flagBit; + Name = name; + _unlocked = unlocked; + } +} + +/// +/// Model for a Key System entry (B2W2). +/// +public partial class KeyEntry5Model : ObservableObject +{ + public int Index { get; } + public string Label { get; } + public bool IsObtain { get; } + + [ObservableProperty] + private bool _enabled; + + public KeyEntry5Model(int index, string label, bool isObtain, bool enabled) + { + Index = index; + Label = label; + IsObtain = isObtain; + _enabled = enabled; + } +} + +/// +/// Model for a Musical prop entry. +/// +public partial class PropModel : ObservableObject +{ + public int Index { get; } + public string Name { get; } + + [ObservableProperty] + private bool _obtained; + + public PropModel(int index, string name, bool obtained) + { + Index = index; + Name = name; + _obtained = obtained; + } +} + +/// +/// ViewModel for the Gen 5 Misc editor. +/// Covers Fly Destinations, Roamer (BW), Key System (B2W2), Musical Props, Records, and Battle Subway. +/// +public partial class Misc5ViewModel : SaveEditorViewModelBase +{ + private readonly SAV5 SAV5; + private readonly BattleSubway5 Subway; + private readonly BattleSubwayPlay5 SubwayPlay; + private readonly Record5 Record; + + // Fly + public ObservableCollection FlyDestinations { get; } = []; + private int _flyOffset; + private int[] _flyDestC = []; + + // Roamer (BW only) + public bool ShowRoamer { get; } + + [ObservableProperty] private int _roamer641State; + [ObservableProperty] private int _roamer642State; + [ObservableProperty] private int _roamStatus; + [ObservableProperty] private bool _libertyPass; + + public string[] RoamerStates { get; } = ["Not roamed", "Roaming", "Defeated", "Captured"]; + public string[] RoamStatusStates { get; } = ["Not happened", "Go to route 7", "Unknown (2)", "Event finished"]; + + // Key System (B2W2 only) + public bool ShowKeySystem { get; } + public ObservableCollection KeyEntries { get; } = []; + + // Musical Props + public ObservableCollection Props { get; } = []; + + // Battle Subway + [ObservableProperty] private int _subwayCurrentType; + [ObservableProperty] private int _subwayCurrentBattle; + + // Subway flags + [ObservableProperty] private bool _subwayFlag0; + [ObservableProperty] private bool _subwayFlag1; + [ObservableProperty] private bool _subwayFlag2; + [ObservableProperty] private bool _subwayFlag3; + [ObservableProperty] private bool _superSingle; + [ObservableProperty] private bool _superDouble; + [ObservableProperty] private bool _superMulti; + [ObservableProperty] private bool _subwayFlag7; + [ObservableProperty] private bool _npcMet; + + // Subway records + [ObservableProperty] private int _singlePast; + [ObservableProperty] private int _singleRecord; + [ObservableProperty] private int _doublePast; + [ObservableProperty] private int _doubleRecord; + [ObservableProperty] private int _multiNpcPast; + [ObservableProperty] private int _multiNpcRecord; + [ObservableProperty] private int _multiFriendsPast; + [ObservableProperty] private int _multiFriendsRecord; + [ObservableProperty] private int _superSinglePast; + [ObservableProperty] private int _superSingleRecord; + [ObservableProperty] private int _superDoublePast; + [ObservableProperty] private int _superDoubleRecord; + [ObservableProperty] private int _superMultiNpcPast; + [ObservableProperty] private int _superMultiNpcRecord; + [ObservableProperty] private int _superMultiFriendsPast; + [ObservableProperty] private int _superMultiFriendsRecord; + + // Records + [ObservableProperty] private int _record16Index; + [ObservableProperty] private int _record16Value; + [ObservableProperty] private int _record32Index; + [ObservableProperty] private uint _record32Value; + + public Misc5ViewModel(SAV5 sav) : base(sav) + { + SAV5 = sav; + Subway = sav.BattleSubway; + SubwayPlay = sav.BattleSubwayPlay; + Record = sav.Records; + + ReadFly(); + ReadRoamer(); + ReadKeySystem(); + ReadMusical(); + ReadSubway(); + ReadRecords(); + + ShowRoamer = sav is SAV5BW; + ShowKeySystem = sav is SAV5B2W2; + } + + private void ReadFly() + { + string[] flyNames; + switch (SAV5.Version) + { + case GameVersion.B or GameVersion.W or GameVersion.BW: + _flyOffset = 0x204B2; + flyNames = [ + "Nuvema Town", "Accumula Town", "Striaton City", "Nacrene City", + "Castelia City", "Nimbasa City", "Driftveil City", "Mistralton City", + "Icirrus City", "Opelucid City", "Victory Road", "Pokemon League", + "Lacunosa Town", "Undella Town", "Black City/White Forest", "(Unity Tower)", + ]; + _flyDestC = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 11, 10, 13, 12, 14]; + break; + case GameVersion.B2 or GameVersion.W2 or GameVersion.B2W2: + _flyOffset = 0x20392; + flyNames = [ + "Aspertia City", "Floccesy Town", "Virbank City", + "Nuvema Town", "Accumula Town", "Striaton City", "Nacrene City", + "Castelia City", "Nimbasa City", "Driftveil City", "Mistralton City", + "Icirrus City", "Opelucid City", + "Lacunosa Town", "Undella Town", "Black City/White Forest", + "Lentimas Town", "Humilau City", "Victory Road", "Pokemon League", + "Pokestar Studios", "Join Avenue", "PWT", "(Unity Tower)", + ]; + _flyDestC = [24, 27, 25, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 20, 28, 26, 66, 19, 5, 6, 7, 22]; + break; + default: + flyNames = []; + break; + } + + uint valFly = ReadUInt32LittleEndian(SAV5.Data[_flyOffset..]); + for (int i = 0; i < flyNames.Length; i++) + { + bool isSet; + if (_flyDestC[i] < 32) + isSet = (valFly & (1u << _flyDestC[i])) != 0; + else + isSet = (SAV5.Data[_flyOffset + (_flyDestC[i] >> 3)] & (1 << (_flyDestC[i] & 7))) != 0; + FlyDestinations.Add(new FlyDest5Model(_flyDestC[i], flyNames[i], isSet)); + } + } + + private void ReadRoamer() + { + if (SAV5 is SAV5BW bw) + { + var encount = bw.Encount; + _roamer641State = Math.Clamp((int)encount.GetRoamerState(1), (int)0, (int)3); + _roamer642State = Math.Clamp((int)encount.GetRoamerState(0), (int)0, (int)3); + _roamStatus = Math.Clamp((int)bw.EventWork.GetWorkRoamer(), (int)0, (int)3); + _libertyPass = bw.Misc.IsLibertyTicketActivated; + } + } + + private void ReadKeySystem() + { + if (SAV5 is SAV5B2W2 b2w2) + { + var keys = b2w2.Keys; + string[] keyNames = ["EasyKey", "ChallengeKey", "CityKey", "IronKey", "IcebergKey"]; + for (int i = 0; i < 5; i++) + { + KeyEntries.Add(new KeyEntry5Model(i, $"Obtain {keyNames[i]}", true, keys.GetIsKeyObtained((KeyType5)i))); + KeyEntries.Add(new KeyEntry5Model(i, $"Unlock {keyNames[i]}", false, keys.GetIsKeyUnlocked((KeyType5)i))); + } + } + } + + private void ReadMusical() + { + var musical = SAV5.Musical; + // Musical has 100 props + for (int i = 0; i < 100; i++) + { + var name = $"Prop {i:000}"; + Props.Add(new PropModel(i, name, musical.GetHasProp(i))); + } + } + + private void ReadSubway() + { + _subwayCurrentType = SubwayPlay.CurrentType; + _subwayCurrentBattle = SubwayPlay.CurrentBattle; + + _subwayFlag0 = Subway.Flag0; + _subwayFlag1 = Subway.Flag1; + _subwayFlag2 = Subway.Flag2; + _subwayFlag3 = Subway.Flag3; + _superSingle = Subway.SuperSingle; + _superDouble = Subway.SuperDouble; + _superMulti = Subway.SuperMulti; + _subwayFlag7 = Subway.Flag7; + _npcMet = Subway.NPCMet; + + _singlePast = Subway.SinglePast; + _singleRecord = Subway.SingleRecord; + _doublePast = Subway.DoublePast; + _doubleRecord = Subway.DoubleRecord; + _multiNpcPast = Subway.MultiNPCPast; + _multiNpcRecord = Subway.MultiNPCRecord; + _multiFriendsPast = Subway.MultiFriendsPast; + _multiFriendsRecord = Subway.MultiFriendsRecord; + _superSinglePast = Subway.SuperSinglePast; + _superSingleRecord = Subway.SuperSingleRecord; + _superDoublePast = Subway.SuperDoublePast; + _superDoubleRecord = Subway.SuperDoubleRecord; + _superMultiNpcPast = Subway.SuperMultiNPCPast; + _superMultiNpcRecord = Subway.SuperMultiNPCRecord; + _superMultiFriendsPast = Subway.SuperMultiFriendsPast; + _superMultiFriendsRecord = Subway.SuperMultiFriendsRecord; + } + + private void ReadRecords() + { + _record16Value = Record.GetRecord16(0); + _record32Value = Record.GetRecord32(0); + } + + partial void OnRecord16IndexChanged(int value) + { + if (value >= 0 && value < Record5.Record16) + Record16Value = Record.GetRecord16(value); + } + + partial void OnRecord32IndexChanged(int value) + { + if (value >= 0 && value < Record5.Record32) + Record32Value = Record.GetRecord32(value); + } + + [RelayCommand] + private void UnlockAllFlyDest() + { + foreach (var dest in FlyDestinations) + dest.Unlocked = true; + } + + [RelayCommand] + private void UnlockAllKeys() + { + foreach (var entry in KeyEntries) + entry.Enabled = true; + } + + [RelayCommand] + private void UnlockAllProps() + { + foreach (var prop in Props) + prop.Obtained = true; + } + + [RelayCommand] + private void Save() + { + SaveFly(); + SaveRoamer(); + SaveKeySystem(); + SaveMusical(); + SaveSubway(); + SaveRecords(); + + SAV.State.Edited = true; + Modified = true; + } + + private void SaveFly() + { + uint valFly = ReadUInt32LittleEndian(SAV5.Data[_flyOffset..]); + for (int i = 0; i < FlyDestinations.Count; i++) + { + var dest = FlyDestinations[i]; + if (_flyDestC[i] < 32) + { + if (dest.Unlocked) + valFly |= 1u << _flyDestC[i]; + else + valFly &= ~(1u << _flyDestC[i]); + } + else + { + var ofs = _flyOffset + (_flyDestC[i] >> 3); + SAV5.Data[ofs] = (byte)((SAV5.Data[ofs] & ~(1 << (_flyDestC[i] & 7))) | ((dest.Unlocked ? 1 : 0) << (_flyDestC[i] & 7))); + } + } + WriteUInt32LittleEndian(SAV5.Data[_flyOffset..], valFly); + } + + private void SaveRoamer() + { + if (SAV5 is SAV5BW bw) + { + var encount = bw.Encount; + encount.SetRoamerState(1, (byte)Roamer641State); + encount.SetRoamerState(0, (byte)Roamer642State); + bw.EventWork.SetWorkRoamer((ushort)RoamStatus); + + if (LibertyPass != bw.Misc.IsLibertyTicketActivated) + bw.Misc.IsLibertyTicketActivated = LibertyPass; + } + } + + private void SaveKeySystem() + { + if (SAV5 is SAV5B2W2 b2w2) + { + var keys = b2w2.Keys; + for (int i = 0; i < 5; i++) + { + var obtainIndex = i * 2; + var unlockIndex = obtainIndex + 1; + keys.SetIsKeyObtained((KeyType5)i, KeyEntries[obtainIndex].Enabled); + keys.SetIsKeyUnlocked((KeyType5)i, KeyEntries[unlockIndex].Enabled); + } + } + } + + private void SaveMusical() + { + var musical = SAV5.Musical; + foreach (var prop in Props) + musical.SetHasProp(prop.Index, prop.Obtained); + } + + private void SaveSubway() + { + SubwayPlay.CurrentType = SubwayCurrentType; + SubwayPlay.CurrentBattle = SubwayCurrentBattle; + + Subway.Flag0 = SubwayFlag0; + Subway.Flag1 = SubwayFlag1; + Subway.Flag2 = SubwayFlag2; + Subway.Flag3 = SubwayFlag3; + Subway.SuperSingle = SuperSingle; + Subway.SuperDouble = SuperDouble; + Subway.SuperMulti = SuperMulti; + Subway.Flag7 = SubwayFlag7; + Subway.NPCMet = NpcMet; + + Subway.SinglePast = SinglePast; + Subway.SingleRecord = SingleRecord; + Subway.DoublePast = DoublePast; + Subway.DoubleRecord = DoubleRecord; + Subway.MultiNPCPast = MultiNpcPast; + Subway.MultiNPCRecord = MultiNpcRecord; + Subway.MultiFriendsPast = MultiFriendsPast; + Subway.MultiFriendsRecord = MultiFriendsRecord; + Subway.SuperSinglePast = SuperSinglePast; + Subway.SuperSingleRecord = SuperSingleRecord; + Subway.SuperDoublePast = SuperDoublePast; + Subway.SuperDoubleRecord = SuperDoubleRecord; + Subway.SuperMultiNPCPast = SuperMultiNpcPast; + Subway.SuperMultiNPCRecord = SuperMultiNpcRecord; + Subway.SuperMultiFriendsPast = SuperMultiFriendsPast; + Subway.SuperMultiFriendsRecord = SuperMultiFriendsRecord; + } + + private void SaveRecords() + { + Record.SetRecord16(Record16Index, (ushort)Record16Value); + Record.SetRecord32(Record32Index, Record32Value); + Record.EndAccess(); + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Pokedex5ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Pokedex5ViewModel.cs new file mode 100644 index 000000000..e2d966955 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/Pokedex5ViewModel.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for a single Gen 5 Pokedex species entry. +/// +public partial class Pokedex5EntryModel : ObservableObject +{ + public ushort Species { get; } + public string Label { get; } + + // Caught + [ObservableProperty] private bool _caught; + + // Seen: Male, Female, Male Shiny, Female Shiny + [ObservableProperty] private bool _seenMale; + [ObservableProperty] private bool _seenFemale; + [ObservableProperty] private bool _seenMaleShiny; + [ObservableProperty] private bool _seenFemaleShiny; + + // Displayed: Male, Female, Male Shiny, Female Shiny + [ObservableProperty] private bool _dispMale; + [ObservableProperty] private bool _dispFemale; + [ObservableProperty] private bool _dispMaleShiny; + [ObservableProperty] private bool _dispFemaleShiny; + + // Language flags (7 languages: JPN, ENG, FRE, ITA, GER, SPA, KOR) + [ObservableProperty] private bool[] _languages; + + public bool HasLanguages { get; } + + public Pokedex5EntryModel(ushort species, string label, bool hasLanguages) + { + Species = species; + Label = label; + HasLanguages = hasLanguages; + _languages = new bool[7]; + } +} + +/// +/// ViewModel for the Gen 5 Pokedex editor. +/// Edits seen/caught/language/form status per species. +/// +public partial class Pokedex5ViewModel : SaveEditorViewModelBase +{ + private readonly SAV5 SAV5; + private readonly Zukan5 Dex; + private const int LangCount = 7; + + [ObservableProperty] private string _searchText = string.Empty; + [ObservableProperty] private bool _nationalDexUnlocked; + [ObservableProperty] private bool _nationalDexActive; + [ObservableProperty] private string _spindaPid = "00000000"; + + public ObservableCollection AllEntries { get; } = []; + + [ObservableProperty] + private ObservableCollection _filteredEntries = []; + + public Pokedex5ViewModel(SAV5 sav) : base(sav) + { + SAV5 = sav; + Dex = sav.Zukan; + + _nationalDexUnlocked = Dex.IsNationalDexUnlocked; + _nationalDexActive = Dex.IsNationalDexMode; + _spindaPid = Dex.Spinda.ToString("X8"); + + var speciesNames = GameInfo.Strings.specieslist; + for (ushort i = 1; i <= sav.MaxSpeciesID; i++) + { + var name = i < speciesNames.Length ? speciesNames[i] : $"Species {i}"; + var label = $"{i:000} - {name}"; + bool hasLangs = i <= 493; // Language flags only for Gen 1-4 species + + var entry = new Pokedex5EntryModel(i, label, hasLangs) + { + Caught = Dex.GetCaught(i), + SeenMale = Dex.GetSeen(i, 0), + SeenFemale = Dex.GetSeen(i, 1), + SeenMaleShiny = Dex.GetSeen(i, 2), + SeenFemaleShiny = Dex.GetSeen(i, 3), + DispMale = Dex.GetDisplayed(i, 0), + DispFemale = Dex.GetDisplayed(i, 1), + DispMaleShiny = Dex.GetDisplayed(i, 2), + DispFemaleShiny = Dex.GetDisplayed(i, 3), + }; + + if (hasLangs) + { + var langs = new bool[LangCount]; + for (int l = 0; l < LangCount; l++) + langs[l] = Dex.GetLanguageFlag(i, l); + entry.Languages = langs; + } + + AllEntries.Add(entry); + } + + FilteredEntries = new ObservableCollection(AllEntries); + } + + partial void OnSearchTextChanged(string value) => ApplyFilter(); + + private void ApplyFilter() + { + if (string.IsNullOrWhiteSpace(SearchText)) + { + FilteredEntries = new ObservableCollection(AllEntries); + return; + } + FilteredEntries = new ObservableCollection( + AllEntries.Where(e => e.Label.Contains(SearchText, StringComparison.OrdinalIgnoreCase))); + } + + [RelayCommand] + private void SeenAll() + { + foreach (var entry in AllEntries) + { + entry.SeenMale = true; + entry.SeenFemale = true; + if (!entry.DispMale && !entry.DispFemale && !entry.DispMaleShiny && !entry.DispFemaleShiny) + entry.DispMale = true; + } + } + + [RelayCommand] + private void CaughtAll() + { + foreach (var entry in AllEntries) + { + entry.Caught = true; + if (entry.HasLanguages) + { + var langs = new bool[LangCount]; + for (int i = 0; i < LangCount; i++) + langs[i] = true; + entry.Languages = langs; + } + } + } + + [RelayCommand] + private void ClearAll() + { + foreach (var entry in AllEntries) + { + entry.Caught = false; + entry.SeenMale = false; + entry.SeenFemale = false; + entry.SeenMaleShiny = false; + entry.SeenFemaleShiny = false; + entry.DispMale = false; + entry.DispFemale = false; + entry.DispMaleShiny = false; + entry.DispFemaleShiny = false; + if (entry.HasLanguages) + entry.Languages = new bool[LangCount]; + } + } + + [RelayCommand] + private void CompleteAll() + { + SeenAll(); + CaughtAll(); + } + + [RelayCommand] + private void Save() + { + foreach (var entry in AllEntries) + { + var species = entry.Species; + Dex.SetCaught(species, entry.Caught); + Dex.SetSeen(species, 0, entry.SeenMale); + Dex.SetSeen(species, 1, entry.SeenFemale); + Dex.SetSeen(species, 2, entry.SeenMaleShiny); + Dex.SetSeen(species, 3, entry.SeenFemaleShiny); + Dex.SetDisplayed(species, 0, entry.DispMale); + Dex.SetDisplayed(species, 1, entry.DispFemale); + Dex.SetDisplayed(species, 2, entry.DispMaleShiny); + Dex.SetDisplayed(species, 3, entry.DispFemaleShiny); + + if (entry.HasLanguages) + { + for (int l = 0; l < LangCount; l++) + Dex.SetLanguageFlag(species, l, entry.Languages[l]); + } + } + + Dex.IsNationalDexUnlocked = NationalDexUnlocked; + Dex.IsNationalDexMode = NationalDexActive; + if (uint.TryParse(SpindaPid, System.Globalization.NumberStyles.HexNumber, null, out var spinda)) + Dex.Spinda = spinda; + + SAV.State.Edited = true; + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVBerryFieldXYViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVBerryFieldXYViewModel.cs new file mode 100644 index 000000000..7f6ccaf57 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVBerryFieldXYViewModel.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for a single berry plot entry. +/// +public partial class BerryPlotModel : ObservableObject +{ + public int Index { get; } + + [ObservableProperty] + private int _berry; + + [ObservableProperty] + private int _u1; + + [ObservableProperty] + private int _u2; + + [ObservableProperty] + private int _u3; + + [ObservableProperty] + private int _u4; + + [ObservableProperty] + private int _u5; + + [ObservableProperty] + private int _u6; + + [ObservableProperty] + private int _u7; + + public string DisplayName => $"Plot {Index + 1}"; + + public BerryPlotModel(int index) => Index = index; +} + +/// +/// ViewModel for the Gen 6 XY Berry Field viewer. +/// Read-only display of berry plots (no save, just viewing). +/// +public partial class SAVBerryFieldXYViewModel : SaveEditorViewModelBase +{ + private readonly SAV6XY _sav; + + public ObservableCollection Plots { get; } = []; + + [ObservableProperty] + private BerryPlotModel? _selectedPlot; + + [ObservableProperty] + private int _selectedPlotIndex = -1; + + public SAVBerryFieldXYViewModel(SAV6XY sav) : base(sav) + { + _sav = sav; + + for (int i = 0; i < BerryField6XY.Count; i++) + { + var model = new BerryPlotModel(i); + Plots.Add(model); + } + + if (Plots.Count > 0) + SelectedPlotIndex = 0; + } + + partial void OnSelectedPlotIndexChanged(int value) + { + if (value < 0 || value >= Plots.Count) + return; + LoadPlot(value); + SelectedPlot = Plots[value]; + } + + private void LoadPlot(int index) + { + var span = _sav.BerryField.GetPlot(index); + var model = Plots[index]; + model.Berry = ReadUInt16LittleEndian(span); + model.U1 = ReadUInt16LittleEndian(span[(2 * 1)..]); + model.U2 = ReadUInt16LittleEndian(span[(2 * 2)..]); + model.U3 = ReadUInt16LittleEndian(span[(2 * 3)..]); + model.U4 = ReadUInt16LittleEndian(span[(2 * 4)..]); + model.U5 = ReadUInt16LittleEndian(span[(2 * 5)..]); + model.U6 = ReadUInt16LittleEndian(span[(2 * 6)..]); + model.U7 = ReadUInt16LittleEndian(span[(2 * 7)..]); + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVHallOfFame6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVHallOfFame6ViewModel.cs new file mode 100644 index 000000000..a04247cb3 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVHallOfFame6ViewModel.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// ViewModel for the Gen 6 Hall of Fame viewer/editor. +/// Shows 16 entries with 6 team members each. +/// +public partial class SAVHallOfFame6ViewModel : SaveEditorViewModelBase +{ + private readonly SAV6 _origin; + private readonly SAV6 _sav; + private readonly HallOfFame6 _fame; + private bool _loading; + + public List EntryNames { get; } = []; + public List SpeciesList { get; } + public List ItemsList { get; } + public List MovesList { get; } + + [ObservableProperty] + private int _selectedEntryIndex = -1; + + [ObservableProperty] + private int _memberIndex = 1; + + [ObservableProperty] + private int _maxMembers = 6; + + [ObservableProperty] + private int _selectedSpecies; + + [ObservableProperty] + private int _selectedItem; + + [ObservableProperty] + private int _move1; + + [ObservableProperty] + private int _move2; + + [ObservableProperty] + private int _move3; + + [ObservableProperty] + private int _move4; + + [ObservableProperty] + private string _ec = "00000000"; + + [ObservableProperty] + private string _tid = "00000"; + + [ObservableProperty] + private string _sid = "00000"; + + [ObservableProperty] + private string _nickname = string.Empty; + + [ObservableProperty] + private string _otName = string.Empty; + + [ObservableProperty] + private bool _isShiny; + + [ObservableProperty] + private bool _isNicknamed; + + [ObservableProperty] + private int _level; + + [ObservableProperty] + private string _entryText = string.Empty; + + [ObservableProperty] + private string _clearIndex = "000"; + + public SAVHallOfFame6ViewModel(SAV6 sav) : base(sav) + { + _sav = (SAV6)(_origin = sav).Clone(); + _fame = ((ISaveBlock6Main)_sav).HallOfFame; + + var filtered = GameInfo.FilteredSources; + SpeciesList = filtered.Species.ToList(); + ItemsList = filtered.Items.ToList(); + MovesList = filtered.Moves.ToList(); + + for (int i = 0; i < HallOfFame6.Entries; i++) + EntryNames.Add($"Entry {i}"); + + SelectedEntryIndex = 0; + } + + partial void OnSelectedEntryIndexChanged(int value) + { + if (value < 0) + return; + LoadEntry(value); + } + + partial void OnMemberIndexChanged(int value) + { + if (_loading || value < 1 || SelectedEntryIndex < 0) + return; + LoadMember(SelectedEntryIndex, value - 1); + } + + private void LoadEntry(int index) + { + _loading = true; + var span = _fame.GetEntry(index); + var vnd = new HallFame6Index(span[^4..]); + ClearIndex = vnd.ClearIndex.ToString("000"); + + var sb = new StringBuilder(); + sb.AppendLine($"Entry #{vnd.ClearIndex}"); + + if (!vnd.HasData) + { + sb.AppendLine("No records in this slot."); + EntryText = sb.ToString(); + MemberIndex = 1; + MaxMembers = 1; + _loading = false; + return; + } + + var year = vnd.Year + 2000; + var month = vnd.Month; + var day = vnd.Day; + sb.AppendLine($"Date: {year}/{month:00}/{day:00}"); + + int monCount = 0; + for (int i = 0; i < 6; i++) + { + var slice = span[(i * HallFame6Entity.SIZE)..]; + var entity = new HallFame6Entity(slice, _sav.Language); + if (entity.Species == 0) + continue; + monCount++; + var str = GameInfo.Strings; + sb.AppendLine($" {str.Species[entity.Species]} Lv.{entity.Level} - {str.Item[entity.HeldItem]}"); + } + + EntryText = sb.ToString(); + MaxMembers = monCount == 0 ? 1 : monCount; + MemberIndex = 1; + LoadMember(index, 0); + _loading = false; + } + + private void LoadMember(int entryIndex, int memberIdx) + { + _loading = true; + var slice = _fame.GetEntity(entryIndex, memberIdx); + var entity = new HallFame6Entity(slice, _sav.Language); + + SelectedSpecies = entity.Species; + SelectedItem = entity.HeldItem; + Move1 = entity.Move1; + Move2 = entity.Move2; + Move3 = entity.Move3; + Move4 = entity.Move4; + Ec = entity.EncryptionConstant.ToString("X8"); + Tid = entity.TID16.ToString("00000"); + Sid = entity.SID16.ToString("00000"); + Nickname = entity.Nickname; + OtName = entity.OriginalTrainerName; + IsShiny = entity.IsShiny; + IsNicknamed = entity.IsNicknamed; + Level = (int)entity.Level; + _loading = false; + } + + private void SaveMember() + { + if (SelectedEntryIndex < 0 || MemberIndex < 1) + return; + var slice = _fame.GetEntity(SelectedEntryIndex, MemberIndex - 1); + var entity = new HallFame6Entity(slice, _sav.Language) + { + Species = (ushort)SelectedSpecies, + HeldItem = (ushort)SelectedItem, + Move1 = (ushort)Move1, + Move2 = (ushort)Move2, + Move3 = (ushort)Move3, + Move4 = (ushort)Move4, + EncryptionConstant = Convert.ToUInt32(Ec, 16), + TID16 = ushort.TryParse(Tid, out var tid) ? tid : (ushort)0, + SID16 = ushort.TryParse(Sid, out var sid) ? sid : (ushort)0, + Nickname = Nickname, + OriginalTrainerName = OtName, + IsShiny = IsShiny, + IsNicknamed = IsNicknamed, + Level = (uint)Math.Clamp(Level, 0, 100), + }; + + var span = _fame.GetEntry(SelectedEntryIndex); + _ = new HallFame6Index(span[^4..]) + { + ClearIndex = ushort.TryParse(ClearIndex, out var ci) ? ci : (ushort)0, + HasData = true, + }; + } + + [RelayCommand] + private void DeleteEntry() + { + if (SelectedEntryIndex < 1) + return; + _fame.ClearEntry(SelectedEntryIndex); + LoadEntry(SelectedEntryIndex); + } + + [RelayCommand] + private void Save() + { + SaveMember(); + _origin.CopyChangesFrom(_sav); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVLink6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVLink6ViewModel.cs new file mode 100644 index 000000000..0f53c965c --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVLink6ViewModel.cs @@ -0,0 +1,163 @@ +using System.Collections.Generic; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// ViewModel for the Gen 6 Link event editor. +/// Edits link data including items, quantities, battle points, and pokemiles. +/// +public partial class SAVLink6ViewModel : SaveEditorViewModelBase +{ + private readonly SaveFile _origin; + private readonly ISaveBlock6Main _clone; + private readonly PL6 _gifts; + + public List ItemsList { get; } + + [ObservableProperty] + private string _linkSource = string.Empty; + + [ObservableProperty] + private bool _linkAvailable; + + [ObservableProperty] + private int _battlePoints; + + [ObservableProperty] + private int _pokemiles; + + [ObservableProperty] + private int _item1; + + [ObservableProperty] + private int _item2; + + [ObservableProperty] + private int _item3; + + [ObservableProperty] + private int _item4; + + [ObservableProperty] + private int _item5; + + [ObservableProperty] + private int _item6; + + [ObservableProperty] + private int _quantity1; + + [ObservableProperty] + private int _quantity2; + + [ObservableProperty] + private int _quantity3; + + [ObservableProperty] + private int _quantity4; + + [ObservableProperty] + private int _quantity5; + + [ObservableProperty] + private int _quantity6; + + [ObservableProperty] + private string _pkm1 = string.Empty; + + [ObservableProperty] + private string _pkm2 = string.Empty; + + [ObservableProperty] + private string _pkm3 = string.Empty; + + [ObservableProperty] + private string _pkm4 = string.Empty; + + [ObservableProperty] + private string _pkm5 = string.Empty; + + [ObservableProperty] + private string _pkm6 = string.Empty; + + public SAVLink6ViewModel(SaveFile sav) : base(sav) + { + _origin = sav; + var clone = sav.Clone(); + _clone = (ISaveBlock6Main)clone; + _gifts = _clone.Link.Gifts; + + ItemsList = GameInfo.FilteredSources.Items.ToList(); + + LoadLinkData(); + } + + private void LoadLinkData() + { + LinkSource = _gifts.Origin; + LinkAvailable = _gifts.Enabled; + + BattlePoints = _gifts.BattlePoints; + Pokemiles = _gifts.Pokemiles; + + Item1 = _gifts.Item1; + Item2 = _gifts.Item2; + Item3 = _gifts.Item3; + Item4 = _gifts.Item4; + Item5 = _gifts.Item5; + Item6 = _gifts.Item6; + + Quantity1 = _gifts.Quantity1; + Quantity2 = _gifts.Quantity2; + Quantity3 = _gifts.Quantity3; + Quantity4 = _gifts.Quantity4; + Quantity5 = _gifts.Quantity5; + Quantity6 = _gifts.Quantity6; + + Pkm1 = GetSpecies(_gifts.Entity1.Species); + Pkm2 = GetSpecies(_gifts.Entity2.Species); + Pkm3 = GetSpecies(_gifts.Entity3.Species); + Pkm4 = GetSpecies(_gifts.Entity4.Species); + Pkm5 = GetSpecies(_gifts.Entity5.Species); + Pkm6 = GetSpecies(_gifts.Entity6.Species); + } + + private static string GetSpecies(ushort species) + { + var arr = GameInfo.Strings.Species; + if (species < arr.Count) + return arr[species]; + return species.ToString(); + } + + [RelayCommand] + private void Save() + { + _gifts.Origin = LinkSource; + _gifts.Enabled = LinkAvailable; + _gifts.BattlePoints = (ushort)BattlePoints; + _gifts.Pokemiles = (ushort)Pokemiles; + + _gifts.Item1 = (ushort)Item1; + _gifts.Item2 = (ushort)Item2; + _gifts.Item3 = (ushort)Item3; + _gifts.Item4 = (ushort)Item4; + _gifts.Item5 = (ushort)Item5; + _gifts.Item6 = (ushort)Item6; + + _gifts.Quantity1 = (ushort)Quantity1; + _gifts.Quantity2 = (ushort)Quantity2; + _gifts.Quantity3 = (ushort)Quantity3; + _gifts.Quantity4 = (ushort)Quantity4; + _gifts.Quantity5 = (ushort)Quantity5; + _gifts.Quantity6 = (ushort)Quantity6; + + _clone.Link.RefreshChecksum(); + _origin.CopyChangesFrom((SaveFile)_clone); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVOPower6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVOPower6ViewModel.cs new file mode 100644 index 000000000..19c9338c4 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVOPower6ViewModel.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for an unlock flag item. +/// +public partial class OPowerUnlockModel : ObservableObject +{ + public string Name { get; } + public int Index { get; } + + [ObservableProperty] + private bool _isUnlocked; + + public OPowerUnlockModel(string name, int index, bool unlocked) + { + Name = name; + Index = index; + _isUnlocked = unlocked; + } +} + +/// +/// Model for a field/battle level row. +/// +public partial class OPowerLevelModel : ObservableObject +{ + public string Name { get; } + public int Index { get; } + + [ObservableProperty] + private byte _level1; + + [ObservableProperty] + private byte _level2; + + public OPowerLevelModel(string name, int index, byte level1, byte level2) + { + Name = name; + Index = index; + _level1 = level1; + _level2 = level2; + } +} + +/// +/// ViewModel for the Gen 6 O-Power editor. +/// Edits unlock states, field/battle levels, and points. +/// +public partial class SAVOPower6ViewModel : SaveEditorViewModelBase +{ + private readonly SaveFile _origin; + private readonly SaveFile _clone; + private readonly OPower6 _block; + + public ObservableCollection UnlockFlags { get; } = []; + public ObservableCollection FieldLevels { get; } = []; + public ObservableCollection BattleLevels { get; } = []; + + [ObservableProperty] + private byte _points; + + public SAVOPower6ViewModel(ISaveBlock6Main sav) : base((SaveFile)sav) + { + _origin = (SaveFile)sav; + _clone = _origin.Clone(); + _block = ((ISaveBlock6Main)_clone).OPower; + + LoadData(); + } + + private void LoadData() + { + UnlockFlags.Clear(); + FieldLevels.Clear(); + BattleLevels.Clear(); + + // Unlock flags - use enum names + var indexNames = Enum.GetNames(); + int indexCount = (int)OPower6Index.Count; + for (int i = 0; i < indexCount; i++) + { + var name = i < indexNames.Length ? indexNames[i] : $"Index {i}"; + bool unlocked = _block.GetState((OPower6Index)i) == OPowerFlagState.Unlocked; + UnlockFlags.Add(new OPowerUnlockModel(name, i, unlocked)); + } + + // Field levels + var fieldNames = Enum.GetNames(); + int fieldCount = (int)OPower6FieldType.Count; + for (int i = 0; i < fieldCount; i++) + { + var name = i < fieldNames.Length ? fieldNames[i] : $"Field {i}"; + FieldLevels.Add(new OPowerLevelModel(name, i, + _block.GetLevel1((OPower6FieldType)i), + _block.GetLevel2((OPower6FieldType)i))); + } + + // Battle levels + var battleNames = Enum.GetNames(); + int battleCount = (int)OPower6BattleType.Count; + for (int i = 0; i < battleCount; i++) + { + var name = i < battleNames.Length ? battleNames[i] : $"Battle {i}"; + BattleLevels.Add(new OPowerLevelModel(name, i, + _block.GetLevel1((OPower6BattleType)i), + _block.GetLevel2((OPower6BattleType)i))); + } + + Points = _block.Points; + } + + [RelayCommand] + private void GiveAll() + { + _block.UnlockAll(); + LoadData(); + } + + [RelayCommand] + private void ClearAll() + { + _block.ClearAll(); + LoadData(); + } + + [RelayCommand] + private void Save() + { + foreach (var flag in UnlockFlags) + _block.SetState((OPower6Index)flag.Index, flag.IsUnlocked ? OPowerFlagState.Unlocked : OPowerFlagState.Locked); + foreach (var field in FieldLevels) + { + _block.SetLevel1((OPower6FieldType)field.Index, field.Level1); + _block.SetLevel2((OPower6FieldType)field.Index, field.Level2); + } + foreach (var battle in BattleLevels) + { + _block.SetLevel1((OPower6BattleType)battle.Index, battle.Level1); + _block.SetLevel2((OPower6BattleType)battle.Index, battle.Level2); + } + _block.Points = Points; + _origin.CopyChangesFrom(_clone); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVPokeBlockORASViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVPokeBlockORASViewModel.cs new file mode 100644 index 000000000..2e57f0a78 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVPokeBlockORASViewModel.cs @@ -0,0 +1,74 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for a single PokeBlock entry in ORAS. +/// +public partial class PokeBlock6Model : ObservableObject +{ + public int Index { get; } + public string Label { get; } + + [ObservableProperty] + private uint _count; + + public PokeBlock6Model(int index, string label, uint count) + { + Index = index; + Label = label; + _count = count; + } +} + +/// +/// ViewModel for the Gen 6 ORAS PokeBlock editor. +/// Edits the 12 pokeblock counts. +/// +public partial class SAVPokeBlockORASViewModel : SaveEditorViewModelBase +{ + private readonly SAV6AO _origin; + private readonly SAV6AO _sav; + + public ObservableCollection Blocks { get; } = []; + + public SAVPokeBlockORASViewModel(SAV6AO sav) : base(sav) + { + _sav = (SAV6AO)(_origin = sav).Clone(); + + var contest = _sav.Contest; + var blockNames = GameInfo.Strings.pokeblocks; + for (int i = 0; i < Contest6.CountBlock; i++) + { + var label = (94 + i < blockNames.Length) ? blockNames[94 + i] : $"Block {i}"; + Blocks.Add(new PokeBlock6Model(i, label, contest.GetBlockCount(i))); + } + } + + [RelayCommand] + private void GiveAll() + { + foreach (var block in Blocks) + block.Count = 999; + } + + [RelayCommand] + private void ClearAll() + { + foreach (var block in Blocks) + block.Count = 0; + } + + [RelayCommand] + private void Save() + { + var contest = _sav.Contest; + foreach (var block in Blocks) + contest.SetBlockCount(block.Index, block.Count); + _origin.CopyChangesFrom(_sav); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVPokedexORASViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVPokedexORASViewModel.cs new file mode 100644 index 000000000..3a5ddc5ca --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVPokedexORASViewModel.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// ViewModel for the Gen 6 ORAS Pokedex editor. +/// Edits seen/caught/language flags and DexNav counts per species. +/// +public partial class SAVPokedexORASViewModel : SaveEditorViewModelBase +{ + private readonly SaveFile _origin; + private readonly SAV6AO _sav; + private readonly Zukan6AO _zukan; + private bool _editing; + private ushort _species = ushort.MaxValue; + + public ObservableCollection SpeciesNames { get; } = []; + + [ObservableProperty] + private int _selectedSpeciesIndex = -1; + + [ObservableProperty] + private bool _caught; + + [ObservableProperty] + private bool _seenMale; + + [ObservableProperty] + private bool _seenFemale; + + [ObservableProperty] + private bool _seenMaleShiny; + + [ObservableProperty] + private bool _seenFemaleShiny; + + [ObservableProperty] + private bool _displayedMale; + + [ObservableProperty] + private bool _displayedFemale; + + [ObservableProperty] + private bool _displayedMaleShiny; + + [ObservableProperty] + private bool _displayedFemaleShiny; + + [ObservableProperty] + private bool _langJPN; + + [ObservableProperty] + private bool _langENG; + + [ObservableProperty] + private bool _langFRE; + + [ObservableProperty] + private bool _langITA; + + [ObservableProperty] + private bool _langGER; + + [ObservableProperty] + private bool _langSPA; + + [ObservableProperty] + private bool _langKOR; + + [ObservableProperty] + private bool _nationalDexUnlocked; + + [ObservableProperty] + private bool _nationalDexActive; + + [ObservableProperty] + private string _spindaPid = "00000000"; + + [ObservableProperty] + private int _countSeen; + + [ObservableProperty] + private int _countObtained; + + public SAVPokedexORASViewModel(SAV6AO sav) : base(sav) + { + _sav = (SAV6AO)(_origin = sav).Clone(); + _zukan = _sav.Zukan; + + for (int i = 1; i <= _sav.MaxSpeciesID; i++) + SpeciesNames.Add($"{i:000} - {GameInfo.Strings.specieslist[i]}"); + + NationalDexUnlocked = _zukan.IsNationalDexUnlocked; + NationalDexActive = _zukan.IsNationalDexMode; + SpindaPid = _zukan.Spinda.ToString("X8"); + + SelectedSpeciesIndex = 0; + } + + partial void OnSelectedSpeciesIndexChanged(int value) + { + if (value < 0 || _editing) + return; + SaveEntry(); + _editing = true; + _species = (ushort)(value + 1); + LoadEntry(); + _editing = false; + } + + private void LoadEntry() + { + Caught = _zukan.GetCaught(_species); + SeenMale = _zukan.GetSeen(_species, 0); + SeenFemale = _zukan.GetSeen(_species, 1); + SeenMaleShiny = _zukan.GetSeen(_species, 2); + SeenFemaleShiny = _zukan.GetSeen(_species, 3); + DisplayedMale = _zukan.GetDisplayed(_species, 0); + DisplayedFemale = _zukan.GetDisplayed(_species, 1); + DisplayedMaleShiny = _zukan.GetDisplayed(_species, 2); + DisplayedFemaleShiny = _zukan.GetDisplayed(_species, 3); + LangJPN = _zukan.GetLanguageFlag(_species, 0); + LangENG = _zukan.GetLanguageFlag(_species, 1); + LangFRE = _zukan.GetLanguageFlag(_species, 2); + LangITA = _zukan.GetLanguageFlag(_species, 3); + LangGER = _zukan.GetLanguageFlag(_species, 4); + LangSPA = _zukan.GetLanguageFlag(_species, 5); + LangKOR = _zukan.GetLanguageFlag(_species, 6); + CountSeen = _zukan.GetCountSeen(_species); + CountObtained = _zukan.GetCountObtained(_species); + } + + private void SaveEntry() + { + if (_species is 0 or > 721) + return; + + _zukan.SetCaught(_species, Caught); + _zukan.SetSeen(_species, 0, SeenMale); + _zukan.SetSeen(_species, 1, SeenFemale); + _zukan.SetSeen(_species, 2, SeenMaleShiny); + _zukan.SetSeen(_species, 3, SeenFemaleShiny); + _zukan.SetDisplayed(_species, 0, DisplayedMale); + _zukan.SetDisplayed(_species, 1, DisplayedFemale); + _zukan.SetDisplayed(_species, 2, DisplayedMaleShiny); + _zukan.SetDisplayed(_species, 3, DisplayedFemaleShiny); + _zukan.SetLanguageFlag(_species, 0, LangJPN); + _zukan.SetLanguageFlag(_species, 1, LangENG); + _zukan.SetLanguageFlag(_species, 2, LangFRE); + _zukan.SetLanguageFlag(_species, 3, LangITA); + _zukan.SetLanguageFlag(_species, 4, LangGER); + _zukan.SetLanguageFlag(_species, 5, LangSPA); + _zukan.SetLanguageFlag(_species, 6, LangKOR); + _zukan.SetCountSeen(_species, (ushort)Math.Clamp(CountSeen, 0, ushort.MaxValue)); + _zukan.SetCountObtained(_species, (ushort)Math.Clamp(CountObtained, 0, ushort.MaxValue)); + } + + [RelayCommand] + private void SeenAll() + { + SaveEntry(); + _zukan.SeenAll(shinyToo: false); + LoadEntry(); + } + + [RelayCommand] + private void SeenNone() + { + SaveEntry(); + _zukan.SeenNone(); + LoadEntry(); + } + + [RelayCommand] + private void CaughtAll() + { + SaveEntry(); + var language = (LanguageID)_sav.Language; + _zukan.CaughtAll(language, allLanguages: false); + LoadEntry(); + } + + [RelayCommand] + private void CaughtNone() + { + SaveEntry(); + _zukan.CaughtNone(); + LoadEntry(); + } + + [RelayCommand] + private void Complete() + { + SaveEntry(); + var language = (LanguageID)_sav.Language; + _zukan.SeenAll(shinyToo: false); + _zukan.CaughtAll(language, allLanguages: false); + LoadEntry(); + } + + [RelayCommand] + private void MaxDexNav() + { + for (ushort i = 0; i < _sav.MaxSpeciesID; i++) + _zukan.SetCountSeen(i, 999); + LoadEntry(); + } + + [RelayCommand] + private void ResetDexNav() + { + for (ushort i = 0; i < _sav.MaxSpeciesID; i++) + _zukan.SetCountSeen(i, 0); + LoadEntry(); + } + + [RelayCommand] + private void Save() + { + SaveEntry(); + _zukan.IsNationalDexUnlocked = NationalDexUnlocked; + _zukan.IsNationalDexMode = NationalDexActive; + _zukan.Spinda = Convert.ToUInt32(SpindaPid, 16); + if (_species is not 0) + _zukan.InitialSpecies = _species; + _origin.CopyChangesFrom(_sav); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVPokedexXYViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVPokedexXYViewModel.cs new file mode 100644 index 000000000..1aae6bde5 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVPokedexXYViewModel.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// ViewModel for the Gen 6 XY Pokedex editor. +/// Edits seen/caught/language flags per species. +/// +public partial class SAVPokedexXYViewModel : SaveEditorViewModelBase +{ + private readonly SaveFile _origin; + private readonly SAV6XY _sav; + private readonly Zukan6XY _zukan; + private bool _editing; + private ushort _species = ushort.MaxValue; + + public ObservableCollection SpeciesNames { get; } = []; + + [ObservableProperty] + private int _selectedSpeciesIndex = -1; + + // Caught + [ObservableProperty] + private bool _caught; + + // Seen M/F/MS/FS + [ObservableProperty] + private bool _seenMale; + + [ObservableProperty] + private bool _seenFemale; + + [ObservableProperty] + private bool _seenMaleShiny; + + [ObservableProperty] + private bool _seenFemaleShiny; + + // Displayed M/F/MS/FS + [ObservableProperty] + private bool _displayedMale; + + [ObservableProperty] + private bool _displayedFemale; + + [ObservableProperty] + private bool _displayedMaleShiny; + + [ObservableProperty] + private bool _displayedFemaleShiny; + + // Language flags + [ObservableProperty] + private bool _langJPN; + + [ObservableProperty] + private bool _langENG; + + [ObservableProperty] + private bool _langFRE; + + [ObservableProperty] + private bool _langITA; + + [ObservableProperty] + private bool _langGER; + + [ObservableProperty] + private bool _langSPA; + + [ObservableProperty] + private bool _langKOR; + + [ObservableProperty] + private bool _nationalDexUnlocked; + + [ObservableProperty] + private bool _nationalDexActive; + + [ObservableProperty] + private string _spindaPid = "00000000"; + + public SAVPokedexXYViewModel(SAV6XY sav) : base(sav) + { + _sav = (SAV6XY)(_origin = sav).Clone(); + _zukan = _sav.Zukan; + + for (int i = 1; i <= _sav.MaxSpeciesID; i++) + SpeciesNames.Add($"{i:000} - {GameInfo.Strings.specieslist[i]}"); + + NationalDexUnlocked = _zukan.IsNationalDexUnlocked; + NationalDexActive = _zukan.IsNationalDexMode; + SpindaPid = _zukan.Spinda.ToString("X8"); + + SelectedSpeciesIndex = 0; + } + + partial void OnSelectedSpeciesIndexChanged(int value) + { + if (value < 0 || _editing) + return; + SaveEntry(); + _editing = true; + _species = (ushort)(value + 1); + LoadEntry(); + _editing = false; + } + + private void LoadEntry() + { + Caught = _zukan.GetCaught(_species); + SeenMale = _zukan.GetSeen(_species, 0); + SeenFemale = _zukan.GetSeen(_species, 1); + SeenMaleShiny = _zukan.GetSeen(_species, 2); + SeenFemaleShiny = _zukan.GetSeen(_species, 3); + DisplayedMale = _zukan.GetDisplayed(_species, 0); + DisplayedFemale = _zukan.GetDisplayed(_species, 1); + DisplayedMaleShiny = _zukan.GetDisplayed(_species, 2); + DisplayedFemaleShiny = _zukan.GetDisplayed(_species, 3); + LangJPN = _zukan.GetLanguageFlag(_species, 0); + LangENG = _zukan.GetLanguageFlag(_species, 1); + LangFRE = _zukan.GetLanguageFlag(_species, 2); + LangITA = _zukan.GetLanguageFlag(_species, 3); + LangGER = _zukan.GetLanguageFlag(_species, 4); + LangSPA = _zukan.GetLanguageFlag(_species, 5); + LangKOR = _zukan.GetLanguageFlag(_species, 6); + } + + private void SaveEntry() + { + if (_species is 0 or > 721) + return; + + _zukan.SetCaught(_species, Caught); + _zukan.SetSeen(_species, 0, SeenMale); + _zukan.SetSeen(_species, 1, SeenFemale); + _zukan.SetSeen(_species, 2, SeenMaleShiny); + _zukan.SetSeen(_species, 3, SeenFemaleShiny); + _zukan.SetDisplayed(_species, 0, DisplayedMale); + _zukan.SetDisplayed(_species, 1, DisplayedFemale); + _zukan.SetDisplayed(_species, 2, DisplayedMaleShiny); + _zukan.SetDisplayed(_species, 3, DisplayedFemaleShiny); + _zukan.SetLanguageFlag(_species, 0, LangJPN); + _zukan.SetLanguageFlag(_species, 1, LangENG); + _zukan.SetLanguageFlag(_species, 2, LangFRE); + _zukan.SetLanguageFlag(_species, 3, LangITA); + _zukan.SetLanguageFlag(_species, 4, LangGER); + _zukan.SetLanguageFlag(_species, 5, LangSPA); + _zukan.SetLanguageFlag(_species, 6, LangKOR); + } + + [RelayCommand] + private void SeenAll() + { + SaveEntry(); + _zukan.SeenAll(shinyToo: false); + LoadEntry(); + } + + [RelayCommand] + private void SeenNone() + { + SaveEntry(); + _zukan.SeenNone(); + LoadEntry(); + } + + [RelayCommand] + private void CaughtAll() + { + SaveEntry(); + var language = (LanguageID)_sav.Language; + _zukan.CaughtAll(language, allLanguages: false); + LoadEntry(); + } + + [RelayCommand] + private void CaughtNone() + { + SaveEntry(); + _zukan.CaughtNone(); + LoadEntry(); + } + + [RelayCommand] + private void Complete() + { + SaveEntry(); + var language = (LanguageID)_sav.Language; + _zukan.SeenAll(shinyToo: false); + _zukan.CaughtAll(language, allLanguages: false); + LoadEntry(); + } + + [RelayCommand] + private void Save() + { + SaveEntry(); + _zukan.IsNationalDexUnlocked = NationalDexUnlocked; + _zukan.IsNationalDexMode = NationalDexActive; + _zukan.Spinda = Convert.ToUInt32(SpindaPid, 16); + if (_species is not 0) + _zukan.InitialSpecies = _species; + _origin.CopyChangesFrom(_sav); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVPokepuff6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVPokepuff6ViewModel.cs new file mode 100644 index 000000000..f39d3b186 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVPokepuff6ViewModel.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for a single Pokepuff slot. +/// +public partial class PokepuffSlotModel : ObservableObject +{ + public int Index { get; } + + [ObservableProperty] + private int _selectedPuffIndex; + + public string DisplayName => $"Slot {Index + 1}"; + + public PokepuffSlotModel(int index, int puffIndex) + { + Index = index; + _selectedPuffIndex = puffIndex; + } +} + +/// +/// ViewModel for the Gen 6 Pokepuff collection editor. +/// +public partial class SAVPokepuff6ViewModel : SaveEditorViewModelBase +{ + private readonly ISaveBlock6Main _sav; + + public ObservableCollection Slots { get; } = []; + public List PuffNames { get; } + + public SAVPokepuff6ViewModel(ISaveBlock6Main sav) : base((SaveFile)sav) + { + _sav = sav; + PuffNames = [.. GameInfo.Strings.puffs]; + + LoadPuffs(); + } + + private void LoadPuffs() + { + Slots.Clear(); + var puffs = _sav.Puff.GetPuffs(); + for (int i = 0; i < puffs.Length; i++) + { + int puffVal = puffs[i]; + if (puffVal >= PuffNames.Count) + puffVal = 0; + Slots.Add(new PokepuffSlotModel(i, puffVal)); + } + } + + [RelayCommand] + private void GiveAll() + { + _sav.Puff.MaxCheat(false); + LoadPuffs(); + } + + [RelayCommand] + private void GiveBest() + { + _sav.Puff.MaxCheat(true); + LoadPuffs(); + } + + [RelayCommand] + private void ClearAll() + { + _sav.Puff.Reset(); + LoadPuffs(); + } + + [RelayCommand] + private void Sort() + { + SavePuffs(); + _sav.Puff.Sort(false); + LoadPuffs(); + } + + [RelayCommand] + private void SortReverse() + { + SavePuffs(); + _sav.Puff.Sort(true); + LoadPuffs(); + } + + private void SavePuffs() + { + var puffs = new byte[Slots.Count]; + for (int i = 0; i < Slots.Count; i++) + puffs[i] = (byte)Slots[i].SelectedPuffIndex; + _sav.Puff.SetPuffs(puffs); + _sav.Puff.PuffCount = puffs.Length; + } + + [RelayCommand] + private void Save() + { + SavePuffs(); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVRoamer6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVRoamer6ViewModel.cs new file mode 100644 index 000000000..90c0da771 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVRoamer6ViewModel.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// ViewModel for the Gen 6 Roaming Pokemon editor. +/// Edits species, roam state, and times encountered. +/// +public partial class SAVRoamer6ViewModel : SaveEditorViewModelBase +{ + private readonly SAV6XY _origin; + private readonly SAV6XY _sav; + private readonly Roamer6 _roamer; + + public List SpeciesChoices { get; } + public List RoamStateChoices { get; } = ["Inactive", "Roaming", "Stationary", "Defeated", "Captured"]; + + [ObservableProperty] + private int _selectedSpeciesIndex; + + [ObservableProperty] + private int _selectedRoamStateIndex; + + [ObservableProperty] + private uint _timesEncountered; + + public SAVRoamer6ViewModel(SAV6XY sav) : base(sav) + { + _sav = (SAV6XY)(_origin = sav).Clone(); + _roamer = _sav.Encount.Roamer; + + var species = GameInfo.Strings.specieslist; + SpeciesChoices = + [ + species[(int)Species.Articuno], + species[(int)Species.Zapdos], + species[(int)Species.Moltres], + ]; + + SelectedSpeciesIndex = GetInitialIndex(); + TimesEncountered = _roamer.TimesEncountered; + SelectedRoamStateIndex = (int)_roamer.RoamStatus; + } + + private int GetInitialIndex() + { + const int speciesOffset = 144; + const int starterChoiceIndex = 48; + if (_roamer.Species != 0) + return _roamer.Species - speciesOffset; + return _sav.EventWork.GetWork(starterChoiceIndex); + } + + [RelayCommand] + private void Save() + { + const int speciesOffset = 144; + _roamer.Species = (ushort)(speciesOffset + SelectedSpeciesIndex); + _roamer.TimesEncountered = TimesEncountered; + _roamer.RoamStatus = (Roamer6State)SelectedRoamStateIndex; + _origin.CopyChangesFrom(_sav); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVSecretBase6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVSecretBase6ViewModel.cs new file mode 100644 index 000000000..5a14b13fb --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVSecretBase6ViewModel.cs @@ -0,0 +1,182 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// ViewModel for the Gen 6 ORAS Secret Base editor. +/// Edits base list, decorations, trainer info. +/// +public partial class SAVSecretBase6ViewModel : SaveEditorViewModelBase +{ + private readonly SaveFile _origin; + private readonly SAV6AO _sav; + private bool _loading; + + public ObservableCollection BaseNames { get; } = []; + + [ObservableProperty] + private int _selectedBaseIndex = -1; + + // Base properties (displayed via PropertyGrid-like fields) + [ObservableProperty] + private string _trainerName = string.Empty; + + [ObservableProperty] + private int _baseLocation; + + [ObservableProperty] + private string _teamName = string.Empty; + + [ObservableProperty] + private string _teamSlogan = string.Empty; + + // Decoration placement + [ObservableProperty] + private int _placementIndex; + + [ObservableProperty] + private int _placementGood; + + [ObservableProperty] + private int _placementX; + + [ObservableProperty] + private int _placementY; + + [ObservableProperty] + private int _placementRotation; + + [ObservableProperty] + private uint _capturedRecord; + + private SecretBase6? _currentBase; + private int _currentPlacementIndex = -1; + + public SAVSecretBase6ViewModel(SAV6AO sav) : base(sav) + { + _sav = (SAV6AO)(_origin = sav).Clone(); + + CapturedRecord = (uint)_sav.Records.GetRecord(080); + ReloadBaseList(); + + if (BaseNames.Count > 0) + SelectedBaseIndex = 0; + } + + private void ReloadBaseList() + { + _loading = true; + BaseNames.Clear(); + var block = _sav.SecretBase; + var self = block.GetSecretBaseSelf(); + BaseNames.Add($"* {self.TrainerName}"); + for (int i = 0; i < SecretBase6Block.OtherSecretBaseCount; i++) + { + var other = block.GetSecretBaseOther(i); + string name = other.TrainerName; + if (string.IsNullOrWhiteSpace(name)) + name = "Empty"; + BaseNames.Add($"{i + 1:00} {name}"); + } + _loading = false; + } + + partial void OnSelectedBaseIndexChanged(int value) + { + if (value < 0 || _loading) + return; + SaveCurrentBase(); + LoadBase(value); + } + + partial void OnPlacementIndexChanged(int value) + { + if (_currentBase == null || _loading) + return; + SavePlacement(); + LoadPlacement(value); + } + + private void LoadBase(int index) + { + _loading = true; + _currentPlacementIndex = -1; + _currentBase = index == 0 + ? _sav.SecretBase.GetSecretBaseSelf() + : _sav.SecretBase.GetSecretBaseOther(index - 1); + + TrainerName = _currentBase.TrainerName; + BaseLocation = _currentBase.BaseLocation; + TeamName = _currentBase.TeamName; + TeamSlogan = _currentBase.TeamSlogan; + + PlacementIndex = 0; + LoadPlacement(0); + _loading = false; + } + + private void LoadPlacement(int index) + { + if (_currentBase == null || index < 0 || index >= SecretBase6.COUNT_GOODS) + return; + _currentPlacementIndex = index; + var p = _currentBase.GetPlacement(index); + PlacementGood = p.Good; + PlacementX = p.X; + PlacementY = p.Y; + PlacementRotation = p.Rotation; + } + + private void SavePlacement() + { + if (_currentBase == null || _currentPlacementIndex < 0) + return; + var p = _currentBase.GetPlacement(_currentPlacementIndex); + p.Good = (ushort)PlacementGood; + p.X = (ushort)PlacementX; + p.Y = (ushort)PlacementY; + p.Rotation = (byte)PlacementRotation; + } + + private void SaveCurrentBase() + { + if (_currentBase == null) + return; + SavePlacement(); + _currentBase.TrainerName = TrainerName; + _currentBase.BaseLocation = BaseLocation; + _currentBase.TeamName = TeamName; + _currentBase.TeamSlogan = TeamSlogan; + } + + [RelayCommand] + private void GiveAllDecor() + { + _sav.SecretBase.GiveAllGoods(); + } + + [RelayCommand] + private void DeleteBase() + { + if (SelectedBaseIndex < 1) + return; + int index = SelectedBaseIndex - 1; + _sav.SecretBase.DeleteOther(index); + _currentBase = null; + _currentPlacementIndex = -1; + ReloadBaseList(); + SelectedBaseIndex = index + 1; + } + + [RelayCommand] + private void Save() + { + SaveCurrentBase(); + _sav.Records.SetRecord(080, (int)CapturedRecord); + _origin.CopyChangesFrom(_sav); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVSuperTrain6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVSuperTrain6ViewModel.cs new file mode 100644 index 000000000..b3b422c89 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVSuperTrain6ViewModel.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for a training bag slot. +/// +public partial class TrainingBagSlotModel : ObservableObject +{ + public int Index { get; } + + [ObservableProperty] + private int _selectedBagIndex; + + public string DisplayName => $"Slot {Index + 1}"; + + public TrainingBagSlotModel(int index, int bagIndex) + { + Index = index; + _selectedBagIndex = bagIndex; + } +} + +/// +/// ViewModel for the Gen 6 Super Training editor. +/// Edits training bags and regimen records. +/// +public partial class SAVSuperTrain6ViewModel : SaveEditorViewModelBase +{ + private readonly SaveFile _origin; + private readonly SAV6 _sav; + private readonly SuperTrainBlock _stb; + private bool _loading; + + public List StageNames { get; } = []; + public List BagNames { get; } + public List SpeciesList { get; } + public ObservableCollection BagSlots { get; } = []; + + [ObservableProperty] + private int _selectedStageIndex = -1; + + // Record holder 1 + [ObservableProperty] + private int _species1; + + [ObservableProperty] + private string _gender1 = "0"; + + [ObservableProperty] + private string _form1 = "0"; + + [ObservableProperty] + private string _time1 = "0"; + + // Record holder 2 + [ObservableProperty] + private int _species2; + + [ObservableProperty] + private string _gender2 = "0"; + + [ObservableProperty] + private string _form2 = "0"; + + [ObservableProperty] + private string _time2 = "0"; + + public SAVSuperTrain6ViewModel(SAV6 sav) : base(sav) + { + _sav = (SAV6)(_origin = sav).Clone(); + _stb = ((ISaveBlock6Main)_sav).SuperTrain; + + var bagNames = GameInfo.Strings.trainingbags.ToList(); + if (bagNames.Count > 0) + bagNames[0] = "---"; + BagNames = bagNames; + + SpeciesList = GameInfo.FilteredSources.Species.ToList(); + + var stages = GameInfo.Strings.trainingstage; + for (int i = 0; i < 32; i++) + StageNames.Add($"{i + 1:00} - {stages[i]}"); + + // Load bags + for (int i = 0; i < 12; i++) + { + int bagId = _stb.GetBag(i); + BagSlots.Add(new TrainingBagSlotModel(i, bagId)); + } + + SelectedStageIndex = 0; + } + + partial void OnSelectedStageIndexChanged(int value) + { + if (value < 0) + return; + LoadStageRecord(value); + } + + private void LoadStageRecord(int index) + { + _loading = true; + var h1 = _stb.GetHolder1(index); + var h2 = _stb.GetHolder2(index); + Species1 = h1.Species; + Gender1 = h1.Gender.ToString(); + Form1 = h1.Form.ToString(); + Species2 = h2.Species; + Gender2 = h2.Gender.ToString(); + Form2 = h2.Form.ToString(); + Time1 = _stb.GetTime1(index).ToString(CultureInfo.InvariantCulture); + Time2 = _stb.GetTime2(index).ToString(CultureInfo.InvariantCulture); + _loading = false; + } + + private void SaveStageRecord(int index) + { + if (index < 0) + return; + var h1 = _stb.GetHolder1(index); + h1.Species = (ushort)Species1; + if (byte.TryParse(Gender1, out var g1)) h1.Gender = g1; + if (byte.TryParse(Form1, out var f1)) h1.Form = f1; + var h2 = _stb.GetHolder2(index); + h2.Species = (ushort)Species2; + if (byte.TryParse(Gender2, out var g2)) h2.Gender = g2; + if (byte.TryParse(Form2, out var f2)) h2.Form = f2; + if (float.TryParse(Time1, out var t1)) _stb.SetTime1(index, t1); + if (float.TryParse(Time2, out var t2)) _stb.SetTime2(index, t2); + } + + [RelayCommand] + private void Save() + { + SaveStageRecord(SelectedStageIndex); + + // Save bags + int emptySlots = 0; + for (int i = 0; i < 12; i++) + { + int bagIndex = BagSlots[i].SelectedBagIndex; + if (bagIndex <= 0) + { + emptySlots++; + continue; + } + _stb.SetBag(i - emptySlots, (byte)bagIndex); + } + + _origin.CopyChangesFrom(_sav); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVTrainer6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVTrainer6ViewModel.cs new file mode 100644 index 000000000..8f1cdbede --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVTrainer6ViewModel.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// ViewModel for the Gen 6 trainer editor. +/// Edits OT name, TID/SID, money, play time, badges, BP, pokemiles, maison records, etc. +/// +public partial class SAVTrainer6ViewModel : SaveEditorViewModelBase +{ + private readonly SaveFile _origin; + private readonly SAV6 _sav; + + public string[] GenderChoices { get; } = ["Male", "Female"]; + + [ObservableProperty] + private string _otName = string.Empty; + + [ObservableProperty] + private string _tid = "00000"; + + [ObservableProperty] + private string _sid = "00000"; + + [ObservableProperty] + private string _money = "0"; + + [ObservableProperty] + private int _gender; + + [ObservableProperty] + private int _playedHours; + + [ObservableProperty] + private int _playedMinutes; + + [ObservableProperty] + private int _playedSeconds; + + [ObservableProperty] + private string _bp = "0"; + + [ObservableProperty] + private string _pokemiles = "0"; + + [ObservableProperty] + private string _style = "0"; + + [ObservableProperty] + private bool _megaUnlocked; + + [ObservableProperty] + private bool _megaRayquazaUnlocked; + + [ObservableProperty] + private bool _showStyle; + + [ObservableProperty] + private bool _showMegaRayquaza; + + // Badges + [ObservableProperty] + private bool _badge1; + + [ObservableProperty] + private bool _badge2; + + [ObservableProperty] + private bool _badge3; + + [ObservableProperty] + private bool _badge4; + + [ObservableProperty] + private bool _badge5; + + [ObservableProperty] + private bool _badge6; + + [ObservableProperty] + private bool _badge7; + + [ObservableProperty] + private bool _badge8; + + // Sayings + [ObservableProperty] + private string _saying1 = string.Empty; + + [ObservableProperty] + private string _saying2 = string.Empty; + + [ObservableProperty] + private string _saying3 = string.Empty; + + [ObservableProperty] + private string _saying4 = string.Empty; + + [ObservableProperty] + private string _saying5 = string.Empty; + + public SAVTrainer6ViewModel(SAV6 sav) : base(sav) + { + _sav = (SAV6)(_origin = sav).Clone(); + + ShowStyle = _sav is SAV6XY; + ShowMegaRayquaza = _sav is SAV6AO; + + LoadData(); + } + + private void LoadData() + { + OtName = _sav.OT; + Tid = _sav.TID16.ToString("00000"); + Sid = _sav.SID16.ToString("00000"); + Money = _sav.Money.ToString(); + Gender = _sav.Gender; + + PlayedHours = _sav.PlayedHours; + PlayedMinutes = _sav.PlayedMinutes; + PlayedSeconds = _sav.PlayedSeconds; + + Bp = _sav.BP.ToString(); + Pokemiles = _sav.GetRecord(63).ToString(); + + var sit = _sav.Situation; + Style = sit.Style.ToString(); + + int badgeval = _sav.Badges; + Badge1 = (badgeval & (1 << 0)) != 0; + Badge2 = (badgeval & (1 << 1)) != 0; + Badge3 = (badgeval & (1 << 2)) != 0; + Badge4 = (badgeval & (1 << 3)) != 0; + Badge5 = (badgeval & (1 << 4)) != 0; + Badge6 = (badgeval & (1 << 5)) != 0; + Badge7 = (badgeval & (1 << 6)) != 0; + Badge8 = (badgeval & (1 << 7)) != 0; + + var status = _sav.Status; + Saying1 = status.Saying1; + Saying2 = status.Saying2; + Saying3 = status.Saying3; + Saying4 = status.Saying4; + Saying5 = status.Saying5; + + MegaUnlocked = status.IsMegaEvolutionUnlocked; + MegaRayquazaUnlocked = status.IsMegaRayquazaUnlocked; + } + + [RelayCommand] + private void MaxCash() + { + Money = "9999999"; + } + + [RelayCommand] + private void Save() + { + _sav.OT = OtName; + _sav.TID16 = (ushort)(uint.TryParse(Tid, out var tid) ? tid : 0u); + _sav.SID16 = (ushort)(uint.TryParse(Sid, out var sid) ? sid : 0u); + _sav.Money = uint.TryParse(Money, out var money) ? money : 0u; + _sav.Gender = (byte)Gender; + _sav.Overworld.ResetPlayerModel(); + + _sav.PlayedHours = (ushort)PlayedHours; + _sav.PlayedMinutes = (ushort)(PlayedMinutes % 60); + _sav.PlayedSeconds = (ushort)(PlayedSeconds % 60); + + _sav.BP = ushort.TryParse(Bp, out var bp) ? bp : (ushort)0; + _sav.SetRecord(63, int.TryParse(Pokemiles, out var pm1) ? pm1 : 0); + _sav.SetRecord(64, int.TryParse(Pokemiles, out var pm2) ? pm2 : 0); + + var sit = _sav.Situation; + if (byte.TryParse(Style, out var style)) + sit.Style = style; + + int badgeval = 0; + if (Badge1) badgeval |= 1 << 0; + if (Badge2) badgeval |= 1 << 1; + if (Badge3) badgeval |= 1 << 2; + if (Badge4) badgeval |= 1 << 3; + if (Badge5) badgeval |= 1 << 4; + if (Badge6) badgeval |= 1 << 5; + if (Badge7) badgeval |= 1 << 6; + if (Badge8) badgeval |= 1 << 7; + _sav.Badges = badgeval; + + var status = _sav.Status; + status.Saying1 = Saying1; + status.Saying2 = Saying2; + status.Saying3 = Saying3; + status.Saying4 = Saying4; + status.Saying5 = Saying5; + + status.IsMegaEvolutionUnlocked = MegaUnlocked; + status.IsMegaRayquazaUnlocked = MegaRayquazaUnlocked; + + _origin.CopyChangesFrom(_sav); + Modified = true; + } +} diff --git a/PKHeX.Avalonia/ViewModels/Subforms/UnityTower5ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/UnityTower5ViewModel.cs new file mode 100644 index 000000000..d27d92302 --- /dev/null +++ b/PKHeX.Avalonia/ViewModels/Subforms/UnityTower5ViewModel.cs @@ -0,0 +1,166 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PKHeX.Core; + +namespace PKHeX.Avalonia.ViewModels.Subforms; + +/// +/// Model for a Geonet location row (Gen 5). +/// +public partial class GeonetEntry5Model : ObservableObject +{ + public int Country { get; } + public string CountryName { get; } + public int Subregion { get; } + public string SubregionName { get; } + + [ObservableProperty] + private int _point; + + public GeonetEntry5Model(int country, string countryName, int subregion, string subregionName, int point) + { + Country = country; + CountryName = countryName; + Subregion = subregion; + SubregionName = subregionName; + _point = point; + } +} + +/// +/// Model for a Unity Tower floor entry. +/// +public partial class UnityTowerFloorModel : ObservableObject +{ + public int Country { get; } + public string CountryName { get; } + + [ObservableProperty] + private bool _unlocked; + + public UnityTowerFloorModel(int country, string countryName, bool unlocked) + { + Country = country; + CountryName = countryName; + _unlocked = unlocked; + } +} + +/// +/// ViewModel for the Unity Tower / Geonet editor (Gen 5). +/// Edits Geonet globe data and Unity Tower floor unlock status. +/// +public partial class UnityTower5ViewModel : SaveEditorViewModelBase +{ + private readonly SAV5 SAV5; + private readonly UnityTower5 Tower; + + [ObservableProperty] private bool _globalFlag; + [ObservableProperty] private bool _unityTowerFlag; + + public ObservableCollection GeonetEntries { get; } = []; + public ObservableCollection FloorEntries { get; } = []; + + public string[] PointNames { get; } = ["None", "Blue", "Yellow", "Red"]; + + public UnityTower5ViewModel(SAV5 sav) : base(sav) + { + SAV5 = sav; + Tower = sav.UnityTower; + + _globalFlag = Tower.GlobalFlag; + _unityTowerFlag = Tower.UnityTowerFlag; + + LoadGeonet(); + LoadFloors(); + } + + private void LoadGeonet() + { + GeonetEntries.Clear(); + for (int i = 1; i <= UnityTower5.CountryCount; i++) + { + var country = i; + var countryName = $"Country {country}"; + var subregionCount = UnityTower5.GetSubregionCount((byte)country); + + if (subregionCount == 0) + { + var point = (int)Tower.GetCountrySubregion((byte)country, 0); + GeonetEntries.Add(new GeonetEntry5Model(country, countryName, 0, "Default", point)); + } + else + { + for (int j = 1; j <= subregionCount; j++) + { + var point = (int)Tower.GetCountrySubregion((byte)country, (byte)j); + GeonetEntries.Add(new GeonetEntry5Model(country, countryName, j, $"Region {j}", point)); + } + } + } + } + + private void LoadFloors() + { + FloorEntries.Clear(); + for (int i = 1; i <= UnityTower5.CountryCount; i++) + { + var countryName = $"Country {i}"; + var unlocked = Tower.GetUnityTowerFloor((byte)i); + FloorEntries.Add(new UnityTowerFloorModel(i, countryName, unlocked)); + } + } + + [RelayCommand] + private void SetAllLocations() + { + Tower.SetAll(); + GlobalFlag = Tower.GlobalFlag; + UnityTowerFlag = Tower.UnityTowerFlag; + LoadGeonet(); + LoadFloors(); + } + + [RelayCommand] + private void SetAllLegalLocations() + { + Tower.SetAllLegal(); + GlobalFlag = Tower.GlobalFlag; + UnityTowerFlag = Tower.UnityTowerFlag; + LoadGeonet(); + LoadFloors(); + } + + [RelayCommand] + private void ClearLocations() + { + Tower.ClearAll(); + GlobalFlag = Tower.GlobalFlag; + UnityTowerFlag = Tower.UnityTowerFlag; + LoadGeonet(); + LoadFloors(); + } + + [RelayCommand] + private void Save() + { + Tower.ClearAll(); + + foreach (var entry in GeonetEntries) + { + if (entry.Country > 0) + Tower.SetCountrySubregion((byte)entry.Country, (byte)entry.Subregion, (GeonetPoint)entry.Point); + } + + foreach (var floor in FloorEntries) + Tower.SetUnityTowerFloor((byte)floor.Country, floor.Unlocked); + + Tower.SetSAVCountry(); + Tower.GlobalFlag = GlobalFlag; + Tower.UnityTowerFlag = UnityTowerFlag; + + SAV.State.Edited = true; + Modified = true; + } +} diff --git a/PKHeX.Avalonia/Views/Subforms/CGearImage5View.axaml b/PKHeX.Avalonia/Views/Subforms/CGearImage5View.axaml new file mode 100644 index 000000000..7677c0cd2 --- /dev/null +++ b/PKHeX.Avalonia/Views/Subforms/CGearImage5View.axaml @@ -0,0 +1,34 @@ + + + + + +