Null safety fixes + Plus Records and Medals buttons

Null safety (prevents NullReferenceException crashes):
- RefreshBox: null check on GetBoxSlotAtIndex result
- RefreshParty: null check on GetPartySlotAtIndex result
- PushUndo: guard existing slot before DecryptedBoxData access
- RefreshExtraSlots: null check on slotInfo.Read result
- ExportBoxShowdown: null check in LINQ Where clause
- VerifyAllPkm: null/empty species guard

Missing controls added:
- Plus Records button in Moves tab (IPlusRecord, Gen 8+)
- Medals button in Cosmetic tab (ISuperTrainRegimen, Gen 6)
This commit is contained in:
montanon 2026-03-16 16:49:43 -03:00
parent a8b60cf4ab
commit 2e7fcb24b3
4 changed files with 63 additions and 9 deletions

View File

@ -811,7 +811,7 @@ private async Task ExportBoxShowdownAsync()
try
{
var box = SaveFile.GetBoxData(SavEditor.CurrentBox);
var text = string.Join("\n\n", box.Where(p => p.Species > 0).Select(ShowdownParsing.GetShowdownText));
var text = string.Join("\n\n", box.Where(p => p is not null && p.Species > 0).Select(ShowdownParsing.GetShowdownText));
if (string.IsNullOrWhiteSpace(text))
{
StatusMessage = "No Pokemon in box to export.";

View File

@ -338,9 +338,13 @@ public string NatureTooltip
[ObservableProperty] private ComboItem? _selectedAlphaMove;
[ObservableProperty] private bool _hasAlphaMove;
// Move Shop / Tech Record visibility
// Move Shop / Tech Record / Plus Record visibility
[ObservableProperty] private bool _hasMoveShop;
[ObservableProperty] private bool _hasTechRecords;
[ObservableProperty] private bool _hasPlusRecord;
// Super Training (Medals) visibility — Gen 6
[ObservableProperty] private bool _hasSuperTraining;
// Gen-specific: Catch Rate (Gen 1)
[ObservableProperty] private int _catchRate;
@ -1478,6 +1482,12 @@ public void PopulateFields(PKM pk)
// Tech Records (Gen 8+)
HasTechRecords = pk is ITechRecord;
// Plus Records (Gen 9a - Z:A)
HasPlusRecord = pk is IPlusRecord;
// Super Training / Medals (Gen 6)
HasSuperTraining = pk is ISuperTrainRegimen;
// Origin Mark indicator
var gen = pk.Generation;
HasOriginMark = gen >= 3;
@ -1896,6 +1906,39 @@ private async Task OpenTechRecords()
catch (Exception ex) { LegalityReport = $"Tech Record error: {ex.Message}"; }
}
[RelayCommand]
private async Task OpenPlusRecord()
{
if (Entity is not IPlusRecord plus) return;
if (Entity.PersonalInfo is not IPermitPlus permit) return;
try
{
PreparePKM();
var vm = new PlusRecordEditorViewModel(plus, permit, Entity);
var view = new PlusRecordEditorView { DataContext = vm };
var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
if (mainWindow != null)
await view.ShowDialog(mainWindow);
}
catch (Exception ex) { LegalityReport = $"Plus Record error: {ex.Message}"; }
}
[RelayCommand]
private async Task OpenMedals()
{
if (Entity is not ISuperTrainRegimen st) return;
try
{
PreparePKM();
var vm = new SuperTrainingEditorViewModel(st);
var view = new SuperTrainingEditorView { DataContext = vm };
var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
if (mainWindow != null)
await view.ShowDialog(mainWindow);
}
catch (Exception ex) { LegalityReport = $"Super Training error: {ex.Message}"; }
}
// --- Ribbons / Memories commands ---
[RelayCommand]

View File

@ -287,6 +287,7 @@ private void RefreshBox()
for (int i = 0; i < slotCount && i < BoxSlots.Count; i++)
{
var pk = _sav.GetBoxSlotAtIndex(CurrentBox, i);
if (pk is null) continue;
BoxSlots[i].Entity = pk;
if (pk.Species == 0)
{
@ -310,6 +311,7 @@ private void RefreshParty()
for (int i = 0; i < _sav.PartyCount && i < PartySlots.Count; i++)
{
var pk = _sav.GetPartySlotAtIndex(i);
if (pk is null) continue;
PartySlots[i].Entity = pk;
if (pk.Species == 0)
{
@ -388,7 +390,8 @@ private void SetSlot(SlotModel? slot)
if (boxIndex >= 0)
{
var existing = _sav.GetBoxSlotAtIndex(CurrentBox, boxIndex);
PushUndo(CurrentBox, boxIndex, existing, isParty: false);
if (existing is not null)
PushUndo(CurrentBox, boxIndex, existing, isParty: false);
_sav.SetBoxSlotAtIndex(pk, CurrentBox, boxIndex);
RefreshBox();
return;
@ -398,7 +401,8 @@ private void SetSlot(SlotModel? slot)
if (partyIndex >= 0)
{
var existing = _sav.GetPartySlotAtIndex(partyIndex);
PushUndo(0, partyIndex, existing, isParty: true);
if (existing is not null)
PushUndo(0, partyIndex, existing, isParty: true);
_sav.SetPartySlotAtIndex(pk, partyIndex);
RefreshParty();
}
@ -426,7 +430,8 @@ private void DeleteSlot(SlotModel? slot)
if (boxIndex >= 0)
{
var existing = _sav.GetBoxSlotAtIndex(CurrentBox, boxIndex);
PushUndo(CurrentBox, boxIndex, existing, isParty: false);
if (existing is not null)
PushUndo(CurrentBox, boxIndex, existing, isParty: false);
_sav.SetBoxSlotAtIndex(_sav.BlankPKM, CurrentBox, boxIndex);
RefreshBox();
return;
@ -436,7 +441,8 @@ private void DeleteSlot(SlotModel? slot)
if (partyIndex >= 0)
{
var existing = _sav.GetPartySlotAtIndex(partyIndex);
PushUndo(0, partyIndex, existing, isParty: true);
if (existing is not null)
PushUndo(0, partyIndex, existing, isParty: true);
_sav.DeletePartySlot(partyIndex);
RefreshParty();
}
@ -524,6 +530,7 @@ private void RefreshExtraSlots(SaveFile sav)
foreach (var slotInfo in extras)
{
var pk = slotInfo.Read(sav);
if (pk is null) continue;
var model = new SlotModel
{
Slot = slotInfo.Slot,
@ -675,7 +682,7 @@ private void VerifyAllPkm()
for (int slot = 0; slot < _sav.BoxSlotCount; slot++)
{
var pk = _sav.GetBoxSlotAtIndex(box, slot);
if (pk.Species == 0) continue;
if (pk is null || pk.Species == 0) continue;
total++;
var la = new LegalityAnalysis(pk);
if (la.Valid) legal++; else illegal++;

View File

@ -523,12 +523,14 @@
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="200" HorizontalAlignment="Left" />
</StackPanel>
<!-- Move Shop / Tech Records buttons -->
<!-- Move Shop / Tech Records / Plus Records buttons -->
<StackPanel Orientation="Horizontal" Spacing="4" Margin="0,4,0,0">
<Button Content="Move Shop" Command="{Binding OpenMoveShopCommand}" Padding="6,2" FontSize="10"
IsVisible="{Binding HasMoveShop}" />
<Button Content="Tech Records" Command="{Binding OpenTechRecordsCommand}" Padding="6,2" FontSize="10"
IsVisible="{Binding HasTechRecords}" />
<Button Content="Plus Records" Command="{Binding OpenPlusRecordCommand}" Padding="6,2" FontSize="10"
IsVisible="{Binding HasPlusRecord}" />
</StackPanel>
<!-- Relearn Moves -->
@ -683,10 +685,12 @@
</Grid>
</StackPanel>
<!-- Ribbons / Memories buttons -->
<!-- Ribbons / Memories / Medals buttons -->
<StackPanel Orientation="Horizontal" Spacing="4" Margin="0,8,0,0">
<Button Content="Ribbons" Command="{Binding OpenRibbonsCommand}" Padding="6,2" FontSize="10" />
<Button Content="Memories" Command="{Binding OpenMemoriesCommand}" Padding="6,2" FontSize="10" />
<Button Content="Medals" Command="{Binding OpenMedalsCommand}" Padding="6,2" FontSize="10"
IsVisible="{Binding HasSuperTraining}" />
</StackPanel>
</StackPanel>