mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-23 20:43:07 +00:00
2d774ac7cc
Make extrabytes a pkm property (don't mutate array pls) reconfigure startup loading to only initialize after initial load of sav & pkm (using blanks if not provided)
595 lines
21 KiB
C#
595 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Media;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using PKHeX.Core;
|
|
using PKHeX.WinForms.Properties;
|
|
|
|
namespace PKHeX.WinForms.Controls
|
|
{
|
|
/// <summary>
|
|
/// Manager class for moving slots.
|
|
/// </summary>
|
|
public sealed class SlotChangeManager : IDisposable
|
|
{
|
|
// Disposeables
|
|
public readonly SAVEditor SE;
|
|
private Image OriginalBackground;
|
|
private Image CurrentBackground;
|
|
|
|
public Image ColorizedColor { get; private set; }
|
|
public int ColorizedBox { get; private set; } = -1;
|
|
public int ColorizedSlot { get; private set; } = -1;
|
|
|
|
public bool GlowHover { get; set; } = true;
|
|
public Color GlowInitial { get; set; } = Color.White;
|
|
public Color GlowFinal { get; set; } = Color.LightSkyBlue;
|
|
public readonly BitmapAnimator HoverWorker;
|
|
|
|
private SaveFile SAV => SE.SAV;
|
|
public SlotChangeInfo DragInfo;
|
|
public readonly List<BoxEditor> Boxes = new List<BoxEditor>();
|
|
public readonly List<ISlotViewer<PictureBox>> OtherSlots = new List<ISlotViewer<PictureBox>>();
|
|
public event DragEventHandler RequestExternalDragDrop;
|
|
private readonly ToolTip ShowSet = new ToolTip {InitialDelay = 200, IsBalloon = false};
|
|
private readonly SoundPlayer Sounds = new SoundPlayer();
|
|
private PictureBox HoveredSlot;
|
|
|
|
public SlotChangeManager(SAVEditor se)
|
|
{
|
|
HoverWorker = new BitmapAnimator(Resources.slotHover);
|
|
SE = se;
|
|
}
|
|
|
|
public void Reset() { DragInfo = new SlotChangeInfo(SAV); ColorizedBox = ColorizedSlot = -1; }
|
|
public bool CanStartDrag => DragInfo.LeftMouseIsDown && !Cursor.Position.Equals(MouseDownPosition);
|
|
private Point MouseDownPosition { get; set; }
|
|
|
|
public void SetCursor(Cursor z, object sender)
|
|
{
|
|
if (SE != null)
|
|
DragInfo.Cursor = ((Control)sender).FindForm().Cursor = z;
|
|
}
|
|
|
|
public void MouseEnter(object sender, EventArgs e)
|
|
{
|
|
var pb = (PictureBox)sender;
|
|
if (pb.Image == null)
|
|
return;
|
|
BeginHoverSlot(pb);
|
|
}
|
|
|
|
private void BeginHoverSlot(PictureBox pb)
|
|
{
|
|
var view = WinFormsUtil.FindFirstControlOfType<ISlotViewer<PictureBox>>(pb);
|
|
var data = view.GetSlotData(pb);
|
|
var pk = SAV.GetStoredSlot(data.Offset);
|
|
HoveredSlot = pb;
|
|
|
|
OriginalBackground = pb.BackgroundImage;
|
|
|
|
Bitmap hover;
|
|
if (GlowHover)
|
|
{
|
|
HoverWorker.Stop();
|
|
|
|
var bgr = new[] { GlowInitial.B, GlowInitial.G, GlowInitial.R };
|
|
SpriteUtil.GetSpriteGlow(pk, bgr, out var glowdata, out var GlowBase);
|
|
hover = ImageUtil.LayerImage(GlowBase, Resources.slotHover, 0, 0);
|
|
HoverWorker.GlowToColor = GlowFinal;
|
|
HoverWorker.GlowFromColor = GlowInitial;
|
|
HoverWorker.Start(pb, GlowBase, glowdata, OriginalBackground);
|
|
}
|
|
else
|
|
{
|
|
hover = Resources.slotHover;
|
|
}
|
|
|
|
pb.BackgroundImage = CurrentBackground = OriginalBackground == null ? hover : ImageUtil.LayerImage(OriginalBackground, hover, 0, 0);
|
|
|
|
if (Settings.Default.HoverSlotShowText)
|
|
ShowSimulatorSetTooltip(pb, pk);
|
|
if (Settings.Default.HoverSlotPlayCry)
|
|
PlayCry(pk);
|
|
}
|
|
|
|
private void EndHoverSlot()
|
|
{
|
|
if (HoveredSlot != null)
|
|
HoverCancel();
|
|
ShowSet.RemoveAll();
|
|
Sounds.Stop();
|
|
}
|
|
|
|
public void HoverCancel()
|
|
{
|
|
HoverWorker.Stop();
|
|
HoveredSlot = null;
|
|
}
|
|
|
|
public void RefreshHoverSlot(ISlotViewer<PictureBox> parent)
|
|
{
|
|
if (HoveredSlot == null || !parent.SlotPictureBoxes.Contains(HoveredSlot))
|
|
return;
|
|
|
|
BeginHoverSlot(HoveredSlot);
|
|
}
|
|
|
|
public void MouseLeave(object sender, EventArgs e)
|
|
{
|
|
EndHoverSlot();
|
|
var pb = (PictureBox)sender;
|
|
if (pb.BackgroundImage != CurrentBackground)
|
|
return;
|
|
pb.BackgroundImage = OriginalBackground;
|
|
}
|
|
|
|
public void MouseClick(object sender, MouseEventArgs e)
|
|
{
|
|
if (!DragInfo.DragDropInProgress)
|
|
SE.ClickSlot(sender, e);
|
|
}
|
|
|
|
public void MouseUp(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left)
|
|
DragInfo.LeftMouseIsDown = false;
|
|
if (e.Button == MouseButtons.Right)
|
|
DragInfo.RightMouseIsDown = false;
|
|
}
|
|
|
|
public void MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left)
|
|
{
|
|
DragInfo.LeftMouseIsDown = true;
|
|
MouseDownPosition = Cursor.Position;
|
|
}
|
|
if (e.Button == MouseButtons.Right)
|
|
DragInfo.RightMouseIsDown = true;
|
|
}
|
|
|
|
public void QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
|
|
{
|
|
if (e.Action != DragAction.Cancel && e.Action != DragAction.Drop)
|
|
return;
|
|
DragInfo.LeftMouseIsDown = false;
|
|
DragInfo.RightMouseIsDown = false;
|
|
DragInfo.DragDropInProgress = false;
|
|
}
|
|
|
|
public void DragEnter(object sender, DragEventArgs e)
|
|
{
|
|
if (e.AllowedEffect == (DragDropEffects.Copy | DragDropEffects.Link)) // external file
|
|
e.Effect = DragDropEffects.Copy;
|
|
else if (e.Data != null) // within
|
|
e.Effect = DragDropEffects.Move;
|
|
|
|
if (DragInfo.DragDropInProgress)
|
|
SetCursor((Cursor)DragInfo.Cursor, sender);
|
|
}
|
|
|
|
public void MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (!CanStartDrag)
|
|
return;
|
|
|
|
// Abort if there is no Pokemon in the given slot.
|
|
PictureBox pb = (PictureBox)sender;
|
|
if (pb.Image == null)
|
|
return;
|
|
var view = WinFormsUtil.FindFirstControlOfType<ISlotViewer<PictureBox>>(pb);
|
|
var src = view.GetSlotData(pb);
|
|
if (!src.Editable || SAV.IsSlotLocked(src.Box, src.Slot))
|
|
return;
|
|
bool encrypt = Control.ModifierKeys == Keys.Control;
|
|
HandleMovePKM(pb, encrypt);
|
|
}
|
|
|
|
public void DragDrop(object sender, DragEventArgs e)
|
|
{
|
|
PictureBox pb = (PictureBox)sender;
|
|
var view = WinFormsUtil.FindFirstControlOfType<ISlotViewer<PictureBox>>(pb);
|
|
var src = view.GetSlotData(pb);
|
|
if (!src.Editable || SAV.IsSlotLocked(src.Box, src.Slot))
|
|
{
|
|
SystemSounds.Asterisk.Play();
|
|
e.Effect = DragDropEffects.Copy;
|
|
DragInfo.Reset();
|
|
return;
|
|
}
|
|
|
|
bool overwrite = Control.ModifierKeys == Keys.Alt;
|
|
bool clone = Control.ModifierKeys == Keys.Control;
|
|
DragInfo.Destination = src;
|
|
HandleDropPKM(sender, e, overwrite, clone);
|
|
}
|
|
|
|
private void ShowSimulatorSetTooltip(Control pb, PKM pk)
|
|
{
|
|
if (pk.Species == 0)
|
|
{
|
|
ShowSet.RemoveAll();
|
|
return;
|
|
}
|
|
var text = GetLocalizedPreviewText(pk, Settings.Default.Language);
|
|
ShowSet.SetToolTip(pb, text);
|
|
}
|
|
|
|
private void PlayCry(PKM pk)
|
|
{
|
|
if (pk.Species == 0)
|
|
return;
|
|
|
|
string path = GetCryPath(pk, Main.CryPath);
|
|
if (!File.Exists(path))
|
|
return;
|
|
|
|
Sounds.SoundLocation = path;
|
|
try { Sounds.Play(); }
|
|
catch { }
|
|
}
|
|
|
|
private static ISlotViewer<T> GetViewParent<T>(T pb) where T : Control
|
|
=> WinFormsUtil.FindFirstControlOfType<ISlotViewer<T>>(pb);
|
|
|
|
public void HandleMovePKM(PictureBox pb, bool encrypt)
|
|
{
|
|
// Create a temporary PKM file to perform a drag drop operation.
|
|
|
|
// Set flag to prevent re-entering.
|
|
DragInfo.DragDropInProgress = true;
|
|
|
|
// Prepare Data
|
|
DragInfo.Source = GetViewParent(pb).GetSlotData(pb);
|
|
DragInfo.Source.OriginalData = SAV.GetData(DragInfo.Source.Offset, SAV.SIZE_STORED);
|
|
|
|
// Make a new file name based off the PID
|
|
string newfile = CreateDragDropPKM(pb, encrypt, out bool external);
|
|
DragInfo.Reset();
|
|
SetCursor(SE.GetDefaultCursor, pb);
|
|
|
|
// Browser apps need time to load data since the file isn't moved to a location on the user's local storage.
|
|
// Tested 10ms -> too quick, 100ms was fine. 500ms should be safe?
|
|
// Keep it to 10 seconds; Discord upload only stores the file path until you click Upload.
|
|
int delay = external ? 10_000 : 0;
|
|
DeleteAsync(newfile, delay);
|
|
if (DragInfo.Source.IsParty || DragInfo.Destination.IsParty)
|
|
SE.SetParty();
|
|
}
|
|
|
|
private async void DeleteAsync(string path, int delay)
|
|
{
|
|
await Task.Delay(delay).ConfigureAwait(true);
|
|
if (File.Exists(path) && DragInfo.CurrentPath == null)
|
|
File.Delete(path);
|
|
}
|
|
|
|
private string CreateDragDropPKM(PictureBox pb, bool encrypt, out bool external)
|
|
{
|
|
byte[] dragdata = SAV.DecryptPKM(DragInfo.Source.OriginalData);
|
|
Array.Resize(ref dragdata, SAV.SIZE_STORED);
|
|
|
|
// Make File
|
|
PKM pkx = SAV.GetPKM(dragdata);
|
|
string newfile = FileUtil.GetPKMTempFileName(pkx, encrypt);
|
|
try
|
|
{
|
|
TryMakeDragDropPKM(pb, encrypt, pkx, newfile, out external);
|
|
}
|
|
catch (Exception x)
|
|
{
|
|
WinFormsUtil.Error("Drag & Drop Error", x);
|
|
external = false;
|
|
}
|
|
|
|
return newfile;
|
|
}
|
|
|
|
private bool TryMakeDragDropPKM(PictureBox pb, bool encrypt, PKM pkx, string newfile, out bool external)
|
|
{
|
|
File.WriteAllBytes(newfile, encrypt ? pkx.EncryptedBoxData : pkx.DecryptedBoxData);
|
|
var img = (Bitmap)pb.Image;
|
|
SetCursor(new Cursor(img.GetHicon()), pb);
|
|
HoverCancel();
|
|
pb.Image = null;
|
|
pb.BackgroundImage = Resources.slotDrag;
|
|
// Thread Blocks on DoDragDrop
|
|
DragInfo.CurrentPath = newfile;
|
|
DragDropEffects result = pb.DoDragDrop(new DataObject(DataFormats.FileDrop, new[] { newfile }), DragDropEffects.Move);
|
|
external = !DragInfo.Source.IsValid || result != DragDropEffects.Link;
|
|
if (external || DragInfo.SameSlot || result != DragDropEffects.Link) // not dropped to another box slot, restore img
|
|
{
|
|
pb.Image = img;
|
|
pb.BackgroundImage = OriginalBackground;
|
|
SetCursor(SE.GetDefaultCursor, pb);
|
|
return false;
|
|
}
|
|
|
|
if (result == DragDropEffects.Copy) // viewed in tabs or cloned
|
|
{
|
|
if (!DragInfo.Destination.IsValid) // apply 'view' highlight
|
|
SetColor(DragInfo.Source.Box, DragInfo.Source.Slot, Resources.slotView);
|
|
external = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void SetSlotSprite(SlotChange loc, PKM pk, BoxEditor x = null) => (x ?? SE.Box).SetSlotFiller(pk, loc.Box, loc.Slot);
|
|
|
|
public void HandleDropPKM(object sender, DragEventArgs e, bool overwrite, bool clone)
|
|
{
|
|
var pb = (PictureBox)sender;
|
|
DragInfo.Destination = GetViewParent(pb).GetSlotData(pb);
|
|
// Check for In-Dropped files (PKX,SAV,ETC)
|
|
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
|
if (Directory.Exists(files[0])) { SE.LoadBoxes(out string _, files[0]); return; }
|
|
if (DragInfo.SameSlot)
|
|
{
|
|
e.Effect = DragDropEffects.Link;
|
|
return;
|
|
}
|
|
if (SAV.IsSlotLocked(DragInfo.Destination.Box, DragInfo.Destination.Slot))
|
|
{
|
|
AlertInvalidate(MessageStrings.MsgSaveSlotLocked);
|
|
return;
|
|
}
|
|
bool noEgg = DragInfo.Destination.IsParty && SE.SAV.IsPartyAllEggs(DragInfo.Destination.Slot) && !SE.HaX;
|
|
if (DragInfo.Source.Offset < 0) // external source
|
|
{
|
|
if (!TryLoadFiles(files, e, noEgg))
|
|
AlertInvalidate(MessageStrings.MsgSaveSlotBadData);
|
|
return;
|
|
}
|
|
if (!TrySetPKMDestination(sender, e, overwrite, clone, noEgg))
|
|
{
|
|
AlertInvalidate(MessageStrings.MsgSaveSlotEmpty);
|
|
return;
|
|
}
|
|
|
|
if (DragInfo.Source.Parent == null) // internal file
|
|
DragInfo.Reset();
|
|
}
|
|
|
|
private void AlertInvalidate(string msg)
|
|
{
|
|
DragInfo.Destination.Slot = -1; // Invalidate
|
|
WinFormsUtil.Alert(msg);
|
|
}
|
|
|
|
private bool TryLoadFiles(IReadOnlyList<string> files, DragEventArgs e, bool noEgg)
|
|
{
|
|
if (files.Count == 0)
|
|
return false;
|
|
|
|
var temp = FileUtil.GetSingleFromPath(files[0], SAV);
|
|
if (temp == null)
|
|
{
|
|
RequestExternalDragDrop?.Invoke(this, e); // pass thru
|
|
return true; // treat as handled
|
|
}
|
|
|
|
PKM pk = PKMConverter.ConvertToType(temp, SAV.PKMType, out string c);
|
|
if (pk == null)
|
|
{
|
|
WinFormsUtil.Error(c);
|
|
Debug.WriteLine(c);
|
|
return false;
|
|
}
|
|
|
|
if (noEgg && (pk.Species == 0 || pk.IsEgg))
|
|
return false;
|
|
|
|
if (PKMConverter.IsIncompatibleGB(pk.Format, SAV.Japanese, pk.Japanese))
|
|
{
|
|
c = PKMConverter.GetIncompatibleGBMessage(pk, SAV.Japanese);
|
|
WinFormsUtil.Error(c);
|
|
Debug.WriteLine(c);
|
|
return false;
|
|
}
|
|
|
|
var errata = SAV.IsPKMCompatible(pk);
|
|
if (errata.Count > 0)
|
|
{
|
|
string concat = string.Join(Environment.NewLine, errata);
|
|
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, concat, MessageStrings.MsgContinue))
|
|
{
|
|
Debug.WriteLine(c);
|
|
Debug.WriteLine(concat);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SetPKM(pk, false, Resources.slotSet);
|
|
Debug.WriteLine(c);
|
|
return true;
|
|
}
|
|
|
|
private bool TrySetPKMDestination(object sender, DragEventArgs e, bool overwrite, bool clone, bool noEgg)
|
|
{
|
|
PKM pkz = GetPKM(true);
|
|
if (noEgg && (pkz.Species == 0 || pkz.IsEgg))
|
|
return false;
|
|
|
|
if (DragInfo.Source.IsValid)
|
|
TrySetPKMSource(sender, overwrite, clone);
|
|
|
|
// Copy from temp to destination slot.
|
|
SetPKM(pkz, false, null);
|
|
|
|
e.Effect = clone ? DragDropEffects.Copy : DragDropEffects.Link;
|
|
SetCursor(SE.GetDefaultCursor, sender);
|
|
return true;
|
|
}
|
|
|
|
private bool TrySetPKMSource(object sender, bool overwrite, bool clone)
|
|
{
|
|
if (overwrite && DragInfo.Destination.IsValid) // overwrite delete old slot
|
|
{
|
|
// Clear from slot
|
|
SetPKM(SAV.BlankPKM, true, null);
|
|
return true;
|
|
}
|
|
if (!clone && DragInfo.Destination.IsValid)
|
|
{
|
|
// Load data from destination
|
|
PKM pk = ((PictureBox)sender).Image != null
|
|
? GetPKM(false)
|
|
: SAV.BlankPKM;
|
|
|
|
// Set destination pokemon data to source slot
|
|
SetPKM(pk, true, null);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void SetColor(int box, int slot, Image img)
|
|
{
|
|
foreach (var boxview in Boxes)
|
|
updateView(boxview);
|
|
foreach (var other in OtherSlots)
|
|
updateView(other);
|
|
|
|
void updateView(ISlotViewer<PictureBox> view)
|
|
{
|
|
if (view.ViewIndex == ColorizedBox && ColorizedSlot >= 0)
|
|
view.SlotPictureBoxes[ColorizedSlot].BackgroundImage = null;
|
|
if (view.ViewIndex == box && slot >= 0)
|
|
view.SlotPictureBoxes[slot].BackgroundImage = img;
|
|
}
|
|
|
|
ColorizedBox = box;
|
|
ColorizedSlot = slot;
|
|
ColorizedColor = img;
|
|
|
|
OriginalBackground = img;
|
|
if (HoverWorker != null)
|
|
HoverWorker.OriginalBackground = img;
|
|
}
|
|
|
|
// PKM Get Set
|
|
private PKM GetPKM(bool src) => GetPKM(src ? DragInfo.Source : DragInfo.Destination);
|
|
|
|
public PKM GetPKM(SlotChange slot)
|
|
{
|
|
int o = slot.Offset;
|
|
if (o < 0)
|
|
return slot.PKM;
|
|
|
|
if (slot.IsParty)
|
|
return SAV.GetPartySlot(o);
|
|
|
|
var pk = SAV.GetStoredSlot(o);
|
|
pk.Slot = slot.Slot;
|
|
pk.Box = slot.Box;
|
|
return pk;
|
|
}
|
|
|
|
private void SetPKM(PKM pk, bool src, Image img) => SetPKM(pk, src ? DragInfo.Source : DragInfo.Destination, src, img);
|
|
|
|
public void SetPKM(PKM pk, SlotChange slot, bool src, Image img)
|
|
{
|
|
if (slot.IsParty)
|
|
{
|
|
SetPKMParty(pk, src, slot);
|
|
if (img == Resources.slotDel)
|
|
slot.Slot = SAV.PartyCount;
|
|
SetColor(slot.Box, slot.Slot, img ?? Resources.slotSet);
|
|
return;
|
|
}
|
|
|
|
int o = slot.Offset;
|
|
SAV.SetStoredSlot(pk, o);
|
|
if (slot.Type == StorageSlotType.Box)
|
|
{
|
|
foreach (var boxview in Boxes)
|
|
{
|
|
if (boxview.CurrentBox != slot.Box)
|
|
continue;
|
|
Debug.WriteLine($"Setting to {boxview.Parent.Name}'s [{boxview.CurrentBox + 1:d2}]|{boxview.CurrentBoxName} at Slot {slot.Slot + 1}.");
|
|
SetSlotSprite(slot, pk, boxview);
|
|
}
|
|
}
|
|
SetColor(slot.Box, slot.Slot, img ?? Resources.slotSet);
|
|
}
|
|
|
|
private void SetPKMParty(PKM pk, bool src, SlotChange slot)
|
|
{
|
|
int o = slot.Offset;
|
|
if (src)
|
|
{
|
|
if (pk.Species == 0) // Empty Slot
|
|
{
|
|
SAV.DeletePartySlot(slot.Slot);
|
|
SE.SetParty();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (SAV.PartyCount < slot.Slot)
|
|
{
|
|
o = SAV.GetPartyOffset(SAV.PartyCount);
|
|
slot.Slot = SAV.PartyCount;
|
|
}
|
|
}
|
|
|
|
SAV.SetPartySlot(pk, o);
|
|
SE.SetParty();
|
|
}
|
|
|
|
// Utility
|
|
public void SwapBoxes(int index, int other)
|
|
{
|
|
if (index == other)
|
|
return;
|
|
SAV.SwapBox(index, other);
|
|
UpdateBoxViewAtBoxIndexes(index, other);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Sounds.Dispose();
|
|
HoverWorker.Dispose();
|
|
SE?.Dispose();
|
|
OriginalBackground?.Dispose();
|
|
CurrentBackground?.Dispose();
|
|
ColorizedColor?.Dispose();
|
|
}
|
|
|
|
private void UpdateBoxViewAtBoxIndexes(params int[] boxIndexes)
|
|
{
|
|
foreach (var box in Boxes)
|
|
{
|
|
var current = box.CurrentBox;
|
|
if (!boxIndexes.Contains(current))
|
|
continue;
|
|
box.ResetSlots();
|
|
box.ResetBoxNames(current);
|
|
}
|
|
}
|
|
|
|
private static string GetCryPath(PKM pk, string cryFolder)
|
|
{
|
|
var name = PKX.GetResourceStringSprite(pk.Species, pk.AltForm, pk.Gender, pk.Format).Replace('_', '-').Substring(1);
|
|
var path = Path.Combine(cryFolder, $"{name}.wav");
|
|
if (!File.Exists(path))
|
|
path = Path.Combine(cryFolder, $"{pk.Species}.wav");
|
|
return path;
|
|
}
|
|
|
|
private static string GetLocalizedPreviewText(PKM pk, string language)
|
|
{
|
|
var set = new ShowdownSet(pk);
|
|
if (pk.Format <= 2) // Nature preview from IVs
|
|
set.Nature = Experience.GetNatureVC(pk.EXP);
|
|
return set.LocalizedText(language);
|
|
}
|
|
}
|
|
}
|