PKHeX/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs
Kurt 41578132cf Minor tweaks
Add Get/Set Relearn method to PKM.cs
Alpha Mastered move now indicates incomplete text entry (like moves/relearn)
Split up legal move indication helper class, use DI to allow other implementations of DataSource to be returned (pkhex mobile?).
Remove unused gender refresh method (see previous added UserControl commit)
Add helper method to center control within control
2022-05-02 18:11:31 -07:00

2082 lines
75 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using PKHeX.Core;
using PKHeX.Drawing;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using PKHeX.Drawing.PokeSprite;
using PKHeX.Drawing.PokeSprite.Properties;
using static PKHeX.Core.MessageStrings;
namespace PKHeX.WinForms.Controls
{
public sealed partial class PKMEditor : UserControl, IMainEditor
{
public bool IsInitialized { get; private set; }
public PKMEditor()
{
InitializeComponent();
// Groupbox doesn't show Click event in Designer...
GB_OT.Click += ClickGT;
GB_nOT.Click += ClickGT;
GB_CurrentMoves.Click += ClickMoves;
GB_RelearnMoves.Click += ClickMoves;
var font = FontUtil.GetPKXFont();
TB_Nickname.Font = TB_OT.Font = TB_HT.Font = font;
// Commonly reused Control arrays
Moves = new[] { CB_Move1, CB_Move2, CB_Move3, CB_Move4 };
Relearn = new[] { CB_RelearnMove1, CB_RelearnMove2, CB_RelearnMove3, CB_RelearnMove4 };
PPUps = new[] { CB_PPu1, CB_PPu2, CB_PPu3, CB_PPu4 };
MovePP = new[] { TB_PP1, TB_PP2, TB_PP3, TB_PP4 };
Markings = new[] { PB_Mark1, PB_Mark2, PB_Mark3, PB_Mark4, PB_Mark5, PB_Mark6 };
// Legality Indicators
relearnPB = new[] { PB_WarnRelearn1, PB_WarnRelearn2, PB_WarnRelearn3, PB_WarnRelearn4 };
movePB = new[] { PB_WarnMove1, PB_WarnMove2, PB_WarnMove3, PB_WarnMove4 };
// Validation of incompletely entered data fields
bool Criteria(Control c) => c.BackColor == Draw.InvalidSelection && c is ComboBox { Items.Count: not 0 };
ValidatedControls = new ValidationRequiredSet[]
{
new(Moves, _ => true, Criteria),
new(new[] {CB_Species}, _ => true, Criteria),
new(new[] {CB_HeldItem}, pk => pk.Format >= 2, Criteria),
new(new[] {CB_Ability, CB_Nature, CB_MetLocation, CB_Ball}, pk => pk.Format >= 3, Criteria),
new(new[] {CB_EggLocation}, pk => pk.Format >= 4, Criteria),
new(new[] {CB_Country, CB_SubRegion}, pk => pk is PK6 or PK7, Criteria),
new(Relearn, pk => pk.Format >= 6, Criteria),
new(new[] {CB_StatNature}, pk => pk.Format >= 8, Criteria),
new(new[] {CB_AlphaMastered}, pk => pk is PA8, Criteria),
};
foreach (var c in WinFormsUtil.GetAllControlsOfType<ComboBox>(this))
c.KeyDown += WinFormsUtil.RemoveDropCB;
Stats.MainEditor = this;
LoadShowdownSet = LoadShowdownSetDefault;
TID_Trainer.UpdatedID += Update_ID;
// Controls contained in a TabPage are not created until the tab page is shown
// Any data bindings in these controls are not activated until the tab page is shown.
FlickerInterface();
}
private sealed class ValidationRequiredSet
{
private readonly Control[] Controls;
private readonly Func<PKM, bool> ShouldCheck;
private readonly Func<Control, bool> IsInvalidState;
public Control? IsNotValid(PKM pk)
{
if (!ShouldCheck(pk))
return null;
return Array.Find(Controls, z => IsInvalidState(z));
}
public ValidationRequiredSet(Control[] controls, Func<PKM, bool> shouldCheck, Func<Control, bool> state)
{
Controls = controls;
ShouldCheck = shouldCheck;
IsInvalidState = state;
}
}
public void InitializeBinding()
{
ComboBox[] cbs =
{
CB_Nature, CB_StatNature,
CB_Country, CB_SubRegion, CB_3DSReg, CB_Language, CB_Ball, CB_HeldItem, CB_Species, DEV_Ability,
CB_GroundTile, CB_GameOrigin, CB_BattleVersion, CB_Ability, CB_MetLocation, CB_EggLocation, CB_Language, CB_HTLanguage,
CB_AlphaMastered,
};
foreach (var cb in cbs.Concat(Moves.Concat(Relearn)))
cb.InitializeBinding();
IsInitialized = true;
}
private void UpdateStats()
{
Stats.UpdateStats();
if (Entity is PB7)
SizeCP.TryResetStats();
}
private void LoadPartyStats(PKM pk) => Stats.LoadPartyStats(pk);
private void SavePartyStats(PKM pk)
{
Stats.SavePartyStats(pk);
pk.Stat_Level = Util.ToInt32((HaX ? MT_Level : TB_Level).Text);
}
public PKM CurrentPKM { get => PreparePKM(); set => Entity = value; }
public bool ModifyPKM { private get; set; } = true;
private bool _hideSecret;
public bool HideSecretValues
{
private get => _hideSecret;
set
{
_hideSecret = value;
var sav = RequestSaveFile;
ToggleSecrets(_hideSecret, sav.Generation);
}
}
public DrawConfig Draw { private get; set; } = null!;
public bool Unicode { get; set; } = true;
private bool _hax;
public bool HaX { get => _hax; set => _hax = Stats.HaX = value; }
private byte[] LastData = Array.Empty<byte>();
public PKM Data { get => Entity; set => Entity = value; }
public PKM Entity { get; private set; } = null!;
public bool FieldsLoaded { get; private set; }
public bool ChangingFields { get; set; }
/// <summary>
/// Currently loaded met location group that is populating Met and Egg location comboboxes
/// </summary>
private GameVersion origintrack;
private int originFormat = -1;
/// <summary>
/// Action to perform when loading a PKM to the editor GUI.
/// </summary>
private Action GetFieldsfromPKM = null!;
/// <summary>
/// Function that returns a <see cref="PKM"/> from the loaded fields.
/// </summary>
private Func<PKM> GetPKMfromFields = null!;
/// <summary>
/// Latest legality check result used to show legality indication.
/// </summary>
private LegalityAnalysis Legality = null!;
/// <summary>
/// List of legal moves for the latest <see cref="Legality"/>.
/// </summary>
private readonly LegalMoveSource<ComboItem> LegalMoveSource = new(new LegalMoveComboSource());
/// <summary>
/// Gender Symbols for showing Genders
/// </summary>
private IReadOnlyList<string> gendersymbols = GameInfo.GenderSymbolUnicode;
public event EventHandler? LegalityChanged;
public event EventHandler? UpdatePreviewSprite;
public event EventHandler? RequestShowdownImport;
public event EventHandler? RequestShowdownExport;
public event ReturnSAVEventHandler SaveFileRequested = null!;
public delegate SaveFile ReturnSAVEventHandler(object sender, EventArgs e);
private readonly PictureBox[] movePB, relearnPB;
public SaveFile RequestSaveFile => SaveFileRequested.Invoke(this, EventArgs.Empty);
public bool PKMIsUnsaved => FieldsLoaded && LastData.Any(b => b != 0) && !LastData.SequenceEqual(CurrentPKM.Data);
private readonly ComboBox[] Moves, Relearn, PPUps;
private readonly ValidationRequiredSet[] ValidatedControls;
private readonly MaskedTextBox[] MovePP;
private readonly PictureBox[] Markings;
private bool forceValidation;
public PKM PreparePKM(bool click = true)
{
if (click)
{
forceValidation = true;
ValidateChildren();
forceValidation = false;
}
var pk = GetPKMfromFields();
LastData = pk.Data;
return pk.Clone();
}
public bool EditsComplete
{
get
{
if (ModifierKeys == (Keys.Control | Keys.Shift | Keys.Alt))
return true; // Override
// If any controls are partially filled out, find the first one so we can indicate as such.
Control? cb = null;
foreach (var type in ValidatedControls)
{
cb = type.IsNotValid(Entity);
if (cb is not null)
break;
}
if (cb != null)
tabMain.SelectedTab = WinFormsUtil.FindFirstControlOfType<TabPage>(cb);
else if (!Stats.Valid)
tabMain.SelectedTab = Tab_Stats;
else if (WinFormsUtil.GetIndex(CB_Species) == 0 && !HaX) // can't set an empty slot...
tabMain.SelectedTab = Tab_Main;
else
return true;
System.Media.SystemSounds.Exclamation.Play();
return false;
}
}
public void SetPKMFormatMode(PKM pk)
{
// Load Extra Byte List
SetPKMFormatExtraBytes(pk);
(GetFieldsfromPKM, GetPKMfromFields) = GetLoadSet(pk);
}
private (Action, Func<PKM>) GetLoadSet(PKM pk) => pk.Format switch
{
1 => (PopulateFieldsPK1, PreparePK1),
2 => (PopulateFieldsPK2, PreparePK2),
3 => (PopulateFieldsPK3, PreparePK3),
4 => (PopulateFieldsPK4, PreparePK4),
5 => (PopulateFieldsPK5, PreparePK5),
6 => (PopulateFieldsPK6, PreparePK6),
7 when pk is PK7 => (PopulateFieldsPK7, PreparePK7),
7 when pk is PB7 => (PopulateFieldsPB7, PreparePB7),
8 when pk is PK8 => (PopulateFieldsPK8, PreparePK8),
8 when pk is PA8 => (PopulateFieldsPA8, PreparePA8),
8 when pk is PB8 => (PopulateFieldsPB8, PreparePB8),
_ => throw new FormatException($"Unrecognized Type: {pk.GetType()}"),
};
private void SetPKMFormatExtraBytes(PKM pk)
{
var extraBytes = pk.ExtraBytes;
GB_ExtraBytes.Visible = GB_ExtraBytes.Enabled = extraBytes.Count != 0;
CB_ExtraBytes.Items.Clear();
foreach (var b in extraBytes)
CB_ExtraBytes.Items.Add($"0x{b:X2}");
if (GB_ExtraBytes.Enabled)
CB_ExtraBytes.SelectedIndex = 0;
}
public void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false) => LoadFieldsFromPKM(pk, focus, skipConversionCheck);
private void LoadFieldsFromPKM(PKM pk, bool focus = true, bool skipConversionCheck = true)
{
if (focus)
Tab_Main.Focus();
if (!skipConversionCheck && !EntityConverter.TryMakePKMCompatible(pk, Entity, out var c, out pk))
{
var msg = c.GetDisplayString(pk, Entity.GetType());
WinFormsUtil.Alert(msg);
return;
}
FieldsLoaded = false;
Entity = pk.Clone();
#if !DEBUG
try { GetFieldsfromPKM(); }
catch { }
#else
GetFieldsfromPKM();
#endif
Stats.UpdateIVs(this, EventArgs.Empty);
UpdatePKRSInfected(this, EventArgs.Empty);
UpdatePKRSCured(this, EventArgs.Empty);
UpdateNatureModification(CB_StatNature, Entity.StatNature);
if (HaX) // Load original values from pk not pkm
{
MT_Level.Text = (pk.PartyStatsPresent ? pk.Stat_Level : pk.CurrentLevel).ToString();
TB_EXP.Text = pk.EXP.ToString();
if (pk.PartyStatsPresent) // stats present
Stats.LoadPartyStats(pk);
}
FieldsLoaded = true;
SetMarkings();
UpdateLegality();
UpdateSprite();
LastData = PreparePKM().Data;
}
public void UpdateLegality(LegalityAnalysis? la = null, bool skipMoveRepop = false)
{
if (!FieldsLoaded)
return;
Legality = la ?? new LegalityAnalysis(Entity, RequestSaveFile.Personal);
if (!Legality.Parsed || HaX || Entity.Species == 0)
{
PB_WarnMove1.Visible = PB_WarnMove2.Visible = PB_WarnMove3.Visible = PB_WarnMove4.Visible = false;
PB_WarnRelearn1.Visible = PB_WarnRelearn2.Visible = PB_WarnRelearn3.Visible = PB_WarnRelearn4.Visible = false;
LegalityChanged?.Invoke(Legality.Valid, EventArgs.Empty);
return;
}
// Refresh Move Legality
var info = Legality.Info;
var moves = info.Moves;
for (int i = 0; i < 4; i++)
{
var pb = movePB[i];
pb.Visible = true;
pb.Image = GetMoveImage(!moves[i].Valid, Entity, i);
}
if (Entity.Format >= 6)
{
var relearn = info.Relearn;
for (int i = 0; i < 4; i++)
relearnPB[i].Visible = !relearn[i].Valid;
}
if (skipMoveRepop)
return;
// Resort moves
FieldsLoaded = false;
LegalMoveSource.ReloadMoves(Legality.GetSuggestedMovesAndRelearn());
FieldsLoaded = true;
LegalityChanged?.Invoke(Legality.Valid, EventArgs.Empty);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Bitmap? GetMoveImage(bool isIllegal, PKM pkm, int index)
{
if (isIllegal)
return Resources.warn;
if (pkm.Format >= 8 && Legal.GetDummiedMovesHashSet(pkm).Contains(pkm.GetMove(index)))
return Resources.hint;
return null;
}
public void UpdateUnicode(IReadOnlyList<string> symbols)
{
gendersymbols = symbols;
if (!Unicode)
{
BTN_Shinytize.Text = Draw.ShinyDefault;
TB_Nickname.Font = TB_OT.Font = TB_HT.Font = GB_OT.Font;
}
else
{
BTN_Shinytize.Text = Draw.ShinyUnicode;
TB_Nickname.Font = TB_OT.Font = TB_HT.Font = FontUtil.GetPKXFont();
}
}
private static string ReloadGender(string text, IReadOnlyList<string> genders)
{
var index = PKX.GetGenderFromString(text);
if (index >= 2)
return text;
return genders[index];
}
internal void UpdateSprite()
{
if (FieldsLoaded && !forceValidation)
UpdatePreviewSprite?.Invoke(this, EventArgs.Empty);
}
// General Use Functions //
private void SetDetailsOT(ITrainerInfo tr)
{
if (string.IsNullOrWhiteSpace(tr.OT))
return;
// Get Save Information
TB_OT.Text = tr.OT;
UC_OTGender.Gender = tr.Gender & 1;
TID_Trainer.LoadInfo(tr);
if (tr.Game >= 0)
CB_GameOrigin.SelectedValue = tr.Game;
var lang = tr.Language;
if (lang <= 0)
lang = (int)LanguageID.English;
CB_Language.SelectedValue = lang;
if (tr is IRegionOrigin o)
{
CB_3DSReg.SelectedValue = (int)o.ConsoleRegion;
CB_Country.SelectedValue = (int)o.Country;
CB_SubRegion.SelectedValue = (int)o.Region;
}
// Copy OT trash bytes for sensitive games (Gen1/2)
if (tr is SAV1 s1 && Entity is PK1 p1) s1.OT_Trash.CopyTo(p1.OT_Trash);
else if (tr is SAV2 s2 && Entity is PK2 p2) s2.OT_Trash.CopyTo(p2.OT_Trash);
UpdateNickname(this, EventArgs.Empty);
}
private void SetDetailsHT(ITrainerInfo tr)
{
if (string.IsNullOrWhiteSpace(tr.OT))
return;
if (TB_HT.Text.Length > 0)
{
UC_HTGender.Gender = tr.Gender & 1;
if (Entity is IHandlerLanguage)
CB_HTLanguage.SelectedValue = tr.Language;
}
}
private void SetForms()
{
int species = Entity.Species;
var pi = RequestSaveFile.Personal[species];
bool hasForms = FormInfo.HasFormSelection(pi, species, Entity.Format);
CB_Form.Enabled = CB_Form.Visible = Label_Form.Visible = hasForms;
if (HaX && Entity.Format >= 4)
Label_Form.Visible = true; // show with value entry textbox
if (!hasForms)
{
if (HaX)
return;
Entity.Form = 0;
if (CB_Form.Items.Count > 0)
CB_Form.SelectedIndex = 0;
return;
}
var ds = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, gendersymbols, Entity.Format);
if (ds.Length == 1 && string.IsNullOrEmpty(ds[0])) // empty (Alolan Totems)
CB_Form.Enabled = CB_Form.Visible = Label_Form.Visible = false;
else
CB_Form.DataSource = ds;
}
private void SetAbilityList()
{
if (Entity.Format < 3) // no abilities
return;
if (Entity.Format > 3 && FieldsLoaded) // has forms
Entity.Form = CB_Form.SelectedIndex; // update pkm field for form specific abilities
int abil = CB_Ability.SelectedIndex;
bool tmp = FieldsLoaded;
FieldsLoaded = false;
CB_Ability.DataSource = GameInfo.FilteredSources.GetAbilityList(Entity);
CB_Ability.SelectedIndex = GetSafeIndex(CB_Ability, abil); // restore original index if available
FieldsLoaded = tmp;
}
private static int GetSafeIndex(ComboBox cb, int index) => Math.Max(0, Math.Min(cb.Items.Count - 1, index));
private void UpdateIsShiny()
{
// Set the Controls
var type = ShinyExtensions.GetType(Entity);
BTN_Shinytize.Visible = BTN_Shinytize.Enabled = type == Shiny.Never;
PB_ShinyStar.Visible = type == Shiny.AlwaysStar;
PB_ShinySquare.Visible = type == Shiny.AlwaysSquare;
// Refresh Markings (for Shiny Star if applicable)
SetMarkings();
}
private void SetMarkings()
{
var pba = Markings;
var markings = Entity.Markings;
for (int i = 0; i < pba.Length; i++)
pba[i].Image = GetMarkSprite(pba[i], markings[i] != 0);
PB_MarkShiny.Image = GetMarkSprite(PB_MarkShiny, !BTN_Shinytize.Enabled);
PB_MarkCured.Image = GetMarkSprite(PB_MarkCured, CHK_Cured.Checked);
PB_Favorite.Image = GetMarkSprite(PB_Favorite, Entity is IFavorite {Favorite: true});
PB_Origin.Image = GetOriginSprite(Entity);
// Colored Markings
if (Entity.Format < 7)
return;
for (int i = 0; i < pba.Length; i++)
{
if (!Draw.GetMarkingColor(markings[i], out Color c))
continue;
var pb = pba[i];
pb.Image = ImageUtil.ChangeAllColorTo(pb.Image, c);
}
}
private static Image? GetOriginSprite(PKM pkm)
{
if (pkm.Format < 6)
return null;
// Specific Markings
if (pkm.VC)
return Properties.Resources.gen_vc;
if (pkm.GO)
return Properties.Resources.gen_go;
if (pkm.LGPE)
return Properties.Resources.gen_gg;
// Lumped Generations
if (pkm.Gen6)
return Properties.Resources.gen_6;
if (pkm.Gen7)
return Properties.Resources.gen_7;
if (pkm.SWSH)
return Properties.Resources.gen_8;
if (pkm.BDSP)
return Properties.Resources.gen_bs;
if (pkm.LA)
return Properties.Resources.gen_la;
return null;
}
private static void SetCountrySubRegion(ComboBox cb, string type)
{
int oldIndex = cb.SelectedIndex;
cb.DataSource = Util.GetCountryRegionList(type, GameInfo.CurrentLanguage);
if (oldIndex > 0 && oldIndex < cb.Items.Count)
cb.SelectedIndex = oldIndex;
}
// Prompted Updates of PKM //
private void ClickFriendship(object sender, EventArgs e)
{
var pk = Entity;
bool worst = ModifierKeys == Keys.Control ^ pk.IsEgg;
var current = int.Parse(TB_Friendship.Text);
var value = worst
? pk.IsEgg ? EggStateLegality.GetMinimumEggHatchCycles(pk) : 0
: pk.IsEgg ? EggStateLegality.GetMaximumEggHatchCycles(pk) : current == 255 ? pk.PersonalInfo.BaseFriendship : 255;
TB_Friendship.Text = value.ToString();
}
private void ClickLevel(object sender, EventArgs e)
{
if (ModifierKeys == Keys.Control && sender is TextBoxBase tb)
tb.Text = "100";
}
private void ClickGender(object sender, EventArgs e)
{
if (!Entity.PersonalInfo.IsDualGender)
return; // can't toggle
var (canToggle, gender) = UC_Gender.ToggleGender();
if (!canToggle)
return;
if (Entity.Format <= 2)
{
Stats.SetATKIVGender(gender);
UpdateIsShiny();
}
else if (Entity.Format <= 4)
{
Entity.Version = WinFormsUtil.GetIndex(CB_GameOrigin);
Entity.Nature = WinFormsUtil.GetIndex(CB_Nature);
Entity.Form = CB_Form.SelectedIndex;
Entity.SetPIDGender(gender);
TB_PID.Text = Entity.PID.ToString("X8");
}
Entity.Gender = gender;
if (PKX.GetGenderFromString(CB_Form.Text) < 2) // Gendered Forms
CB_Form.SelectedIndex = Math.Min(gender, CB_Form.Items.Count - 1);
UpdatePreviewSprite?.Invoke(UC_Gender, EventArgs.Empty);
}
private void ClickPP(object sender, EventArgs e)
{
for (int i = 0; i < MovePP.Length; i++)
RefreshMovePP(i);
}
private void ClickPPUps(object sender, EventArgs e)
{
bool min = (ModifierKeys & Keys.Control) != 0 || !Legal.IsPPUpAvailable(Entity);
static int GetValue(ListControl cb, bool zero) => zero || WinFormsUtil.GetIndex(cb) == 0 ? 0 : 3;
CB_PPu1.SelectedIndex = GetValue(CB_Move1, min);
CB_PPu2.SelectedIndex = GetValue(CB_Move2, min);
CB_PPu3.SelectedIndex = GetValue(CB_Move3, min);
CB_PPu4.SelectedIndex = GetValue(CB_Move4, min);
}
private void ClickMarking(object sender, EventArgs e)
{
int index = Array.IndexOf(Markings, (PictureBox)sender);
Entity.ToggleMarking(index);
SetMarkings();
}
private void ClickFavorite(object sender, EventArgs e)
{
if (Entity is IFavorite pb7)
pb7.Favorite ^= true;
SetMarkings();
}
private void ClickOT(object sender, EventArgs e) => SetDetailsOT(SaveFileRequested.Invoke(this, e));
private void ClickCT(object sender, EventArgs e) => SetDetailsHT(SaveFileRequested.Invoke(this, e));
private void ClickBall(object sender, EventArgs e)
{
Entity.Ball = WinFormsUtil.GetIndex(CB_Ball);
if ((ModifierKeys & Keys.Alt) != 0)
{
CB_Ball.SelectedValue = (int)Ball.Poke;
return;
}
if ((ModifierKeys & Keys.Shift) != 0)
{
CB_Ball.SelectedValue = BallApplicator.ApplyBallLegalByColor(Entity);
return;
}
using var frm = new BallBrowser();
frm.LoadBalls(Entity);
frm.ShowDialog();
if (frm.BallChoice >= 0)
CB_Ball.SelectedValue = frm.BallChoice;
}
private void ClickShinyLeaf(object sender, EventArgs e) => ShinyLeaf.CheckAll(ModifierKeys != Keys.Control);
private void ClickMetLocation(object sender, EventArgs e)
{
if (HaX)
return;
Entity = PreparePKM();
UpdateLegality(skipMoveRepop: true);
if (Legality.Valid)
return;
if (!SetSuggestedMetLocation())
return;
Entity = PreparePKM();
UpdateLegality();
}
private void ClickGT(object? sender, EventArgs e)
{
if (!GB_nOT.Visible)
return;
if (sender == GB_OT)
Entity.CurrentHandler = 0;
else if (TB_HT.Text.Length > 0)
Entity.CurrentHandler = 1;
UpadteHandlingTrainerBackground(Entity.CurrentHandler);
ReloadToFriendshipTextBox(Entity);
}
private void ClickNature(object sender, EventArgs e)
{
if (Entity.Format < 8)
return;
if (sender == Label_Nature)
CB_Nature.SelectedIndex = CB_StatNature.SelectedIndex;
else
CB_StatNature.SelectedIndex = CB_Nature.SelectedIndex;
}
private void ClickMoves(object? sender, EventArgs e)
{
UpdateLegality(skipMoveRepop: true);
if (sender == GB_CurrentMoves)
{
bool random = ModifierKeys == Keys.Control;
if (!SetSuggestedMoves(random))
return;
}
else if (sender == GB_RelearnMoves)
{
if (!SetSuggestedRelearnMoves())
return;
}
else
{
return;
}
UpdateLegality();
}
private bool SetSuggestedMoves(bool random = false, bool silent = false)
{
var m = Entity.GetMoveSet(random);
if (m.Length == 0 || m.All(z => z == 0))
{
if (!silent)
WinFormsUtil.Alert(MsgPKMSuggestionFormat);
return false;
}
if (Entity.Moves.SequenceEqual(m))
return false;
if (!silent)
{
var mv = GameInfo.Strings.Move;
var movestrings = m.Select(v => (uint)v >= mv.Count ? MsgProgramError : mv[v]);
var msg = string.Join(Environment.NewLine, movestrings);
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgPKMSuggestionMoves, msg))
return false;
}
Entity.SetMoves(m);
FieldsLoaded = false;
LoadMoves(Entity);
ClickPP(this, EventArgs.Empty);
FieldsLoaded = true;
return true;
}
private bool SetSuggestedRelearnMoves(bool silent = false)
{
if (Entity.Format < 6)
return false;
var m = Legality.GetSuggestedRelearnMoves();
if (Entity.RelearnMoves.SequenceEqual(m) || m.Count != 4)
return false;
if (!silent)
{
var mv = GameInfo.Strings.Move;
var movestrings = m.Select(v => (uint)v >= mv.Count ? MsgProgramError : mv[v]);
var msg = string.Join(Environment.NewLine, movestrings);
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgPKMSuggestionRelearn, msg))
return false;
}
CB_RelearnMove1.SelectedValue = m[0];
CB_RelearnMove2.SelectedValue = m[1];
CB_RelearnMove3.SelectedValue = m[2];
CB_RelearnMove4.SelectedValue = m[3];
return true;
}
private bool SetSuggestedMetLocation(bool silent = false)
{
var encounter = EncounterSuggestion.GetSuggestedMetInfo(Entity);
if (encounter == null || (Entity.Format >= 3 && encounter.Location < 0))
{
if (!silent)
WinFormsUtil.Alert(MsgPKMSuggestionNone);
return false;
}
int level = encounter.LevelMin;
int location = encounter.Location;
int minlvl = EncounterSuggestion.GetLowestLevel(Entity, encounter.LevelMin);
if (minlvl == 0)
minlvl = level;
if (Entity.CurrentLevel >= minlvl && Entity.Met_Level == level && Entity.Met_Location == location)
{
if (!encounter.HasGroundTile(Entity.Format) || WinFormsUtil.GetIndex(CB_GroundTile) == (int)encounter.GetSuggestedGroundTile())
return false;
}
if (minlvl < level)
minlvl = level;
if (!silent)
{
var suggestions = EntitySuggestionUtil.GetMetLocationSuggestionMessage(Entity, level, location, minlvl);
if (suggestions.Count <= 1) // no suggestion
return false;
var msg = string.Join(Environment.NewLine, suggestions);
if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg) != DialogResult.Yes)
return false;
}
if (Entity.Format >= 3)
{
Entity.Met_Location = location;
TB_MetLevel.Text = encounter.GetSuggestedMetLevel(Entity).ToString();
CB_MetLocation.SelectedValue = location;
if (encounter.HasGroundTile(Entity.Format))
CB_GroundTile.SelectedValue = (int)encounter.GetSuggestedGroundTile();
if (Entity.Gen6 && Entity.WasEgg && ModifyPKM)
Entity.SetHatchMemory6();
}
if (Entity.CurrentLevel < minlvl)
TB_Level.Text = minlvl.ToString();
return true;
}
public void UpdateIVsGB(bool skipForm)
{
if (!FieldsLoaded)
return;
UC_Gender.Gender = Entity.Gender;
if (Entity.Species == (int)Species.Unown && !skipForm)
CB_Form.SelectedIndex = Entity.Form;
UpdateIsShiny();
UpdateSprite();
}
private void UpdateBall(object sender, EventArgs e)
{
PB_Ball.Image = SpriteUtil.GetBallSprite(WinFormsUtil.GetIndex(CB_Ball));
}
private void UpdateEXPLevel(object sender, EventArgs e)
{
if (ChangingFields)
return;
ChangingFields = true;
if (sender == TB_EXP)
{
// Change the Level
uint EXP = Util.ToUInt32(TB_EXP.Text);
var gr = Entity.PersonalInfo.EXPGrowth;
int Level = Experience.GetLevel(EXP, gr);
if (Level == 100)
EXP = Experience.GetEXP(100, gr);
TB_Level.Text = Level.ToString();
if (!HaX)
TB_EXP.Text = EXP.ToString();
else if (Level <= 100 && Util.ToInt32(MT_Level.Text) <= 100)
MT_Level.Text = Level.ToString();
}
else
{
// Change the XP
int level = Util.ToInt32((HaX ? MT_Level : TB_Level).Text);
if (level <= 0)
{
TB_Level.Text = "1";
}
else if (level > 100)
{
TB_Level.Text = "100";
if (!HaX)
level = 100;
}
if (level > byte.MaxValue)
MT_Level.Text = "255";
else if (level <= 100)
TB_EXP.Text = Experience.GetEXP(level, Entity.PersonalInfo.EXPGrowth).ToString();
}
ChangingFields = false;
if (FieldsLoaded) // store values back
{
Entity.EXP = Util.ToUInt32(TB_EXP.Text);
Entity.Stat_Level = Util.ToInt32((HaX ? MT_Level : TB_Level).Text);
}
UpdateStats();
UpdateLegality();
}
private void UpdateRandomPID(object sender, EventArgs e)
{
if (Entity.Format < 3)
return;
if (FieldsLoaded)
Entity.PID = Util.GetHexValue(TB_PID.Text);
if (sender == UC_Gender)
Entity.SetPIDGender(Entity.Gender);
else if (sender == CB_Nature && Entity.Nature != WinFormsUtil.GetIndex(CB_Nature))
Entity.SetPIDNature(WinFormsUtil.GetIndex(CB_Nature));
else if (sender == BTN_RerollPID)
Entity.SetPIDGender(Entity.Gender);
else if (sender == CB_Ability && CB_Ability.SelectedIndex != Entity.PIDAbility && Entity.PIDAbility > -1)
Entity.SetAbilityIndex(CB_Ability.SelectedIndex);
TB_PID.Text = Entity.PID.ToString("X8");
if (Entity.Format >= 6 && (Entity.Gen3 || Entity.Gen4 || Entity.Gen5))
TB_EC.Text = TB_PID.Text;
Update_ID(TB_EC, e);
}
private void UpdateRandomEC(object sender, EventArgs e)
{
if (Entity.Format < 6)
return;
Entity.SetRandomEC();
TB_EC.Text = Entity.EncryptionConstant.ToString("X8");
Update_ID(TB_EC, e);
UpdateLegality();
}
private void Update255_MTB(object sender, EventArgs e)
{
if (sender is not MaskedTextBox tb)
return;
if (Util.ToInt32(tb.Text) > byte.MaxValue)
tb.Text = "255";
if (sender == TB_Friendship && int.TryParse(TB_Friendship.Text, out var value))
{
UpdateFromFriendshipTextBox(Entity, value);
UpdateStats();
}
}
private void UpdateFormArgument(object sender, EventArgs e)
{
if (FieldsLoaded && Entity.Species == (int)Species.Alcremie)
UpdateSprite();
}
private void UpdateForm(object sender, EventArgs e)
{
if (FieldsLoaded && sender == CB_Form)
{
Entity.Form = CB_Form.SelectedIndex;
uint EXP = Experience.GetEXP(Entity.CurrentLevel, Entity.PersonalInfo.EXPGrowth);
TB_EXP.Text = EXP.ToString();
}
UpdateStats();
SetAbilityList();
// Gender Forms
if (WinFormsUtil.GetIndex(CB_Species) == (int)Species.Unown && FieldsLoaded)
{
if (Entity.Format == 3)
{
Entity.SetPIDUnown3(CB_Form.SelectedIndex);
TB_PID.Text = Entity.PID.ToString("X8");
}
else if (Entity.Format == 2)
{
int desiredForm = CB_Form.SelectedIndex;
while (Entity.Form != desiredForm)
{
FieldsLoaded = false;
Stats.UpdateRandomIVs(sender, EventArgs.Empty);
FieldsLoaded = true;
}
}
}
else if (CB_Form.Enabled && PKX.GetGenderFromString(CB_Form.Text) < 2)
{
if (CB_Form.Items.Count == 2) // actually M/F; Pumpkaboo formes in German are S,M,L,XL
{
Entity.Gender = CB_Form.SelectedIndex;
UC_Gender.Gender = Entity.GetSaneGender();
}
}
else
{
UC_Gender.Gender = Entity.GetSaneGender();
}
RefreshFormArguments();
if (ChangingFields)
return;
UpdateSprite();
}
private void RefreshFormArguments()
{
if (Entity is not IFormArgument f)
return;
if (FieldsLoaded)
FA_Form.SaveArgument(f);
FA_Form.LoadArgument(f, Entity.Species, Entity.Form, Entity.Format);
}
private void UpdatePP(object sender, EventArgs e)
{
if (sender is not ComboBox cb)
return;
int index = Array.IndexOf(Moves, cb);
if (index < 0)
index = Array.IndexOf(PPUps, cb);
if (index < 0)
return;
RefreshMovePP(index);
}
private void RefreshMovePP(int index)
{
int move = WinFormsUtil.GetIndex(Moves[index]);
var ppUpControl = PPUps[index];
int ppUpCount = ppUpControl.SelectedIndex;
if (move <= 0)
{
ppUpControl.SelectedIndex = 0;
MovePP[index].Text = 0.ToString();
}
else
{
MovePP[index].Text = Entity.GetMovePP(move, ppUpCount).ToString();
}
}
private void UpdatePKRSstrain(object sender, EventArgs e)
{
// Change the PKRS Days to the legal bounds.
int currentDuration = CB_PKRSDays.SelectedIndex;
CB_PKRSDays.Items.Clear();
var strain = CB_PKRSStrain.SelectedIndex;
int max = Pokerus.GetMaxDuration(strain);
for (int day = 0; day <= max; day++)
CB_PKRSDays.Items.Add(day.ToString());
// Set the days back if they're legal
CB_PKRSDays.SelectedIndex = strain == 0 ? 0 : Math.Min(max, currentDuration);
}
private void UpdatePKRSdays(object sender, EventArgs e)
{
var days = CB_PKRSDays.SelectedIndex;
if (days != 0)
return;
// If no days are selected
var strain = CB_PKRSStrain.SelectedIndex;
if (Pokerus.IsSusceptible(strain, days))
CHK_Cured.Checked = CHK_Infected.Checked = false; // No Strain = Never Cured / Infected, triggers Strain update
else if (Pokerus.IsImmune(strain, days))
CHK_Cured.Checked = true; // Any Strain = Cured
}
private void UpdatePKRSCured(object sender, EventArgs e)
{
// Cured PokeRus is toggled
if (CHK_Cured.Checked)
{
// Has Had PokeRus
Label_PKRSdays.Visible = CB_PKRSDays.Visible = false;
CB_PKRSDays.SelectedIndex = 0;
Label_PKRS.Visible = CB_PKRSStrain.Visible = true;
CHK_Infected.Checked = true;
// If we're cured we have to have a strain infection.
if (CB_PKRSStrain.SelectedIndex == 0)
CB_PKRSStrain.SelectedIndex = 1;
}
else if (!CHK_Infected.Checked)
{
// Not Infected, Disable the other
Label_PKRS.Visible = CB_PKRSStrain.Visible = false;
CB_PKRSStrain.SelectedIndex = 0;
}
else
{
// Still Infected for a duration
Label_PKRSdays.Visible = CB_PKRSDays.Visible = true;
CB_PKRSDays.SelectedValue = 1;
}
// if not cured yet, days > 0
if (!CHK_Cured.Checked && CHK_Infected.Checked && CB_PKRSDays.SelectedIndex == 0)
CB_PKRSDays.SelectedIndex = 1;
SetMarkings();
}
private void UpdatePKRSInfected(object sender, EventArgs e)
{
if (CHK_Cured.Checked)
{
if (!CHK_Infected.Checked)
CHK_Cured.Checked = false;
return;
}
Label_PKRS.Visible = CB_PKRSStrain.Visible = CHK_Infected.Checked;
if (!CHK_Infected.Checked)
{
CB_PKRSStrain.SelectedIndex = 0;
CB_PKRSDays.SelectedIndex = 0;
Label_PKRSdays.Visible = CB_PKRSDays.Visible = false;
}
else if (CB_PKRSStrain.SelectedIndex == 0)
{
CB_PKRSStrain.SelectedIndex = CB_PKRSDays.SelectedIndex = 1;
Label_PKRSdays.Visible = CB_PKRSDays.Visible = true;
UpdatePKRSCured(sender, e);
}
}
private void UpdateCountry(object sender, EventArgs e)
{
int index;
if (sender is ComboBox c && (index = WinFormsUtil.GetIndex(c)) > 0)
SetCountrySubRegion(CB_SubRegion, $"sr_{index:000}");
}
private void UpdateSpecies(object sender, EventArgs e)
{
// Get Species dependent information
if (FieldsLoaded)
Entity.Species = WinFormsUtil.GetIndex(CB_Species);
SpeciesIDTip.SetToolTip(CB_Species, Entity.Species.ToString("000"));
SetAbilityList();
SetForms();
UpdateForm(sender, EventArgs.Empty);
if (!FieldsLoaded)
return;
// Recalculate EXP for Given Level
uint EXP = Experience.GetEXP(Entity.CurrentLevel, Entity.PersonalInfo.EXPGrowth);
TB_EXP.Text = EXP.ToString();
// Check for Gender Changes
UC_Gender.Gender = Entity.GetSaneGender();
// If species changes and no nickname, set the new name == speciesName.
if (!CHK_Nicknamed.Checked)
UpdateNickname(sender, e);
UpdateLegality();
}
private void UpdateOriginGame(object sender, EventArgs e)
{
GameVersion version = (GameVersion)WinFormsUtil.GetIndex(CB_GameOrigin);
if (version.IsValidSavedVersion())
{
CheckMetLocationChange(version, Entity.Format);
if (FieldsLoaded)
Entity.Version = (int)version;
}
// Visibility logic for Gen 4 ground tile; only show for Gen 4 Pokemon.
if (Entity is IGroundTile)
{
bool g4 = Entity.Gen4;
CB_GroundTile.Visible = Label_GroundTile.Visible = g4 && Entity.Format < 7;
if (!g4)
CB_GroundTile.SelectedValue = (int)GroundTileType.None;
}
if (!FieldsLoaded)
return;
PB_Origin.Image = GetOriginSprite(Entity);
TID_Trainer.LoadIDValues(Entity);
UpdateLegality();
}
private void CheckMetLocationChange(GameVersion version, int format)
{
// Does the list of locations need to be changed to another group?
var group = GameUtil.GetMetLocationVersionGroup(version);
if (group != origintrack || format != originFormat)
ReloadMetLocations(version, format);
origintrack = group;
originFormat = format;
}
private void ReloadMetLocations(GameVersion version, int format)
{
var metList = GameInfo.GetLocationList(version, format, egg: false);
CB_MetLocation.DataSource = new BindingSource(metList, null);
CB_MetLocation.DropDownWidth = GetWidth(metList, CB_MetLocation.Font);
var eggList = GameInfo.GetLocationList(version, format, egg: true);
CB_EggLocation.DataSource = new BindingSource(eggList, null);
CB_EggLocation.DropDownWidth = GetWidth(eggList, CB_EggLocation.Font);
static int GetWidth(IEnumerable<ComboItem> items, Font f) =>
items.Max(z => TextRenderer.MeasureText(z.Text, f).Width) +
SystemInformation.VerticalScrollBarWidth;
if (FieldsLoaded)
{
SetMarkings(); // Set/Remove the Nativity marking when gamegroup changes too
int metLoc = EncounterSuggestion.GetSuggestedTransferLocation(Entity);
int eggLoc = CHK_AsEgg.Checked
? EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(format, version)
: Locations.GetNoneLocation(version);
CB_MetLocation.SelectedValue = Math.Max(0, metLoc);
CB_EggLocation.SelectedValue = eggLoc;
}
else
{
ValidateChildren(); // hacky validation forcing
}
}
private void UpdateExtraByteValue(object sender, EventArgs e)
{
if (CB_ExtraBytes.Items.Count == 0 || sender is not MaskedTextBox mtb)
return;
// Changed Extra Byte's Value
var value = Util.ToInt32(mtb.Text);
if (value > byte.MaxValue)
{
mtb.Text = "255";
return; // above statement triggers the event again.
}
int offset = Convert.ToInt32(CB_ExtraBytes.Text, 16);
Entity.Data[offset] = (byte)value;
}
private void UpdateExtraByteIndex(object sender, EventArgs e)
{
if (CB_ExtraBytes.Items.Count == 0)
return;
// Byte changed, need to refresh the Text box for the byte's value.
var offset = Convert.ToInt32(CB_ExtraBytes.Text, 16);
TB_ExtraByte.Text = Entity.Data[offset].ToString();
}
public void ChangeNature(int newNature)
{
if (Entity.Format < 3)
return;
var cb = Entity.Format >= 8 ? CB_StatNature : CB_Nature;
cb.SelectedValue = newNature;
}
private void UpdateNatureModification(ComboBox cb, int nature)
{
string text = Stats.UpdateNatureModification(nature);
NatureTip.SetToolTip(cb, text);
}
private void UpdateIsNicknamed(object sender, EventArgs e)
{
if (!FieldsLoaded)
return;
Entity.Nickname = TB_Nickname.Text;
if (CHK_Nicknamed.Checked)
return;
int species = WinFormsUtil.GetIndex(CB_Species);
if (species < 1 || species > Entity.MaxSpeciesID)
return;
if (CHK_IsEgg.Checked)
species = 0; // get the egg name.
if (SpeciesName.IsNicknamedAnyLanguage(species, TB_Nickname.Text, Entity.Format))
CHK_Nicknamed.Checked = true;
}
private void UpdateNickname(object sender, EventArgs e)
{
if (sender == Label_Species)
{
switch (ModifierKeys)
{
case Keys.Control: RequestShowdownImport?.Invoke(sender, e); return;
case Keys.Alt: RequestShowdownExport?.Invoke(sender, e); return;
default:
return;
}
}
if (CHK_Nicknamed.Checked)
return;
// Fetch Current Species and set it as Nickname Text
int species = WinFormsUtil.GetIndex(CB_Species);
if ((uint)(species - 1) >= Entity.MaxSpeciesID)
{ TB_Nickname.Text = string.Empty; return; }
if (CHK_IsEgg.Checked)
species = 0; // get the egg name.
// If name is that of another language, don't replace the nickname
if (sender != CB_Language && species != 0 && !SpeciesName.IsNicknamedAnyLanguage(species, TB_Nickname.Text, Entity.Format))
return;
int lang = WinFormsUtil.GetIndex(CB_Language);
TB_Nickname.Text = SpeciesName.GetSpeciesNameGeneration(species, lang, Entity.Format);
if (Entity is GBPKM pk)
pk.SetNotNicknamed();
}
private void UpdateNicknameClick(object sender, MouseEventArgs e)
{
TextBox tb = sender as TextBox ?? TB_Nickname;
// Special Character Form
if (ModifierKeys != Keys.Control)
return;
var sav = RequestSaveFile;
if (tb == TB_Nickname)
{
Entity.Nickname = tb.Text;
var span = Entity.Nickname_Trash;
var d = new TrashEditor(tb, span, sav);
d.ShowDialog();
tb.Text = d.FinalString;
d.FinalBytes.CopyTo(span);
}
else if (tb == TB_OT)
{
Entity.OT_Name = tb.Text;
var span = Entity.OT_Trash;
var d = new TrashEditor(tb, span, sav);
d.ShowDialog();
tb.Text = d.FinalString;
d.FinalBytes.CopyTo(span);
}
else if (tb == TB_HT)
{
Entity.HT_Name = tb.Text;
var span = Entity.HT_Trash;
var d = new TrashEditor(tb, span, sav);
d.ShowDialog();
tb.Text = d.FinalString;
d.FinalBytes.CopyTo(span);
}
}
private void UpdateNotOT(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(TB_HT.Text))
{
ClickGT(GB_OT, EventArgs.Empty); // Switch CT over to OT.
UC_HTGender.Visible = false;
UC_HTGender.Gender = 0;
ReloadToFriendshipTextBox(Entity);
}
else if (!UC_HTGender.Visible)
{
UC_HTGender.Visible = true;
}
}
private void UpdateIsEgg(object sender, EventArgs e)
{
// Display hatch counter if it is an egg, Display Friendship if it is not.
Label_HatchCounter.Visible = CHK_IsEgg.Checked && Entity.Format > 1;
Label_Friendship.Visible = !CHK_IsEgg.Checked && Entity.Format > 1;
if (!FieldsLoaded)
return;
if (Entity.Format == 3 && CHK_IsEgg.Checked)
Entity.OT_Name = TB_OT.Text; // going to be remapped
Entity.IsEgg = CHK_IsEgg.Checked;
if (CHK_IsEgg.Checked)
{
TB_Friendship.Text = EggStateLegality.GetMinimumEggHatchCycles(Entity).ToString();
// If we are an egg, it won't have a met location.
CHK_AsEgg.Checked = true;
GB_EggConditions.Enabled = true;
CAL_MetDate.Value = new DateTime(2000, 01, 01);
// if egg wasn't originally obtained by OT => Link Trade, else => None
if (Entity.Format >= 4)
{
var sav = SaveFileRequested.Invoke(this, e);
bool isTraded = sav.OT != TB_OT.Text || sav.TID != Entity.TID || sav.SID != Entity.SID;
var loc = isTraded ? Locations.TradedEggLocation(sav.Generation, sav.Version) : Locations.GetNoneLocation(sav.Version);
CB_MetLocation.SelectedValue = loc;
}
else if (Entity.Format == 3)
{
CB_Language.SelectedValue = Entity.Language; // JPN
TB_OT.Text = Entity.OT_Name;
}
CHK_Nicknamed.Checked = EggStateLegality.IsNicknameFlagSet(Entity);
TB_Nickname.Text = SpeciesName.GetSpeciesNameGeneration(0, WinFormsUtil.GetIndex(CB_Language), Entity.Format);
// Wipe egg memories
if (Entity.Format >= 6 && ModifyPKM)
Entity.ClearMemories();
}
else // Not Egg
{
if (!CHK_Nicknamed.Checked)
UpdateNickname(this, EventArgs.Empty);
TB_Friendship.Text = Entity.PersonalInfo.BaseFriendship.ToString();
if (CB_EggLocation.SelectedIndex == 0)
{
CAL_MetDate.Value = DateTime.Now;
CAL_EggDate.Value = new DateTime(2000, 01, 01);
CHK_AsEgg.Checked = false;
GB_EggConditions.Enabled = false;
}
else
{
CAL_MetDate.Value = CAL_EggDate.Value;
CB_MetLocation.SelectedValue = EncounterSuggestion.GetSuggestedEggMetLocation(Entity);
}
if (TB_Nickname.Text == SpeciesName.GetSpeciesNameGeneration(0, WinFormsUtil.GetIndex(CB_Language), Entity.Format))
CHK_Nicknamed.Checked = false;
}
UpdateNickname(this, EventArgs.Empty);
UpdateSprite();
}
private void UpdateMetAsEgg(object sender, EventArgs e)
{
GB_EggConditions.Enabled = CHK_AsEgg.Checked;
if (CHK_AsEgg.Checked)
{
if (!FieldsLoaded)
return;
CAL_EggDate.Value = DateTime.Now;
bool isTradedEgg = Entity.IsEgg && Entity.Version != (int)RequestSaveFile.Version;
CB_EggLocation.SelectedValue = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(Entity, isTradedEgg);
return;
}
// Remove egg met data
CHK_IsEgg.Checked = false;
CAL_EggDate.Value = new DateTime(2000, 01, 01);
CB_EggLocation.SelectedValue = Locations.GetNoneLocation((GameVersion)Entity.Version);
UpdateLegality();
}
private void UpdateShinyPID(object sender, EventArgs e)
{
var changePID = Entity.Format >= 3 && (ModifierKeys & Keys.Alt) == 0;
UpdateShiny(changePID);
}
private void UpdateShiny(bool changePID)
{
Entity.PID = Util.GetHexValue(TB_PID.Text);
Entity.Nature = WinFormsUtil.GetIndex(CB_Nature);
Entity.Gender = UC_Gender.Gender;
Entity.Form = CB_Form.SelectedIndex;
Entity.Version = WinFormsUtil.GetIndex(CB_GameOrigin);
if (Entity.Format > 2)
{
var type = (ModifierKeys & ~Keys.Alt) switch
{
Keys.Shift => Shiny.AlwaysSquare,
Keys.Control => Shiny.AlwaysStar,
_ => Shiny.Random,
};
if (changePID)
{
CommonEdits.SetShiny(Entity, type);
TB_PID.Text = Entity.PID.ToString("X8");
int gen = Entity.Generation;
bool pre3DS = gen is 3 or 4 or 5;
if (pre3DS && Entity.Format >= 6)
TB_EC.Text = TB_PID.Text;
}
else
{
Entity.SetShinySID(type);
TID_Trainer.UpdateSID();
}
}
else
{
Entity.SetShiny();
Stats.LoadIVs(Entity.IVs);
Stats.UpdateIVs(this, EventArgs.Empty);
}
UpdateIsShiny();
UpdatePreviewSprite?.Invoke(this, EventArgs.Empty);
UpdateLegality();
}
private void UpdateTSV(object sender, EventArgs e)
{
if (Entity.Format <= 2)
return;
TID_Trainer.UpdateTSV();
Entity.PID = Util.GetHexValue(TB_PID.Text);
var tip = $"PSV: {Entity.PSV:d4}";
if (Entity.IsShiny)
tip += $" | Xor = {Entity.ShinyXor}";
Tip3.SetToolTip(TB_PID, tip);
}
private void Update_ID(object? sender, EventArgs e)
{
if (!FieldsLoaded)
return;
// Trim out nonhex characters
TB_PID.Text = (Entity.PID = Util.GetHexValue(TB_PID.Text)).ToString("X8");
TB_EC.Text = (Entity.EncryptionConstant = Util.GetHexValue(TB_EC.Text)).ToString("X8");
UpdateIsShiny();
UpdateSprite();
Stats.UpdateCharacteristic(); // If the EC is changed, EC%6 (Characteristic) might be changed.
if (Entity.Format <= 4)
{
FieldsLoaded = false;
Entity.PID = Util.GetHexValue(TB_PID.Text);
CB_Nature.SelectedValue = Entity.Nature;
UC_Gender.Gender = Entity.Gender;
UpdateNatureModification(CB_Nature, Entity.Nature);
FieldsLoaded = true;
}
}
private void Update_ID64(object sender, EventArgs e)
{
if (!FieldsLoaded)
return;
// Trim out nonhex characters
if (sender == TB_HomeTracker && Entity is IHomeTrack home)
{
var value = Util.GetHexValue64(TB_HomeTracker.Text);
home.Tracker = value;
TB_HomeTracker.Text = value.ToString("X16");
}
}
private void UpdateShadowID(object sender, EventArgs e)
{
if (!FieldsLoaded)
return;
FLP_Purification.Visible = NUD_ShadowID.Value > 0;
}
private void UpdatePurification(object sender, EventArgs e)
{
if (!FieldsLoaded)
return;
FieldsLoaded = false;
var value = NUD_Purification.Value;
CHK_Shadow.Checked = Entity is CK3 ? value != CK3.Purified : value > 0;
FieldsLoaded = true;
}
private void UpdateShadowCHK(object sender, EventArgs e)
{
if (!FieldsLoaded)
return;
FieldsLoaded = false;
NUD_Purification.Value = CHK_Shadow.Checked ? 1 : Entity is CK3 && NUD_ShadowID.Value != 0 ? CK3.Purified : 0;
((IShadowPKM)Entity).Purification = (int)NUD_Purification.Value;
UpdatePreviewSprite?.Invoke(this, EventArgs.Empty);
FieldsLoaded = true;
}
private void ValidateComboBox(ComboBox cb)
{
if (cb.Text.Length == 0 && cb.Items.Count > 0)
cb.SelectedIndex = 0;
else if (cb.SelectedValue == null)
cb.BackColor = Draw.InvalidSelection;
else
cb.ResetBackColor();
}
private void ValidateComboBox(object sender, CancelEventArgs e)
{
if (sender is not ComboBox cb)
return;
ValidateComboBox(cb);
UpdateSprite();
}
private void ValidateComboBox2(object sender, EventArgs e)
{
if (!FieldsLoaded)
return;
ValidateComboBox(sender, new CancelEventArgs());
if (sender == CB_Ability)
{
if (Entity.Format >= 6)
TB_AbilityNumber.Text = (1 << CB_Ability.SelectedIndex).ToString();
else if (Entity.Format <= 5 && CB_Ability.SelectedIndex < 2) // Format <= 5, not hidden
UpdateRandomPID(sender, e);
UpdateLegality();
}
else if (sender == CB_Nature)
{
if (Entity.Format <= 4)
UpdateRandomPID(sender, e);
Entity.Nature = WinFormsUtil.GetIndex(CB_Nature);
UpdateNatureModification(CB_Nature, Entity.Nature);
Stats.UpdateIVs(sender, EventArgs.Empty); // updating Nature will trigger stats to update as well
UpdateLegality();
}
else if (sender == CB_StatNature)
{
Entity.StatNature = WinFormsUtil.GetIndex(CB_StatNature);
UpdateNatureModification(CB_StatNature, Entity.StatNature);
Stats.UpdateIVs(sender, EventArgs.Empty); // updating Nature will trigger stats to update as well
UpdateLegality();
}
else if (sender == CB_HeldItem)
{
UpdateLegality();
}
}
private void ValidateMove(object sender, EventArgs e)
{
if (!FieldsLoaded || sender is not ComboBox cb)
return;
ValidateComboBox(cb);
// Store value back, repopulate legality.
int value = WinFormsUtil.GetIndex(cb);
int index = Array.IndexOf(Moves, cb);
if (index != -1)
{
UpdatePP(sender, e);
Entity.SetMove(index, value);
}
else if ((index = Array.IndexOf(Relearn, cb)) != -1)
{
Entity.SetRelearnMove(index, value);
}
else if (cb == CB_AlphaMastered && Entity is PA8 pa8)
{
pa8.AlphaMove = (ushort)value;
}
else
{
// Shouldn't hit here.
throw new ArgumentException(nameof(sender));
}
UpdateLegality(skipMoveRepop: true);
}
private void ValidateMovePaint(object sender, DrawItemEventArgs e)
{
if (e.Index < 0)
return;
var (text, value) = (ComboItem)((ComboBox)sender).Items[e.Index];
var valid = LegalMoveSource.Info.CanLearn(value) && !HaX;
var current = (e.State & DrawItemState.Selected) != 0;
var brush = Draw.Brushes.GetBackground(valid, current);
var textColor = Draw.GetText(current);
DrawMoveRectangle(e, brush, text, textColor);
}
private static void DrawMoveRectangle(DrawItemEventArgs e, Brush brush, string text, Color textColor)
{
var rec = new Rectangle(e.Bounds.X - 1, e.Bounds.Y, e.Bounds.Width + 1, e.Bounds.Height + 0); // 1px left
e.Graphics.FillRectangle(brush, rec);
const TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | TextFormatFlags.ExpandTabs | TextFormatFlags.SingleLine;
TextRenderer.DrawText(e.Graphics, text, e.Font, rec, textColor, flags);
}
private void MeasureDropDownHeight(object sender, MeasureItemEventArgs e) => e.ItemHeight = CB_RelearnMove1.ItemHeight;
private void ValidateMoveDropDown(object sender, EventArgs e)
{
var s = (ComboBox) sender;
var index = Array.IndexOf(Moves, s);
// Populating the combobox drop-down list is deferred until the dropdown is entered into at least once.
// Saves some lag delays when viewing a pkm.
if (LegalMoveSource.Display.GetIsMoveBoxOrdered(index))
return;
SetMoveDataSource(s);
LegalMoveSource.Display.SetIsMoveBoxOrdered(index, true);
}
private void SetMoveDataSource(ComboBox c)
{
var index = WinFormsUtil.GetIndex(c);
c.DataSource = new BindingSource(LegalMoveSource.Display.DataSource, null);
c.SelectedValue = index;
}
private void ValidateLocation(object sender, EventArgs e)
{
if (!FieldsLoaded)
return;
ValidateComboBox((ComboBox)sender);
Entity.Met_Location = WinFormsUtil.GetIndex(CB_MetLocation);
Entity.Egg_Location = WinFormsUtil.GetIndex(CB_EggLocation);
UpdateLegality();
}
// Secondary Windows for Ribbons/Amie/Memories
private void OpenRibbons(object sender, EventArgs e)
{
using var form = new RibbonEditor(Entity);
form.ShowDialog();
}
private void OpenMedals(object sender, EventArgs e)
{
using var form = new SuperTrainingEditor(Entity);
form.ShowDialog();
}
private void OpenHistory(object sender, EventArgs e)
{
// Write back current values
Entity.HT_Name = TB_HT.Text;
Entity.OT_Name = TB_OT.Text;
Entity.IsEgg = CHK_IsEgg.Checked;
UpdateFromFriendshipTextBox(Entity, Util.ToInt32(TB_Friendship.Text));
using var form = new MemoryAmie(Entity);
form.ShowDialog();
ReloadToFriendshipTextBox(Entity);
}
private void B_Records_Click(object sender, EventArgs e)
{
if (Entity is not ITechRecord8 t)
return;
if (ModifierKeys == Keys.Shift)
{
t.SetRecordFlags(Entity.Moves);
UpdateLegality();
return;
}
using var form = new TechRecordEditor(t, Entity);
form.ShowDialog();
UpdateLegality();
}
private void B_MoveShop_Click(object sender, EventArgs e)
{
if (Entity is not IMoveShop8Mastery m)
return;
if (ModifierKeys == Keys.Shift)
{
m.ClearMoveShopFlags();
m.SetMoveShopFlags(Entity.Moves);
m.SetMoveShopFlagsMastered();
UpdateLegality();
return;
}
using var form = new MoveShopEditor(m, m, Entity);
form.ShowDialog();
UpdateLegality();
}
/// <summary>
/// Refreshes the interface for the current PKM format.
/// </summary>
/// <param name="sav">Save File context the editor is editing for</param>
/// <param name="pk">Pokémon data to edit</param>
public bool ToggleInterface(SaveFile sav, PKM pk)
{
Entity = sav.GetCompatiblePKM(pk);
ToggleInterface(Entity);
return FinalizeInterface(sav);
}
private void ToggleInterface(PKM t)
{
var pb7 = t is PB7;
int gen = t.Format;
FLP_Purification.Visible = FLP_ShadowID.Visible = t is IShadowPKM;
bool sizeCP = gen >= 8 || pb7;
FLP_SizeCP.Visible = sizeCP;
if (sizeCP)
SizeCP.ToggleVisibility(t);
PB_Favorite.Visible = t is IFavorite;
PB_BattleVersion.Visible = FLP_BattleVersion.Visible = t is IBattleVersion;
BTN_History.Visible = gen >= 6 && !pb7;
BTN_Ribbons.Visible = gen >= 3 && !pb7;
BTN_Medals.Visible = gen is 6 or 7 && !pb7;
FLP_Country.Visible = FLP_SubRegion.Visible = FLP_3DSRegion.Visible = t is IRegionOrigin;
FLP_OriginalNature.Visible = gen >= 8;
B_Records.Visible = t is ITechRecord8;
B_MoveShop.Visible = t is IMoveShop8Mastery;
CB_HTLanguage.Visible = gen >= 8;
L_AlphaMastered.Visible = CB_AlphaMastered.Visible = t is PA8;
ToggleInterface(Entity.Format);
}
private void ToggleSecrets(bool hidden, int gen)
{
Label_EncryptionConstant.Visible = BTN_RerollEC.Visible = TB_EC.Visible = gen >= 6 && !hidden;
BTN_RerollPID.Visible = Label_PID.Visible = TB_PID.Visible = gen >= 3 && !hidden;
TB_HomeTracker.Visible = L_HomeTracker.Visible = gen >= 8 && !hidden;
}
private void ToggleInterface(int gen)
{
ToggleSecrets(HideSecretValues, gen);
GB_nOT.Visible = GB_RelearnMoves.Visible = gen >= 6;
PB_Origin.Visible = gen >= 6;
FLP_NSparkle.Visible = L_NSparkle.Visible = CHK_NSparkle.Visible = gen == 5;
CHK_AsEgg.Visible = GB_EggConditions.Visible = PB_Mark5.Visible = PB_Mark6.Visible = gen >= 4;
FLP_ShinyLeaf.Visible = L_ShinyLeaf.Visible = ShinyLeaf.Visible = gen == 4;
DEV_Ability.Enabled = DEV_Ability.Visible = gen > 3 && HaX;
CB_Ability.Visible = !DEV_Ability.Enabled && gen >= 3;
FLP_Nature.Visible = gen >= 3;
FLP_Ability.Visible = gen >= 3;
GB_ExtraBytes.Visible = GB_ExtraBytes.Enabled = gen >= 3;
GB_Markings.Visible = gen >= 3;
CB_Form.Enabled = gen >= 3;
FA_Form.Visible = gen >= 6;
FLP_Friendship.Visible = FLP_Form.Visible = gen >= 2;
FLP_HeldItem.Visible = gen >= 2;
CHK_IsEgg.Visible = gen >= 2;
FLP_PKRS.Visible = FLP_EggPKRSRight.Visible = gen >= 2;
UC_Gender.Visible = gen >= 2;
FLP_CatchRate.Visible = gen == 1;
// HaX override, needs to be after DEV_Ability enabled assignment.
TB_AbilityNumber.Visible = gen >= 6 && DEV_Ability.Enabled;
// Met Tab
L_HomeTracker.Visible = TB_HomeTracker.Visible = gen >= 8;
FLP_MetDate.Visible = gen >= 4;
FLP_Fateful.Visible = FLP_Ball.Visible = FLP_OriginGame.Visible = gen >= 3;
FLP_MetLocation.Visible = FLP_MetLevel.Visible = gen >= 2;
FLP_GroundTile.Visible = gen is 4 or 5 or 6;
FLP_TimeOfDay.Visible = gen == 2;
Contest.ToggleInterface(Entity, gen);
Stats.ToggleInterface(Entity, gen);
CenterSubEditors();
}
private bool FinalizeInterface(SaveFile sav)
{
FieldsLoaded = false;
bool TranslationRequired = false;
PopulateFilteredDataSources(sav);
PopulateFields(Entity);
// Save File Specific Limits
TB_OT.MaxLength = Entity.OTLength;
TB_HT.MaxLength = Entity.OTLength;
TB_Nickname.MaxLength = Entity.NickLength;
// Hide Unused Tabs
if (Entity.Format == 1 && tabMain.TabPages.Contains(Tab_Met))
{
tabMain.TabPages.Remove(Tab_Met);
}
else if (Entity.Format != 1 && !tabMain.TabPages.Contains(Tab_Met))
{
tabMain.TabPages.Insert(1, Tab_Met);
TranslationRequired = true;
}
if (!HaX && sav is SAV7b)
{
FLP_HeldItem.Visible = false;
FLP_Country.Visible = false;
FLP_SubRegion.Visible = false;
FLP_3DSRegion.Visible = false;
}
if (!HaX && sav is SAV8LA)
{
FLP_HeldItem.Visible = false;
}
// Common HaX Interface
MT_Level.Enabled = MT_Level.Visible = HaX;
TB_Level.Visible = !HaX;
// pk2 save files do not have an Origin Game stored. Prompt the met location list to update.
if (Entity.Format == 2)
CheckMetLocationChange(GameVersion.C, Entity.Format);
return TranslationRequired;
}
private void CenterSubEditors()
{
// Recenter PKM SubEditors
var firstTabArea = tabMain.TabPages[0]; // first is always initialized
FLP_PKMEditors.CenterWithin(firstTabArea);
FLP_MoveFlags.CenterWithin(firstTabArea);
}
public void EnableDragDrop(DragEventHandler enter, DragEventHandler drop)
{
AllowDrop = true;
DragDrop += drop;
foreach (var tab in tabMain.TabPages.OfType<TabPage>())
{
tab.AllowDrop = true;
tab.DragEnter += enter;
tab.DragDrop += drop;
}
}
// ReSharper disable once FieldCanBeMadeReadOnly.Global
public Action<IBattleTemplate> LoadShowdownSet;
private void LoadShowdownSetDefault(IBattleTemplate Set)
{
var pk = PreparePKM();
pk.ApplySetDetails(Set);
PopulateFields(pk);
}
private void CB_BattleVersion_SelectedValueChanged(object sender, EventArgs e)
{
if (Entity is not IBattleVersion b)
return;
var value = (byte)WinFormsUtil.GetIndex(CB_BattleVersion);
b.BattleVersion = value;
PB_BattleVersion.Image = GetMarkSprite(PB_BattleVersion, value != 0);
}
private static Image GetMarkSprite(PictureBox p, bool opaque, double trans = 0.175)
{
var sprite = p.InitialImage;
return opaque ? sprite : ImageUtil.ChangeOpacity(sprite, trans);
}
private void ClickVersionMarking(object sender, EventArgs e)
{
tabMain.SelectedTab = Tab_Met;
if (sender == PB_BattleVersion)
CB_BattleVersion.DroppedDown = true;
else
CB_GameOrigin.DroppedDown = true;
}
public void ChangeLanguage(ITrainerInfo sav, PKM pk)
{
// Force an update to the met locations
origintrack = GameVersion.Invalid;
InitializeLanguage(sav);
CenterSubEditors();
}
public void FlickerInterface()
{
tabMain.SelectedTab = Tab_Met; // parent tab of CB_GameOrigin
tabMain.SelectedTab = Tab_Main; // first tab
}
private void InitializeLanguage(ITrainerInfo sav)
{
var source = GameInfo.FilteredSources;
// Set the various ComboBox DataSources up with their allowed entries
SetCountrySubRegion(CB_Country, "countries");
CB_3DSReg.DataSource = source.ConsoleRegions;
CB_GroundTile.DataSource = new BindingSource(source.G4GroundTiles, null);
CB_Nature.DataSource = new BindingSource(source.Natures, null);
CB_StatNature.DataSource = new BindingSource(source.Natures, null);
// Sub editors
Stats.InitializeDataSources();
PopulateFilteredDataSources(sav, true);
}
private static void SetIfDifferentCount(IReadOnlyCollection<ComboItem> update, ComboBox exist, bool force = false)
{
if (!force && (exist.DataSource is BindingSource b && b.Count == update.Count))
return;
exist.DataSource = new BindingSource(update, null);
}
private void PopulateFilteredDataSources(ITrainerInfo sav, bool force = false)
{
var source = GameInfo.FilteredSources;
SetIfDifferentCount(source.Languages, CB_Language, force);
if (sav.Generation >= 2)
{
var game = (GameVersion) sav.Game;
if (game <= 0)
game = GameUtil.GetVersion(sav.Generation);
CheckMetLocationChange(game, sav.Generation);
SetIfDifferentCount(source.Items, CB_HeldItem, force);
}
if (sav.Generation >= 3)
{
SetIfDifferentCount(source.Balls, CB_Ball, force);
SetIfDifferentCount(source.Games, CB_GameOrigin, force);
}
if (sav.Generation >= 4)
SetIfDifferentCount(source.Abilities, DEV_Ability, force);
if (sav.Generation >= 8)
{
var lang = source.Languages;
var langWith0 = new List<ComboItem>(1 + lang.Count) {GameInfo.Sources.Empty};
langWith0.AddRange(lang);
SetIfDifferentCount(langWith0, CB_HTLanguage, force);
var game = source.Games;
var gamesWith0 = new List<ComboItem>(1 + game.Count) {GameInfo.Sources.Empty};
gamesWith0.AddRange(game);
SetIfDifferentCount(gamesWith0, CB_BattleVersion, force);
}
SetIfDifferentCount(source.Species, CB_Species, force);
// Set the Move ComboBoxes too..
LegalMoveSource.ChangeMoveSource(source.Moves);
foreach (var cb in Moves.Concat(Relearn))
SetIfDifferentCount(source.Moves, cb, force);
if (sav is SAV8LA)
SetIfDifferentCount(source.Moves, CB_AlphaMastered, force);
}
}
}