PKHeX/PKHeX.WinForms/Subforms/SAV_Encounters.cs

330 lines
12 KiB
C#
Raw Normal View History

using PKHeX.Core;
using PKHeX.Core.Searching;
using PKHeX.WinForms.Controls;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using PKHeX.Drawing;
using PKHeX.WinForms.Properties;
using static PKHeX.Core.MessageStrings;
namespace PKHeX.WinForms
{
public partial class SAV_Encounters : Form
{
private readonly PKMEditor PKME_Tabs;
private SaveFile SAV => PKME_Tabs.RequestSaveFile;
public SAV_Encounters(PKMEditor f1)
{
InitializeComponent();
PKME_Tabs = f1;
var grid = EncounterPokeGrid;
var smallWidth = grid.Width;
var smallHeight = grid.Height;
grid.InitializeGrid(6, 11, SpriteUtil.Spriter);
grid.SetBackground(Resources.box_wp_clean);
var newWidth = grid.Width;
var newHeight = grid.Height;
var wdelta = newWidth - smallWidth;
if (wdelta != 0)
Width += wdelta;
var hdelta = newHeight - smallHeight;
if (hdelta != 0)
Height += hdelta;
PKXBOXES = grid.Entries.ToArray();
// Enable Scrolling when hovered over
foreach (var slot in PKXBOXES)
{
// Enable Click
slot.MouseClick += (sender, e) =>
{
if (ModifierKeys == Keys.Control)
ClickView(sender, e);
};
slot.ContextMenuStrip = mnu;
}
Counter = L_Count.Text;
L_Viewed.Text = string.Empty; // invis for now
L_Viewed.MouseEnter += (sender, e) => hover.SetToolTip(L_Viewed, L_Viewed.Text);
PopulateComboBoxes();
WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
GetTypeFilters();
// Load Data
L_Count.Text = "Ready...";
CenterToParent();
}
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
private void GetTypeFilters()
{
var types = (EncounterOrder[])Enum.GetValues(typeof(EncounterOrder));
var checks = types.Select(z => new CheckBox
{
Name = z.ToString(),
Text = z.ToString(),
AutoSize = true,
Checked = true,
Padding = Padding.Empty,
Margin = Padding.Empty,
}).ToArray();
foreach (var chk in checks)
{
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
TypeFilters.Controls.Add(chk);
TypeFilters.SetFlowBreak(chk, true);
}
}
private EncounterOrder[] GetTypes()
{
return TypeFilters.Controls.OfType<CheckBox>().Where(z => z.Checked).Select(z => z.Name)
.Select(z => (EncounterOrder)Enum.Parse(typeof(EncounterOrder), z)).ToArray();
}
private readonly PictureBox[] PKXBOXES;
private List<IEncounterable> Results;
private int slotSelected = -1; // = null;
private Image slotColor;
private const int RES_MAX = 66;
private const int RES_MIN = 6;
private readonly string Counter;
// Important Events
private void ClickView(object sender, EventArgs e)
{
var pb = WinFormsUtil.GetUnderlyingControl<PictureBox>(sender);
int index = Array.IndexOf(PKXBOXES, pb);
if (index >= RES_MAX)
{
System.Media.SystemSounds.Exclamation.Play();
return;
}
index += SCR_Box.Value * RES_MIN;
if (index >= Results.Count)
{
System.Media.SystemSounds.Exclamation.Play();
return;
}
var enc = Results[index];
var pk = enc.ConvertToPKM(SAV);
pk.RefreshChecksum();
PKME_Tabs.PopulateFields(pk, false);
slotSelected = index;
2019-11-16 01:34:18 +00:00
slotColor = SpriteUtil.Spriter.View;
FillPKXBoxes(SCR_Box.Value);
}
private void PopulateComboBoxes()
{
// Set the Text
CB_Species.InitializeBinding();
CB_GameOrigin.InitializeBinding();
var Any = new ComboItem(MsgAny, -1);
var DS_Species = new List<ComboItem>(GameInfo.SpeciesDataSource);
DS_Species.RemoveAt(0); DS_Species.Insert(0, Any); CB_Species.DataSource = DS_Species;
// Set the Move ComboBoxes too..
var DS_Move = new List<ComboItem>(GameInfo.MoveDataSource);
DS_Move.RemoveAt(0); DS_Move.Insert(0, Any);
{
foreach (ComboBox cb in new[] { CB_Move1, CB_Move2, CB_Move3, CB_Move4 })
{
cb.InitializeBinding();
cb.DataSource = new BindingSource(DS_Move, null);
}
}
var DS_Version = new List<ComboItem>(GameInfo.VersionDataSource);
DS_Version.Insert(0, Any); CB_GameOrigin.DataSource = DS_Version;
// Trigger a Reset
2019-02-02 18:19:41 +00:00
ResetFilters(null, EventArgs.Empty);
}
private void ResetFilters(object sender, EventArgs e)
{
CB_Species.SelectedIndex = 0;
CB_Move1.SelectedIndex = CB_Move2.SelectedIndex = CB_Move3.SelectedIndex = CB_Move4.SelectedIndex = 0;
CB_GameOrigin.SelectedIndex = 0;
RTB_Instructions.Clear();
2019-01-01 07:39:08 +00:00
if (sender == null)
return; // still starting up
foreach (var chk in TypeFilters.Controls.OfType<CheckBox>())
chk.Checked = true;
2019-01-01 07:39:08 +00:00
System.Media.SystemSounds.Asterisk.Play();
}
// View Updates
private IEnumerable<IEncounterable> SearchDatabase()
{
var settings = GetSearchSettings();
var moves = settings.Moves.ToArray();
// If nothing is specified, instead of just returning all possible encounters, just return nothing.
if (settings.Species <= 0 && moves.Length == 0)
return Array.Empty<IEncounterable>();
var pk = SAV.BlankPKM;
var species = settings.Species <= 0 ? Enumerable.Range(1, SAV.MaxSpeciesID) : new[] { settings.Species };
var versions = settings.GetVersions(SAV);
var results = species.SelectMany(z => GetEncounters(z, moves, pk, versions));
if (settings.SearchEgg != null)
results = results.Where(z => z.EggEncounter == settings.SearchEgg);
// return filtered results
var comparer = new ReferenceComparer<IEncounterable>();
results = results.Distinct(comparer); // only distinct objects
// when all sprites in new size are available, remove this filter
results = SAV is SAV8SWSH
? results.Where(z => ((PersonalInfoSWSH)PersonalTable.SWSH.GetFormeEntry(z.Species, z.Form)).IsPresentInGame)
: results.Where(z => !(z is IGeneration g) || g.Generation <= 7);
return results;
}
private class ReferenceComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y) => RuntimeHelpers.GetHashCode(x).Equals(RuntimeHelpers.GetHashCode(y));
public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj);
}
private static IEnumerable<IEncounterable> GetEncounters(int species, int[] moves, PKM pk, IReadOnlyList<GameVersion> vers)
{
pk.Species = species;
return EncounterMovesetGenerator.GenerateEncounters(pk, moves, vers);
}
private SearchSettings GetSearchSettings()
{
var settings = new SearchSettings
{
Format = SAV.Generation, // 0->(n-1) => 1->n
Generation = SAV.Generation,
Species = WinFormsUtil.GetIndex(CB_Species),
BatchInstructions = RTB_Instructions.Lines,
Version = WinFormsUtil.GetIndex(CB_GameOrigin),
};
settings.AddMove(WinFormsUtil.GetIndex(CB_Move1));
settings.AddMove(WinFormsUtil.GetIndex(CB_Move2));
settings.AddMove(WinFormsUtil.GetIndex(CB_Move3));
settings.AddMove(WinFormsUtil.GetIndex(CB_Move4));
if (CHK_IsEgg.CheckState != CheckState.Indeterminate)
settings.SearchEgg = CHK_IsEgg.CheckState == CheckState.Checked;
return settings;
}
private async void B_Search_Click(object sender, EventArgs e)
{
B_Search.Enabled = false;
EncounterMovesetGenerator.PriorityList = GetTypes();
var search = SearchDatabase();
var results = await Task.Run(() => search.ToList()).ConfigureAwait(true);
if (results.Count == 0)
WinFormsUtil.Alert(MsgDBSearchNone);
SetResults(results); // updates Count Label as well.
System.Media.SystemSounds.Asterisk.Play();
B_Search.Enabled = true;
EncounterMovesetGenerator.ResetFilters();
}
private void UpdateScroll(object sender, ScrollEventArgs e)
{
if (e.OldValue != e.NewValue)
FillPKXBoxes(e.NewValue);
}
private void SetResults(List<IEncounterable> res)
{
Results = res;
SCR_Box.Maximum = (int)Math.Ceiling((decimal)Results.Count / RES_MIN);
if (SCR_Box.Maximum > 0) SCR_Box.Maximum--;
slotSelected = -1; // reset the slot last viewed
SCR_Box.Value = 0;
FillPKXBoxes(0);
L_Count.Text = string.Format(Counter, Results.Count);
B_Search.Enabled = true;
}
private void FillPKXBoxes(int start)
{
if (Results == null)
{
for (int i = 0; i < RES_MAX; i++)
PKXBOXES[i].Image = null;
return;
}
int begin = start*RES_MIN;
int end = Math.Min(RES_MAX, Results.Count - begin);
for (int i = 0; i < end; i++)
{
var enc = Results[i + begin];
2018-12-02 18:44:06 +00:00
var form = GetForm(enc);
PKXBOXES[i].Image = SpriteUtil.GetSprite(enc.Species, form, 0, 0, 0, enc.EggEncounter, false, enc is IGeneration g ? g.Generation : -1);
}
for (int i = end; i < RES_MAX; i++)
PKXBOXES[i].Image = null;
for (int i = 0; i < RES_MAX; i++)
2019-11-16 01:34:18 +00:00
PKXBOXES[i].BackgroundImage = SpriteUtil.Spriter.Transparent;
if (slotSelected != -1 && slotSelected >= begin && slotSelected < begin + RES_MAX)
2019-11-16 01:34:18 +00:00
PKXBOXES[slotSelected - begin].BackgroundImage = slotColor ?? SpriteUtil.Spriter.View;
}
2018-12-02 18:44:06 +00:00
private static int GetForm(IEncounterable enc)
{
return enc switch
2018-12-02 18:44:06 +00:00
{
EncounterSlot s => s.Form,
EncounterStatic s => s.Form,
MysteryGift m => m.Form,
EncounterTrade t => t.Form,
_ => 0
};
2018-12-02 18:44:06 +00:00
}
private void Menu_SearchAdvanced_Click(object sender, EventArgs e)
{
2019-02-02 18:19:41 +00:00
// todo
}
private void Menu_Exit_Click(object sender, EventArgs e) => Close();
protected override void OnMouseWheel(MouseEventArgs e)
{
2019-11-16 01:34:18 +00:00
if (!EncounterPokeGrid.RectangleToScreen(EncounterPokeGrid.ClientRectangle).Contains(MousePosition))
return;
int oldval = SCR_Box.Value;
int newval = oldval + (e.Delta < 0 ? 1 : -1);
if (newval >= SCR_Box.Minimum && SCR_Box.Maximum >= newval)
FillPKXBoxes(SCR_Box.Value = newval);
}
}
}