Fix all critical audit gaps: PKRS, sprite refresh, StatNature, Noble

Critical fixes from 10-agent parity audit:
- PKRS system: Infected/Cured checkboxes + Strain/Days controls in Main tab
- Sprite refresh: UpdateSprite() on species/form/gender/shiny changes
- StatNature: ComboBox in Main tab (Gen 8+, separate from Nature)
- Noble flag: CheckBox in Stats tab (Legends Arceus, INoble)
- Randomize IV/EV buttons added to Stats tab
- OnIsInfectedChanged handler for PKRS detail visibility toggle
- Move legality indicators confirmed present in XAML
This commit is contained in:
montanon 2026-03-16 11:32:59 -03:00
parent c87d7fc81d
commit c83949ccf9
2 changed files with 170 additions and 23 deletions

View File

@ -58,6 +58,29 @@ public partial class PKMEditorViewModel : ObservableObject
partial void OnGenderChanged(byte value)
{
OnPropertyChanged(nameof(GenderSymbol));
if (!_isPopulating)
UpdateSprite();
}
// Stat Nature (Gen 8+)
[ObservableProperty] private Nature _statNature;
[ObservableProperty] private bool _hasStatNature;
[ObservableProperty] private ComboItem? _selectedStatNature;
partial void OnSelectedStatNatureChanged(ComboItem? value)
{
if (value is not null)
StatNature = (Nature)value.Value;
if (!_isPopulating)
{
OnPropertyChanged(nameof(AtkColor));
OnPropertyChanged(nameof(DefColor));
OnPropertyChanged(nameof(SpAColor));
OnPropertyChanged(nameof(SpDColor));
OnPropertyChanged(nameof(SpeColor));
RecalcStats();
UpdateLegality();
}
}
[ObservableProperty] private int _ability;
@ -66,6 +89,19 @@ public partial class PKMEditorViewModel : ObservableObject
[ObservableProperty] private bool _isEgg;
[ObservableProperty] private bool _isNicknamed;
// Pokerus
[ObservableProperty] private bool _isInfected;
[ObservableProperty] private bool _isCured;
[ObservableProperty] private int _pkrsStrain;
[ObservableProperty] private int _pkrsDays;
public bool ShowPkrsDetails => IsInfected;
partial void OnIsInfectedChanged(bool value)
{
OnPropertyChanged(nameof(ShowPkrsDetails));
}
// Shiny display indicators
[ObservableProperty] private bool _isShinyDisplay;
[ObservableProperty] private bool _isSquareShiny;
@ -167,6 +203,10 @@ public partial class PKMEditorViewModel : ObservableObject
[ObservableProperty] private bool _isAlpha;
[ObservableProperty] private bool _hasAlpha;
// Noble (Legends Arceus)
[ObservableProperty] private bool _isNoble;
[ObservableProperty] private bool _hasNoble;
// Tera Type (Gen 9)
[ObservableProperty] private int _teraTypeOriginal;
[ObservableProperty] private int _teraTypeOverride;
@ -469,7 +509,10 @@ private void RerollEc()
if (value is not null)
Form = (byte)value.Value;
if (!_isPopulating)
{
UpdateSprite();
UpdateLegality();
}
}
partial void OnSelectedSpeciesChanged(ComboItem? value)
@ -495,6 +538,7 @@ private void RerollEc()
try { Nickname = speciesName; }
finally { _isPopulating = false; }
}
UpdateSprite();
UpdateLegality();
}
}
@ -659,6 +703,11 @@ public void PopulateFields(PKM pk)
Form = pk.Form;
Gender = pk.Gender;
Nature = pk.Nature;
// Stat Nature (Gen 8+ have separate stat nature)
HasStatNature = pk.Format >= 8;
StatNature = pk.StatNature;
Ability = pk.Ability;
HeldItem = pk.HeldItem;
IsShiny = pk.IsShiny;
@ -667,6 +716,12 @@ public void PopulateFields(PKM pk)
IsEgg = pk.IsEgg;
IsNicknamed = pk.IsNicknamed;
// Pokerus
IsInfected = pk.IsPokerusInfected;
IsCured = pk.IsPokerusCured;
PkrsStrain = pk.PokerusStrain;
PkrsDays = pk.PokerusDays;
// New fields
PidHex = pk.PID.ToString("X8");
Exp = pk.EXP;
@ -812,6 +867,18 @@ public void PopulateFields(PKM pk)
IsAlpha = false;
}
// Noble (Legends Arceus)
if (pk is INoble noble)
{
HasNoble = true;
IsNoble = noble.IsNoble;
}
else
{
HasNoble = false;
IsNoble = false;
}
// Tera Type (Gen 9)
if (pk is ITeraType tt)
{
@ -1251,6 +1318,8 @@ public void PopulateFields(PKM pk)
// Look up ComboItems by matching Value
SelectedSpecies = SpeciesList.FirstOrDefault(x => x.Value == pk.Species);
SelectedNature = NatureList.FirstOrDefault(x => x.Value == (int)pk.Nature);
if (HasStatNature)
SelectedStatNature = NatureList.FirstOrDefault(x => x.Value == (int)pk.StatNature);
SelectedHeldItem = HeldItemList.FirstOrDefault(x => x.Value == pk.HeldItem);
SelectedMove1 = MoveList.FirstOrDefault(x => x.Value == pk.Move1);
SelectedMove2 = MoveList.FirstOrDefault(x => x.Value == pk.Move2);
@ -1307,6 +1376,8 @@ public void PopulateFields(PKM pk)
Entity.Form = Form;
Entity.Gender = Gender;
Entity.Nature = Nature;
if (HasStatNature)
Entity.StatNature = StatNature;
Entity.Ability = Ability;
Entity.HeldItem = HeldItem;
@ -1397,6 +1468,10 @@ public void PopulateFields(PKM pk)
if (Entity is IAlpha alphaSave)
alphaSave.IsAlpha = IsAlpha;
// Noble
if (Entity is INoble nobleSave)
nobleSave.IsNoble = IsNoble;
// Tera Type
if (Entity is ITeraType ttSave)
{
@ -1411,6 +1486,10 @@ public void PopulateFields(PKM pk)
Entity.IsEgg = IsEgg;
Entity.IsNicknamed = IsNicknamed;
// Pokerus
Entity.PokerusStrain = PkrsStrain;
Entity.PokerusDays = PkrsDays;
// OT Gender
Entity.OriginalTrainerGender = OtGender;
@ -1669,6 +1748,40 @@ private void ClearEvs()
Ev_SPA = 0; Ev_SPD = 0; Ev_SPE = 0;
}
[RelayCommand]
private void RandomizeIvs()
{
var rng = new Random();
Iv_HP = rng.Next(32);
Iv_ATK = rng.Next(32);
Iv_DEF = rng.Next(32);
Iv_SPA = rng.Next(32);
Iv_SPD = rng.Next(32);
Iv_SPE = rng.Next(32);
}
[RelayCommand]
private void RandomizeEvs()
{
var rng = new Random();
int remaining = 510;
var stats = new int[6];
for (int i = 0; i < 6 && remaining > 0; i++)
{
int max = Math.Min(252, remaining);
stats[i] = rng.Next(max + 1);
remaining -= stats[i];
}
// Shuffle so it's not biased toward HP
for (int i = stats.Length - 1; i > 0; i--)
{
int j = rng.Next(i + 1);
(stats[i], stats[j]) = (stats[j], stats[i]);
}
Ev_HP = stats[0]; Ev_ATK = stats[1]; Ev_DEF = stats[2];
Ev_SPA = stats[3]; Ev_SPD = stats[4]; Ev_SPE = stats[5];
}
// --- Full legality report (verbose, copies to clipboard) ---
[RelayCommand]
@ -1727,6 +1840,12 @@ private async Task ShowFullLegalityReport()
UpdateLegality();
}
partial void OnIsShinyChanged(bool value)
{
if (!_isPopulating)
UpdateSprite();
}
// --- IV changed handlers → recalc stats + legality ---
partial void OnIv_HPChanged(int value) { if (!_isPopulating) RecalcStats(); }

View File

@ -52,7 +52,7 @@
</StackPanel>
</TabItem.Header>
<Grid Margin="4,2,4,2"
RowDefinitions="27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27"
RowDefinitions="27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27"
ColumnDefinitions="104,*">
<!-- PID -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="PID:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
@ -89,61 +89,84 @@
<ComboBox Grid.Row="4" Grid.Column="1" ItemsSource="{Binding NatureList}" SelectedItem="{Binding SelectedNature}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left" />
<!-- Stat Nature (Gen 8+) -->
<TextBlock Grid.Row="5" Grid.Column="0" Text="Stat Nature:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
IsVisible="{Binding HasStatNature}" />
<ComboBox Grid.Row="5" Grid.Column="1" ItemsSource="{Binding NatureList}" SelectedItem="{Binding SelectedStatNature}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left"
IsVisible="{Binding HasStatNature}" />
<!-- Form -->
<TextBlock Grid.Row="5" Grid.Column="0" Text="Form:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Grid.Row="5" Grid.Column="1" ItemsSource="{Binding FormList}" SelectedItem="{Binding SelectedForm}"
<TextBlock Grid.Row="6" Grid.Column="0" Text="Form:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Grid.Row="6" Grid.Column="1" ItemsSource="{Binding FormList}" SelectedItem="{Binding SelectedForm}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left" />
<!-- Held Item -->
<TextBlock Grid.Row="6" Grid.Column="0" Text="Held Item:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Grid.Row="6" Grid.Column="1" ItemsSource="{Binding HeldItemList}" SelectedItem="{Binding SelectedHeldItem}"
<TextBlock Grid.Row="7" Grid.Column="0" Text="Held Item:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Grid.Row="7" Grid.Column="1" ItemsSource="{Binding HeldItemList}" SelectedItem="{Binding SelectedHeldItem}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left" />
<!-- Ability -->
<TextBlock Grid.Row="7" Grid.Column="0" Text="Ability:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Grid.Row="7" Grid.Column="1" ItemsSource="{Binding AbilityList}" SelectedItem="{Binding SelectedAbility}"
<TextBlock Grid.Row="8" Grid.Column="0" Text="Ability:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Grid.Row="8" Grid.Column="1" ItemsSource="{Binding AbilityList}" SelectedItem="{Binding SelectedAbility}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left" />
<!-- Friendship -->
<TextBlock Grid.Row="8" Grid.Column="0" Text="Friendship:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<NumericUpDown Grid.Row="8" Grid.Column="1" Value="{Binding Friendship}" Minimum="0" Maximum="255" Height="25" Width="60" HorizontalAlignment="Left" />
<TextBlock Grid.Row="9" Grid.Column="0" Text="Friendship:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<NumericUpDown Grid.Row="9" Grid.Column="1" Value="{Binding Friendship}" Minimum="0" Maximum="255" Height="25" Width="60" HorizontalAlignment="Left" />
<!-- Language -->
<TextBlock Grid.Row="9" Grid.Column="0" Text="Language:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Grid.Row="9" Grid.Column="1" ItemsSource="{Binding LanguageList}" SelectedItem="{Binding SelectedLanguage}"
<TextBlock Grid.Row="10" Grid.Column="0" Text="Language:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Grid.Row="10" Grid.Column="1" ItemsSource="{Binding LanguageList}" SelectedItem="{Binding SelectedLanguage}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left" />
<!-- IsEgg + Shiny -->
<StackPanel Grid.Row="10" Grid.Column="1" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
<StackPanel Grid.Row="11" Grid.Column="1" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
<CheckBox IsChecked="{Binding IsEgg}" Content="Is Egg" />
<CheckBox IsChecked="{Binding IsShiny}" Content="Shiny" />
</StackPanel>
<!-- PKRS -->
<StackPanel Grid.Row="12" Grid.Column="1" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
<CheckBox IsChecked="{Binding IsInfected}" Content="Infected" />
<CheckBox IsChecked="{Binding IsCured}" Content="Cured" />
</StackPanel>
<!-- PKRS Details (conditional) -->
<TextBlock Grid.Row="13" Grid.Column="0" Text="PKRS:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
IsVisible="{Binding ShowPkrsDetails}" />
<StackPanel Grid.Row="13" Grid.Column="1" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center"
IsVisible="{Binding ShowPkrsDetails}">
<TextBlock Text="Strain:" VerticalAlignment="Center" />
<NumericUpDown Value="{Binding PkrsStrain}" Minimum="0" Maximum="15" Height="25" Width="50" />
<TextBlock Text="Days:" VerticalAlignment="Center" Margin="4,0,0,0" />
<NumericUpDown Value="{Binding PkrsDays}" Minimum="0" Maximum="4" Height="25" Width="50" />
</StackPanel>
<!-- Shadow Pokemon (XD/Colosseum) -->
<TextBlock Grid.Row="11" Grid.Column="0" Text="Shadow ID:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
<TextBlock Grid.Row="14" Grid.Column="0" Text="Shadow ID:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
IsVisible="{Binding HasShadow}" />
<NumericUpDown Grid.Row="11" Grid.Column="1" Value="{Binding ShadowId}" Minimum="0" Maximum="65535" Height="25" Width="72" HorizontalAlignment="Left"
<NumericUpDown Grid.Row="14" Grid.Column="1" Value="{Binding ShadowId}" Minimum="0" Maximum="65535" Height="25" Width="72" HorizontalAlignment="Left"
IsVisible="{Binding HasShadow}" />
<TextBlock Grid.Row="12" Grid.Column="0" Text="Purification:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
<TextBlock Grid.Row="15" Grid.Column="0" Text="Purification:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
IsVisible="{Binding HasShadow}" />
<NumericUpDown Grid.Row="12" Grid.Column="1" Value="{Binding Purification}" Minimum="-100" Maximum="20000" Height="25" Width="72" HorizontalAlignment="Left"
<NumericUpDown Grid.Row="15" Grid.Column="1" Value="{Binding Purification}" Minimum="-100" Maximum="20000" Height="25" Width="72" HorizontalAlignment="Left"
IsVisible="{Binding HasShadow}" />
<!-- Catch Rate (Gen 1) -->
<TextBlock Grid.Row="13" Grid.Column="0" Text="Catch Rate:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
<TextBlock Grid.Row="16" Grid.Column="0" Text="Catch Rate:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
IsVisible="{Binding HasCatchRate}" />
<NumericUpDown Grid.Row="13" Grid.Column="1" Value="{Binding CatchRate}" Minimum="0" Maximum="255" Height="25" Width="60" HorizontalAlignment="Left"
<NumericUpDown Grid.Row="16" Grid.Column="1" Value="{Binding CatchRate}" Minimum="0" Maximum="255" Height="25" Width="60" HorizontalAlignment="Left"
IsVisible="{Binding HasCatchRate}" />
<!-- N's Sparkle (Gen 5) -->
<CheckBox Grid.Row="14" Grid.Column="1" Content="N's Sparkle" IsChecked="{Binding NSparkle}"
<CheckBox Grid.Row="17" Grid.Column="1" Content="N's Sparkle" IsChecked="{Binding NSparkle}"
IsVisible="{Binding HasNSparkle}" VerticalAlignment="Center" />
<!-- Form Argument -->
<TextBlock Grid.Row="15" Grid.Column="0" Text="Form Arg:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
<TextBlock Grid.Row="18" Grid.Column="0" Text="Form Arg:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
IsVisible="{Binding HasFormArgument}" />
<NumericUpDown Grid.Row="15" Grid.Column="1" Value="{Binding FormArgument}" Minimum="0" Maximum="{Binding FormArgumentMax}" Height="25" Width="80" HorizontalAlignment="Left"
<NumericUpDown Grid.Row="18" Grid.Column="1" Value="{Binding FormArgument}" Minimum="0" Maximum="{Binding FormArgumentMax}" Height="25" Width="80" HorizontalAlignment="Left"
IsVisible="{Binding HasFormArgument}" />
</Grid>
</TabItem>
@ -305,8 +328,10 @@
<StackPanel Orientation="Horizontal" Spacing="4" Margin="0,2,0,0">
<Button Content="Max IVs" Command="{Binding MaxIvsCommand}" Padding="6,2" FontSize="10" />
<Button Content="Clear IVs" Command="{Binding ClearIvsCommand}" Padding="6,2" FontSize="10" />
<Button Content="Rand IVs" Command="{Binding RandomizeIvsCommand}" Padding="6,2" FontSize="10" />
<Button Content="Max EVs" Command="{Binding MaxEvsCommand}" Padding="6,2" FontSize="10" />
<Button Content="Clear EVs" Command="{Binding ClearEvsCommand}" Padding="6,2" FontSize="10" />
<Button Content="Rand EVs" Command="{Binding RandomizeEvsCommand}" Padding="6,2" FontSize="10" />
</StackPanel>
<!-- Awakening Values (Let's Go) -->
@ -375,8 +400,11 @@
<!-- Gigantamax -->
<CheckBox IsVisible="{Binding HasGigantamax}" IsChecked="{Binding CanGigantamax}" Content="Can Gigantamax" Margin="4,2,0,0" />
<!-- Alpha (Legends Arceus) -->
<CheckBox IsVisible="{Binding HasAlpha}" IsChecked="{Binding IsAlpha}" Content="Is Alpha" Margin="4,2,0,0" />
<!-- Alpha (Legends Arceus) + Noble -->
<StackPanel Orientation="Horizontal" Spacing="8" Margin="4,2,0,0">
<CheckBox IsVisible="{Binding HasAlpha}" IsChecked="{Binding IsAlpha}" Content="Is Alpha" />
<CheckBox IsVisible="{Binding HasNoble}" IsChecked="{Binding IsNoble}" Content="Noble" />
</StackPanel>
<!-- Tera Type (Gen 9) -->
<StackPanel IsVisible="{Binding HasTeraType}" Spacing="2">