Track a PKM's Box,Slot,StorageFlags,Identifier metadata separately (#3222)

* Track a PKM's Box,Slot,StorageFlags,Identifier metadata separately

Don't store within the object, track the slot origin data separately.

Batch editing now pre-filters if using Box/Slot/Identifier logic; split up mods/filters as they're starting to get pretty hefty.

- Requesting a Box Data report now shows all slots in the save file (party, misc)
- Can now exclude backup saves from database search via toggle (separate from settings preventing load entirely)
- Replace some linq usages with direct code

* Remove WasLink virtual in PKM

Inline any logic, since we now have encounter objects to indicate matching, rather than the proto-legality logic checking properties of a PKM.

* Use Fateful to directly check gen5 mysterygift origins

No other encounter types in gen5 apply Fateful

* Simplify double ball comparison

Used to be separate for deferral cases, now no longer needed to be separate.

* Grab move/relearn reference and update locally

Fix relearn move identifier

* Inline defog HM transfer preference check

HasMove is faster than getting moves & checking contains. Skips allocation by setting values directly.

* Extract more met location metadata checks: WasBredEgg

* Replace Console.Write* with Debug.Write*

There's no console output UI, so don't include them in release builds.

* Inline WasGiftEgg, WasEvent, and WasEventEgg logic

Adios legality tags that aren't entirely correct for the specific format. Just put the computations in EncounterFinder.
This commit is contained in:
Kurt 2021-06-22 20:23:48 -07:00 committed by GitHub
parent 350383cf51
commit 3e7775fc44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 1114 additions and 652 deletions

View file

@ -25,22 +25,51 @@ namespace PKHeX.Core
typeof (PK2), typeof (SK2), typeof (PK1),
};
public static readonly List<string> CustomProperties = new() { PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS };
/// <summary>
/// Extra properties to show in the list of selectable properties (GUI)
/// </summary>
public static readonly List<string> CustomProperties = new()
{
PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS,
IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box),
};
/// <summary>
/// Property names, indexed by <see cref="Types"/>.
/// </summary>
public static readonly string[][] Properties = GetPropArray();
private static readonly Dictionary<string, PropertyInfo>[] Props = Types.Select(z => ReflectUtil.GetAllPropertyInfoPublic(z)
.GroupBy(p => p.Name).Select(g => g.First()).ToDictionary(p => p.Name))
.ToArray();
private static readonly Dictionary<string, PropertyInfo>[] Props = GetPropertyDictionaries(Types);
private const string CONST_RAND = "$rand";
private const string CONST_SHINY = "$shiny";
private static Dictionary<string, PropertyInfo>[] GetPropertyDictionaries(IReadOnlyList<Type> types)
{
var result = new Dictionary<string, PropertyInfo>[types.Count];
for (int i = 0; i < types.Count; i++)
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic);
return result;
}
private static Dictionary<string, PropertyInfo> GetPropertyDictionary(Type type, Func<Type, IEnumerable<PropertyInfo>> selector)
{
var dict = new Dictionary<string, PropertyInfo>();
var props = selector(type);
foreach (var p in props)
{
if (!dict.ContainsKey(p.Name))
dict.Add(p.Name, p);
}
return dict;
}
internal const string CONST_RAND = "$rand";
internal const string CONST_SHINY = "$shiny";
private const string CONST_SUGGEST = "$suggest";
private const string CONST_BYTES = "$[]";
private const string PROP_LEGAL = "Legal";
private const string PROP_TYPENAME = "ObjectType";
private const string PROP_RIBBONS = "Ribbons";
private const string IdentifierContains = nameof(PKM.Identifier) + "Contains";
internal const string PROP_LEGAL = "Legal";
internal const string PROP_TYPENAME = "ObjectType";
internal const string PROP_RIBBONS = "Ribbons";
internal const string IdentifierContains = nameof(IdentifierContains);
private static string[][] GetPropArray()
{
@ -183,6 +212,30 @@ namespace PKHeX.Core
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, PKM pk) => filters.All(z => IsFilterMatch(z, pk, Props[Array.IndexOf(Types, pk.GetType())]));
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatchMeta(IEnumerable<StringInstruction> filters, SlotCache pk)
{
foreach (var i in filters)
{
foreach (var filter in BatchFilters.FilterMeta)
{
if (!filter.IsMatch(i.PropertyName))
continue;
if (!filter.IsFiltered(pk, i))
return false;
break;
}
}
return true;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
@ -325,7 +378,7 @@ namespace PKHeX.Core
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
{
var match = FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match != null)
return match.IsFiltered(info, cmd);
return IsPropertyFiltered(cmd, info.Entity, props);
@ -340,7 +393,7 @@ namespace PKHeX.Core
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
{
var match = FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match != null)
return match.IsFiltered(pk, cmd);
return IsPropertyFiltered(cmd, pk, props);
@ -370,7 +423,7 @@ namespace PKHeX.Core
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
private static ModifyResult SetSuggestedPKMProperty(string name, BatchInfo info, string propValue)
{
var first = SuggestionMods.Find(z => z.IsMatch(name, propValue, info));
var first = BatchMods.SuggestionMods.Find(z => z.IsMatch(name, propValue, info));
if (first != null)
return first.Modify(name, propValue, info);
return ModifyResult.Error;
@ -411,7 +464,7 @@ namespace PKHeX.Core
return true;
}
var match = ComplexMods.Find(z => z.IsMatch(cmd.PropertyName, cmd.PropertyValue));
var match = BatchMods.ComplexMods.Find(z => z.IsMatch(cmd.PropertyName, cmd.PropertyValue));
if (match == null)
return false;
@ -435,81 +488,5 @@ namespace PKHeX.Core
if (TryGetHasProperty(pk, cmd.PropertyName, out var pi))
ReflectUtil.SetValue(pi, pk, Util.Rand.Next(pk.MaxIV + 1));
}
public static readonly List<IComplexFilter> FilterMods = new()
{
new ComplexFilter(PROP_LEGAL,
(pkm, cmd) => new LegalityAnalysis(pkm).Valid == cmd.Evaluator,
(info, cmd) => info.Legality.Valid == cmd.Evaluator),
new ComplexFilter(PROP_TYPENAME,
(pkm, cmd) => (pkm.GetType().Name == cmd.PropertyValue) == cmd.Evaluator,
(info, cmd) => (info.Entity.GetType().Name == cmd.PropertyValue) == cmd.Evaluator),
new ComplexFilter(IdentifierContains,
(pkm, cmd) => pkm.Identifier?.Contains(cmd.PropertyValue) == cmd.Evaluator,
(info, cmd) => info.Entity.Identifier?.Contains(cmd.PropertyValue) == cmd.Evaluator),
};
public static readonly List<ISuggestModification> SuggestionMods = new()
{
// PB7 Specific
new TypeSuggestion<PB7>(nameof(PB7.Stat_CP), p => p.ResetCP()),
new TypeSuggestion<PB7>(nameof(PB7.HeightAbsolute), p => p.HeightAbsolute = p.CalcHeightAbsolute),
new TypeSuggestion<PB7>(nameof(PB7.WeightAbsolute), p => p.WeightAbsolute = p.CalcWeightAbsolute),
// Date Copy
new TypeSuggestion<PKM>(nameof(PKM.EggMetDate), p => p.EggMetDate = p.MetDate),
new TypeSuggestion<PKM>(nameof(PKM.MetDate), p => p.MetDate = p.EggMetDate),
new TypeSuggestion<PKM>(nameof(PKM.Nature), p => p.Format >= 8, p => p.Nature = p.StatNature),
new TypeSuggestion<PKM>(nameof(PKM.StatNature), p => p.Format >= 8, p => p.StatNature = p.Nature),
new TypeSuggestion<PKM>(nameof(PKM.Stats), p => p.ResetPartyStats()),
new TypeSuggestion<PKM>(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)),
new TypeSuggestion<PKM>(nameof(PKM.Heal), p => p.Heal()),
new TypeSuggestion<PKM>(nameof(PKM.HealPP), p => p.HealPP()),
new TypeSuggestion<PKM>(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()),
new TypeSuggestion<PKM>(nameof(PKM.Move1_PP), p => p.SetSuggestedMovePP(0)),
new TypeSuggestion<PKM>(nameof(PKM.Move2_PP), p => p.SetSuggestedMovePP(1)),
new TypeSuggestion<PKM>(nameof(PKM.Move3_PP), p => p.SetSuggestedMovePP(2)),
new TypeSuggestion<PKM>(nameof(PKM.Move4_PP), p => p.SetSuggestedMovePP(3)),
new ComplexSuggestion(nameof(PKM.Moves), (_, _, info) => SetMoves(info.Entity, info.Legality.GetMoveSet())),
new ComplexSuggestion(nameof(PKM.RelearnMoves), (_, value, info) => SetSuggestedRelearnData(info, value)),
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => SetSuggestedRibbons(info, value)),
new ComplexSuggestion(nameof(PKM.Met_Location), (_, _, info) => SetSuggestedMetData(info)),
};
private static DateTime ParseDate(string val) => DateTime.ParseExact(val, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
public static readonly List<IComplexSet> ComplexMods = new()
{
// Date
new ComplexSet(nameof(PKM.MetDate), (pk, cmd) => pk.MetDate = ParseDate(cmd.PropertyValue)),
new ComplexSet(nameof(PKM.EggMetDate), (pk, cmd) => pk.EggMetDate = ParseDate(cmd.PropertyValue)),
// Value Swap
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == nameof(PKM.PID), (pk, _) => pk.EncryptionConstant = pk.PID),
new ComplexSet(nameof(PKM.PID), value => value == nameof(PKM.EncryptionConstant), (pk, _) => pk.PID = pk.EncryptionConstant),
// Realign to Derived Value
new ComplexSet(nameof(PKM.Ability), value => value.StartsWith("$"), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
new ComplexSet(nameof(PKM.AbilityNumber), value => value.StartsWith("$"), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
// Random
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == CONST_RAND, (pk, _) => pk.EncryptionConstant = Util.Rand32()),
new ComplexSet(nameof(PKM.PID), value => value == CONST_RAND, (pk, _) => pk.PID = Util.Rand32()),
new ComplexSet(nameof(PKM.Gender), value => value == CONST_RAND, (pk, _) => pk.SetPIDGender(pk.Gender)),
// Shiny
new ComplexSet(nameof(PKM.PID),
value => value.StartsWith(CONST_SHINY, true, CultureInfo.CurrentCulture),
(pk, cmd) =>
CommonEdits.SetShiny(pk, cmd.PropertyValue.EndsWith("0") ? Shiny.AlwaysSquare : cmd.PropertyValue.EndsWith("1") ? Shiny.AlwaysStar : Shiny.Random)),
new ComplexSet(nameof(PKM.Species), value => value == "0", (pk, _) => Array.Clear(pk.Data, 0, pk.Data.Length)),
new ComplexSet(nameof(PKM.IsNicknamed), value => string.Equals(value, "false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
};
}
}

View file

@ -26,10 +26,10 @@ namespace PKHeX.Core
{
if (pkm.Species <= 0)
return false;
if (!pkm.Valid || pkm.Locked)
if (!pkm.Valid)
{
Iterated++;
var reason = pkm.Locked ? "Locked." : "Not Valid.";
const string reason = "Not Valid.";
Debug.WriteLine($"{MsgBEModifyFailBlocked} {reason}");
return false;
}
@ -77,5 +77,10 @@ namespace PKHeX.Core
return editor;
}
public void AddSkipped()
{
++Iterated;
}
}
}

View file

@ -0,0 +1,31 @@
using System.Collections.Generic;
using static PKHeX.Core.BatchEditing;
namespace PKHeX.Core
{
public static class BatchFilters
{
public static readonly List<IComplexFilter> FilterMods = new()
{
new ComplexFilter(PROP_LEGAL,
(pkm, cmd) => new LegalityAnalysis(pkm).Valid == cmd.Evaluator,
(info, cmd) => info.Legality.Valid == cmd.Evaluator),
new ComplexFilter(PROP_TYPENAME,
(pkm, cmd) => (pkm.GetType().Name == cmd.PropertyValue) == cmd.Evaluator,
(info, cmd) => (info.Entity.GetType().Name == cmd.PropertyValue) == cmd.Evaluator),
};
public static readonly List<IComplexFilterMeta> FilterMeta = new()
{
new MetaFilter(IdentifierContains,
(obj, cmd) => obj is SlotCache s && s.Identify().Contains(cmd.PropertyValue) == cmd.Evaluator),
new MetaFilter(nameof(SlotInfoBox.Box),
(obj, cmd) => obj is SlotCache { Source: SlotInfoBox b } && (b.Box.ToString() == cmd.PropertyValue) == cmd.Evaluator),
new MetaFilter(nameof(ISlotInfo.Slot),
(obj, cmd) => obj is SlotCache s && (s.Source.Slot.ToString() == cmd.PropertyValue) == cmd.Evaluator),
};
}
}

View file

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using static PKHeX.Core.BatchEditing;
namespace PKHeX.Core
{
public static class BatchMods
{
public static readonly List<ISuggestModification> SuggestionMods = new()
{
// PB7 Specific
new TypeSuggestion<PB7>(nameof(PB7.Stat_CP), p => p.ResetCP()),
new TypeSuggestion<PB7>(nameof(PB7.HeightAbsolute), p => p.HeightAbsolute = p.CalcHeightAbsolute),
new TypeSuggestion<PB7>(nameof(PB7.WeightAbsolute), p => p.WeightAbsolute = p.CalcWeightAbsolute),
// Date Copy
new TypeSuggestion<PKM>(nameof(PKM.EggMetDate), p => p.EggMetDate = p.MetDate),
new TypeSuggestion<PKM>(nameof(PKM.MetDate), p => p.MetDate = p.EggMetDate),
new TypeSuggestion<PKM>(nameof(PKM.Nature), p => p.Format >= 8, p => p.Nature = p.StatNature),
new TypeSuggestion<PKM>(nameof(PKM.StatNature), p => p.Format >= 8, p => p.StatNature = p.Nature),
new TypeSuggestion<PKM>(nameof(PKM.Stats), p => p.ResetPartyStats()),
new TypeSuggestion<PKM>(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)),
new TypeSuggestion<PKM>(nameof(PKM.Heal), p => p.Heal()),
new TypeSuggestion<PKM>(nameof(PKM.HealPP), p => p.HealPP()),
new TypeSuggestion<PKM>(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()),
new TypeSuggestion<PKM>(nameof(PKM.Move1_PP), p => p.SetSuggestedMovePP(0)),
new TypeSuggestion<PKM>(nameof(PKM.Move2_PP), p => p.SetSuggestedMovePP(1)),
new TypeSuggestion<PKM>(nameof(PKM.Move3_PP), p => p.SetSuggestedMovePP(2)),
new TypeSuggestion<PKM>(nameof(PKM.Move4_PP), p => p.SetSuggestedMovePP(3)),
new ComplexSuggestion(nameof(PKM.Moves), (_, _, info) => BatchModifications.SetMoves(info.Entity, info.Legality.GetMoveSet())),
new ComplexSuggestion(nameof(PKM.RelearnMoves), (_, value, info) => BatchModifications.SetSuggestedRelearnData(info, value)),
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => BatchModifications.SetSuggestedRibbons(info, value)),
new ComplexSuggestion(nameof(PKM.Met_Location), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
};
private static DateTime ParseDate(string val) => DateTime.ParseExact(val, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
public static readonly List<IComplexSet> ComplexMods = new()
{
// Date
new ComplexSet(nameof(PKM.MetDate), (pk, cmd) => pk.MetDate = ParseDate(cmd.PropertyValue)),
new ComplexSet(nameof(PKM.EggMetDate), (pk, cmd) => pk.EggMetDate = ParseDate(cmd.PropertyValue)),
// Value Swap
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == nameof(PKM.PID), (pk, _) => pk.EncryptionConstant = pk.PID),
new ComplexSet(nameof(PKM.PID), value => value == nameof(PKM.EncryptionConstant), (pk, _) => pk.PID = pk.EncryptionConstant),
// Realign to Derived Value
new ComplexSet(nameof(PKM.Ability), value => value.StartsWith("$"), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
new ComplexSet(nameof(PKM.AbilityNumber), value => value.StartsWith("$"), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
// Random
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == CONST_RAND, (pk, _) => pk.EncryptionConstant = Util.Rand32()),
new ComplexSet(nameof(PKM.PID), value => value == CONST_RAND, (pk, _) => pk.PID = Util.Rand32()),
new ComplexSet(nameof(PKM.Gender), value => value == CONST_RAND, (pk, _) => pk.SetPIDGender(pk.Gender)),
// Shiny
new ComplexSet(nameof(PKM.PID),
value => value.StartsWith(CONST_SHINY, true, CultureInfo.CurrentCulture),
(pk, cmd) => CommonEdits.SetShiny(pk, GetRequestedShinyState(cmd.PropertyValue))),
new ComplexSet(nameof(PKM.Species), value => value == "0", (pk, _) => Array.Clear(pk.Data, 0, pk.Data.Length)),
new ComplexSet(nameof(PKM.IsNicknamed), value => string.Equals(value, "false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
};
private static Shiny GetRequestedShinyState(string text)
{
if (text.EndsWith("0"))
return Shiny.AlwaysSquare;
if (text.EndsWith("1"))
return Shiny.AlwaysStar;
return Shiny.Random;
}
}
}

View file

@ -0,0 +1,11 @@
namespace PKHeX.Core
{
/// <summary>
/// Complex filter of data based on a string value.
/// </summary>
public interface IComplexFilterMeta
{
bool IsMatch(string prop);
bool IsFiltered(object cache, StringInstruction value);
}
}

View file

@ -0,0 +1,22 @@
using System;
namespace PKHeX.Core
{
/// <inheritdoc cref="IComplexFilter"/>
public class MetaFilter : IComplexFilterMeta
{
private readonly string Property;
private readonly Func<object, StringInstruction, bool> FilterPKM;
public MetaFilter(
string property,
Func<object, StringInstruction, bool> filterPkm)
{
Property = property;
FilterPKM = filterPkm;
}
public bool IsMatch(string prop) => prop == Property;
public bool IsFiltered(object pkm, StringInstruction cmd) => FilterPKM(pkm, cmd);
}
}

View file

@ -14,7 +14,7 @@ namespace PKHeX.Core
private readonly ushort[] Stats;
protected readonly PKM pkm; // protected for children generating extra properties
public string? Position => pkm.Identifier;
public virtual string Position => "???";
public string Nickname => pkm.Nickname;
public string Species => Get(Strings.specieslist, pkm.Species);
public string Nature => Get(Strings.natures, pkm.StatNature);

View file

@ -29,7 +29,7 @@ namespace PKHeX.Core
new BoxManipSort(BoxManipType.SortDate, PKMSorting.OrderByDateObtained, s => s.Generation >= 4),
new BoxManipSort(BoxManipType.SortName, list => list.OrderBySpeciesName(GameInfo.Strings.Species)),
new BoxManipSort(BoxManipType.SortFavorite, list => list.OrderByCustom(pk => pk is PB7 {Favorite: true}), s => s is SAV7b),
new BoxManipSortComplex(BoxManipType.SortParty, (list, sav) => list.OrderByCustom(pk => ((SAV7b)sav).Blocks.Storage.GetPartyIndex(pk.Box - 1, pk.Slot - 1)), s => s is SAV7b),
new BoxManipSortComplex(BoxManipType.SortParty, (list, sav, start) => list.BubbleUp(sav, i => ((SAV7b)sav).Blocks.Storage.IsParty(i), start), s => s is SAV7b),
new BoxManipSort(BoxManipType.SortShiny, list => list.OrderByCustom(pk => !pk.IsShiny)),
new BoxManipSort(BoxManipType.SortRandom, list => list.OrderByCustom(_ => Util.Rand32())),
};

View file

@ -15,7 +15,7 @@ namespace PKHeX.Core
public override int Execute(SaveFile sav, BoxManipParam param)
{
IEnumerable<PKM> Method(IEnumerable<PKM> p) => Sorter(p);
IEnumerable<PKM> Method(IEnumerable<PKM> p, int index) => Sorter(p);
return sav.SortBoxes(param.Start, param.Stop, Method, param.Reverse);
}
}

View file

@ -5,9 +5,12 @@ namespace PKHeX.Core
{
public sealed class BoxManipSortComplex : BoxManipBase
{
private readonly Func<IEnumerable<PKM>, SaveFile, IEnumerable<PKM>> Sorter;
private readonly Func<IEnumerable<PKM>, SaveFile, int, IEnumerable<PKM>> Sorter;
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, IEnumerable<PKM>> sorter) : this(type, sorter, _ => true) { }
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, IEnumerable<PKM>> sorter, Func<SaveFile, bool> usable) : base(type, usable) => Sorter = sorter;
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, IEnumerable<PKM>> sorter, Func<SaveFile, bool> usable) : base(type, usable) => Sorter = (pkms, file, _) => sorter(pkms, file);
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, int, IEnumerable<PKM>> sorter) : this(type, sorter, _ => true) { }
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, int, IEnumerable<PKM>> sorter, Func<SaveFile, bool> usable) : base(type, usable) => Sorter = sorter;
public override string GetPrompt(bool all) => all ? MessageStrings.MsgSaveBoxSortAll : MessageStrings.MsgSaveBoxSortCurrent;
public override string GetFail(bool all) => all ? MessageStrings.MsgSaveBoxSortAllFailBattle : MessageStrings.MsgSaveBoxSortCurrentFailBattle;
@ -15,7 +18,7 @@ namespace PKHeX.Core
public override int Execute(SaveFile sav, BoxManipParam param)
{
IEnumerable<PKM> Method(IEnumerable<PKM> p) => Sorter(p, sav);
IEnumerable<PKM> Method(IEnumerable<PKM> p, int index) => Sorter(p, sav, index);
return sav.SortBoxes(param.Start, param.Stop, Method, param.Reverse);
}
}

View file

@ -7,10 +7,39 @@ namespace PKHeX.Core
/// </summary>
public interface ISlotInfo : IEquatable<ISlotInfo>
{
/// <summary>
/// Indicates the type of format the slot originates. Useful for legality purposes.
/// </summary>
SlotOrigin Origin { get; }
/// <summary>
/// Differentiating slot number from other infos of the same type.
/// </summary>
int Slot { get; }
/// <summary>
/// Indicates if this slot can write to the requested <see cref="sav"/>.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <returns>True if can write to</returns>
bool CanWriteTo(SaveFile sav);
/// <summary>
/// Checks if the <see cref="pkm"/> can be written to the <see cref="sav"/> for this slot.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <param name="pkm">Entity data to try writing.</param>
/// <returns>True if can write to</returns>
WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm);
/// <summary>
/// Tries writing the <see cref="pkm"/> to the <see cref="sav"/>.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <param name="pkm">Entity data to try writing.</param>
/// <param name="setting">Setting to use when importing the <see cref="pkm"/> data</param>
/// <returns>Returns false if it did not succeed.</returns>
bool WriteTo(SaveFile sav, PKM pkm, PKMImportSetting setting = PKMImportSetting.UseDefault);
PKM Read(SaveFile sav);
}
}
}

View file

@ -0,0 +1,64 @@
using System;
namespace PKHeX.Core
{
/// <summary>
/// Contains slot data and metadata indicating where the <see cref="PKM"/> originated from.
/// </summary>
public class SlotCache
{
/// <summary>
/// Information regarding how the <see cref="Entity"/> was obtained.
/// </summary>
public readonly ISlotInfo Source;
/// <summary>
/// Save File reference that obtained the <see cref="Entity"/>.
/// </summary>
public readonly SaveFile SAV;
/// <summary>
/// Data that was loaded.
/// </summary>
public readonly PKM Entity;
private static readonly FakeSaveFile NoSaveFile = new();
public SlotCache(SlotInfoFile source, PKM entity)
{
Source = source;
Entity = entity;
SAV = NoSaveFile;
}
public SlotCache(ISlotInfo source, PKM entity, SaveFile sav)
{
Source = source;
Entity = entity;
SAV = sav;
}
public string Identify() => GetFileName() + Source switch
{
SlotInfoBox box => $"[{box.Box + 1:00}] ({SAV.GetBoxName(box.Box)})-{box.Slot + 1:00}: {Entity.FileName}",
SlotInfoFile file => $"File: {file.Path}",
SlotInfoMisc misc => $"{misc.Type}-{misc.Slot}: {Entity.FileName}",
SlotInfoParty party => $"Party: {party.Slot}: {Entity.FileName}",
_ => throw new ArgumentOutOfRangeException(nameof(Source))
};
private string GetFileName()
{
var fn = SAV.Metadata.FileName;
if (fn is null)
return string.Empty;
return $"{fn} @ ";
}
public bool IsDataValid()
{
var e = Entity;
return e.Species != 0 && e.ChecksumValid && (e.Sanity == 0 || e is BK4);
}
}
}

View file

@ -3,10 +3,11 @@ namespace PKHeX.Core
/// <summary>
/// Box Data <see cref="ISlotInfo"/>
/// </summary>
public sealed class SlotInfoBox : ISlotInfo
public class SlotInfoBox : ISlotInfo
{
public int Box { get; }
public int Slot { get; }
public SlotOrigin Origin => SlotOrigin.Box;
public bool CanWriteTo(SaveFile sav) => sav.HasBox && !sav.IsSlotLocked(Box, Slot);
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm) => WriteBlockedMessage.None;
@ -30,4 +31,4 @@ namespace PKHeX.Core
public override int GetHashCode() => (Box * 397) ^ Slot;
}
}
}

View file

@ -0,0 +1,17 @@
namespace PKHeX.Core
{
public sealed class SlotInfoFile : ISlotInfo
{
public readonly string Path;
public SlotOrigin Origin => SlotOrigin.Party;
public int Slot => 0;
public SlotInfoFile(string path) => Path = path;
public bool Equals(ISlotInfo other) => other is SlotInfoFile f && f.Path == Path;
public bool CanWriteTo(SaveFile sav) => false;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm) => WriteBlockedMessage.InvalidDestination;
public bool WriteTo(SaveFile sav, PKM pkm, PKMImportSetting setting = PKMImportSetting.UseDefault) => false;
public PKM Read(SaveFile sav) => sav.BlankPKM;
}
}

View file

@ -0,0 +1,163 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
namespace PKHeX.Core
{
public static class SlotInfoLoader
{
// The "Add" method isn't shared for any interface... so we'll just do this twice.
#region ConcurrentBag Implementation
public static void AddFromSaveFile(SaveFile sav, ConcurrentBag<SlotCache> db)
{
if (sav.HasBox)
AddBoxData(sav, db);
if (sav.HasParty)
AddPartyData(sav, db);
AddExtraData(sav, db);
}
public static void AddFromLocalFile(string file, ConcurrentBag<SlotCache> db, ITrainerInfo dest, ICollection<string> validExtensions)
{
var fi = new FileInfo(file);
if (!validExtensions.Contains(fi.Extension) || !PKX.IsPKM(fi.Length))
return;
var data = File.ReadAllBytes(file);
var prefer = PKX.GetPKMFormatFromExtension(fi.Extension, dest.Generation);
var pk = PKMConverter.GetPKMfromBytes(data, prefer);
if (pk?.Species is not > 0)
return;
var info = new SlotInfoFile(file);
var entry = new SlotCache(info, pk);
db.Add(entry);
}
private static void AddBoxData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var bd = sav.BoxData;
var bc = sav.BoxCount;
var sc = sav.BoxSlotCount;
int ctr = 0;
for (int box = 0; box < bc; box++)
{
for (int slot = 0; slot < sc; slot++, ctr++)
{
var ident = new SlotInfoBox(box, slot);
var result = new SlotCache(ident, bd[ctr], sav);
db.Add(result);
}
}
}
private static void AddPartyData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var pd = sav.PartyData;
for (var index = 0; index < pd.Count; index++)
{
var pk = pd[index];
if (pk.Species == 0)
continue;
var ident = new SlotInfoParty(index);
var result = new SlotCache(ident, pk, sav);
db.Add(result);
}
}
private static void AddExtraData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var extra = sav.GetExtraSlots(true);
foreach (var x in extra)
{
var pk = x.Read(sav);
if (pk.Species == 0)
continue;
var result = new SlotCache(x, pk, sav);
db.Add(result);
}
}
#endregion
#region ICollection Implementation
public static void AddFromSaveFile(SaveFile sav, ICollection<SlotCache> db)
{
if (sav.HasBox)
AddBoxData(sav, db);
if (sav.HasParty)
AddPartyData(sav, db);
AddExtraData(sav, db);
}
public static void AddFromLocalFile(string file, ICollection<SlotCache> db, ITrainerInfo dest, ICollection<string> validExtensions)
{
var fi = new FileInfo(file);
if (!validExtensions.Contains(fi.Extension) || !PKX.IsPKM(fi.Length))
return;
var data = File.ReadAllBytes(file);
var prefer = PKX.GetPKMFormatFromExtension(fi.Extension, dest.Generation);
var pk = PKMConverter.GetPKMfromBytes(data, prefer);
if (pk?.Species is not > 0)
return;
var info = new SlotInfoFile(file);
var entry = new SlotCache(info, pk);
db.Add(entry);
}
public static void AddBoxData(SaveFile sav, ICollection<SlotCache> db)
{
var bd = sav.BoxData;
var bc = sav.BoxCount;
var sc = sav.BoxSlotCount;
int ctr = 0;
for (int box = 0; box < bc; box++)
{
for (int slot = 0; slot < sc; slot++, ctr++)
{
var ident = new SlotInfoBox(box, slot);
var result = new SlotCache(ident, bd[ctr], sav);
db.Add(result);
}
}
}
public static void AddPartyData(SaveFile sav, ICollection<SlotCache> db)
{
var pd = sav.PartyData;
for (var index = 0; index < pd.Count; index++)
{
var pk = pd[index];
if (pk.Species == 0)
continue;
var ident = new SlotInfoParty(index);
var result = new SlotCache(ident, pk, sav);
db.Add(result);
}
}
private static void AddExtraData(SaveFile sav, ICollection<SlotCache> db)
{
var extra = sav.GetExtraSlots(true);
foreach (var x in extra)
{
var pk = x.Read(sav);
if (pk.Species == 0)
continue;
var result = new SlotCache(x, pk, sav);
db.Add(result);
}
}
#endregion
}
}

View file

@ -8,6 +8,7 @@ namespace PKHeX.Core
public int Slot { get; }
public bool PartyFormat { get; }
public int Offset { get; }
public SlotOrigin Origin => PartyFormat ? SlotOrigin.Party : SlotOrigin.Box;
public bool CanWriteTo(SaveFile sav) => false;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm) => WriteBlockedMessage.InvalidDestination;
public StorageSlotType Type { get; init; }

View file

@ -8,6 +8,7 @@ namespace PKHeX.Core
public sealed class SlotInfoParty : ISlotInfo
{
public int Slot { get; private set; }
public SlotOrigin Origin => SlotOrigin.Party;
public bool CanWriteTo(SaveFile sav) => sav.HasParty;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm) => pkm.IsEgg && sav.IsPartyAllEggs(Slot)
@ -36,4 +37,4 @@ namespace PKHeX.Core
public override bool Equals(object obj) => obj is SlotInfoParty p && Equals(p);
public override int GetHashCode() => Slot;
}
}
}

View file

@ -0,0 +1,8 @@
namespace PKHeX.Core
{
public enum SlotOrigin
{
Party = 0,
Box = 1,
}
}

View file

@ -13,11 +13,11 @@ namespace PKHeX.Core
/// </summary>
public sealed class BulkAnalysis
{
public readonly IReadOnlyList<PKM> AllData;
public readonly IReadOnlyList<SlotCache> AllData;
public readonly IReadOnlyList<LegalityAnalysis> AllAnalysis;
public readonly ITrainerInfo Trainer;
public readonly List<CheckResult> Parse = new();
public readonly Dictionary<ulong, PKM> Trackers = new();
public readonly Dictionary<ulong, SlotCache> Trackers = new();
public readonly bool Valid;
private readonly bool[] CloneFlags;
@ -25,17 +25,9 @@ namespace PKHeX.Core
public BulkAnalysis(SaveFile sav)
{
Trainer = sav;
AllData = sav.GetAllPKM();
AllAnalysis = GetIndividualAnalysis(AllData);
CloneFlags = new bool[AllData.Count];
Valid = ScanAll();
}
public BulkAnalysis(ITrainerInfo tr, IEnumerable<PKM> pkms)
{
Trainer = tr;
AllData = pkms is IReadOnlyList<PKM> pk ? pk : pkms.ToList();
var list = new List<SlotCache>(sav.BoxSlotCount + (sav.HasParty ? 6 : 0) + 5);
SlotInfoLoader.AddFromSaveFile(sav, list);
AllData = list;
AllAnalysis = GetIndividualAnalysis(AllData);
CloneFlags = new bool[AllData.Count];
@ -60,19 +52,17 @@ namespace PKHeX.Core
return Parse.All(z => z.Valid);
}
private void AddLine(PKM first, PKM second, string msg, CheckIdentifier i, Severity s = Severity.Invalid)
{
static string GetSummary(PKM pk) => $"[{pk.Box:00}, {pk.Slot:00}] {pk.FileName}";
private static string GetSummary(SlotCache entry) => $"[{entry.Identify()}] {entry.Entity.FileName}";
private void AddLine(SlotCache first, SlotCache second, string msg, CheckIdentifier i, Severity s = Severity.Invalid)
{
var c = $"{msg}{Environment.NewLine}{GetSummary(first)}{Environment.NewLine}{GetSummary(second)}";
var chk = new CheckResult(s, c, i);
Parse.Add(chk);
}
private void AddLine(PKM first, string msg, CheckIdentifier i, Severity s = Severity.Invalid)
private void AddLine(SlotCache first, string msg, CheckIdentifier i, Severity s = Severity.Invalid)
{
static string GetSummary(PKM pk) => $"[{pk.Box:00}, {pk.Slot:00}] {pk.FileName}";
var c = $"{msg}{Environment.NewLine}{GetSummary(first)}";
var chk = new CheckResult(s, c, i);
Parse.Add(chk);
@ -80,111 +70,117 @@ namespace PKHeX.Core
private void CheckClones()
{
var dict = new Dictionary<string, LegalityAnalysis>();
var dict = new Dictionary<string, SlotCache>();
for (int i = 0; i < AllData.Count; i++)
{
var cp = AllData[i];
var cs = AllData[i];
var ca = AllAnalysis[i];
Debug.Assert(cp.Format == Trainer.Generation);
Debug.Assert(cs.Entity.Format == Trainer.Generation);
// Check the upload tracker to see if there's any duplication.
if (cp is IHomeTrack home)
if (cs.Entity is IHomeTrack home)
{
if (home.Tracker != 0)
{
var tracker = home.Tracker;
if (Trackers.TryGetValue(tracker, out var clone))
AddLine(clone, cp, "Clone detected (Duplicate Tracker).", Encounter);
AddLine(cs, clone!, "Clone detected (Duplicate Tracker).", Encounter);
else
Trackers.Add(tracker, cp);
Trackers.Add(tracker, cs);
}
else if (ca.Info.Generation < 8)
{
AddLine(cp, "Missing tracker.", Encounter);
AddLine(cs, "Missing tracker.", Encounter);
}
}
// Hash Details like EC/IV to see if there's any duplication.
var identity = SearchUtil.HashByDetails(cp);
if (!dict.TryGetValue(identity, out var pa))
var identity = SearchUtil.HashByDetails(cs.Entity);
if (!dict.TryGetValue(identity, out var ps))
{
dict.Add(identity, ca);
dict.Add(identity, cs);
continue;
}
CloneFlags[i] = true;
AddLine(pa.pkm, cp, "Clone detected (Details).", Encounter);
AddLine(ps!, cs, "Clone detected (Details).", Encounter);
}
}
private void CheckDuplicateOwnedGifts()
{
var dupes = AllAnalysis.Where(z =>
z.Info.Generation >= 3
&& z.EncounterMatch is MysteryGift {EggEncounter: true} && !z.pkm.WasTradedEgg)
.GroupBy(z => ((MysteryGift)z.EncounterMatch).CardTitle);
var combined = new CombinedReference[AllData.Count];
for (int i = 0; i < combined.Length; i++)
combined[i] = new CombinedReference(AllData[i], AllAnalysis[i]);
var dupes = combined.Where(z =>
z.Analysis.Info.Generation >= 3
&& z.Analysis.EncounterMatch is MysteryGift {EggEncounter: true} && !z.Slot.Entity.WasTradedEgg)
.GroupBy(z => ((MysteryGift)z.Analysis.EncounterMatch).CardTitle);
foreach (var dupe in dupes)
{
var tidGroup = dupe.GroupBy(z => z.pkm.TID | (z.pkm.SID << 16))
var tidGroup = dupe.GroupBy(z => z.Slot.Entity.TID | (z.Slot.Entity.SID << 16))
.Select(z => z.ToList())
.Where(z => z.Count >= 2).ToList();
if (tidGroup.Count == 0)
continue;
AddLine(tidGroup[0][0].pkm, tidGroup[0][1].pkm, $"Receipt of the same egg mystery gifts detected: {dupe.Key}", Encounter);
var first = tidGroup[0][0].Slot;
var second = tidGroup[0][1].Slot;
AddLine(first, second, $"Receipt of the same egg mystery gifts detected: {dupe.Key}", Encounter);
}
}
private void CheckECReuse()
{
var dict = new Dictionary<uint, LegalityAnalysis>();
var dict = new Dictionary<uint, CombinedReference>();
for (int i = 0; i < AllData.Count; i++)
{
if (CloneFlags[i])
continue; // already flagged
var cp = AllData[i];
var ca = AllAnalysis[i];
Debug.Assert(cp.Format >= 6);
var id = cp.EncryptionConstant;
Debug.Assert(cp.Entity.Format >= 6);
var id = cp.Entity.EncryptionConstant;
if (!dict.TryGetValue(id, out var pa))
var cr = new CombinedReference(cp, ca);
if (!dict.TryGetValue(id, out var pa) || pa is null)
{
dict.Add(id, ca);
dict.Add(id, cr);
continue;
}
VerifyECShare(pa, ca);
VerifyECShare(pa, cr);
}
}
private void CheckHOMETrackerReuse()
{
var dict = new Dictionary<ulong, LegalityAnalysis>();
var dict = new Dictionary<ulong, SlotCache>();
for (int i = 0; i < AllData.Count; i++)
{
if (CloneFlags[i])
continue; // already flagged
var cp = AllData[i];
var ca = AllAnalysis[i];
Debug.Assert(cp.Format >= 8);
Debug.Assert(cp is IHomeTrack);
var id = ((IHomeTrack)cp).Tracker;
Debug.Assert(cp.Entity.Format >= 8);
Debug.Assert(cp.Entity is IHomeTrack);
var id = ((IHomeTrack)cp.Entity).Tracker;
if (id == 0)
continue;
if (!dict.TryGetValue(id, out var pa))
if (!dict.TryGetValue(id, out var ps) || ps is null)
{
dict.Add(id, ca);
dict.Add(id, cp);
continue;
}
AddLine(pa.pkm, ca.pkm, "HOME Tracker sharing detected.", Misc);
AddLine(ps, cp, "HOME Tracker sharing detected.", Misc);
}
}
private void CheckPIDReuse()
{
var dict = new Dictionary<uint, LegalityAnalysis>();
var dict = new Dictionary<uint, CombinedReference>();
for (int i = 0; i < AllData.Count; i++)
{
if (CloneFlags[i])
@ -192,53 +188,73 @@ namespace PKHeX.Core
var cp = AllData[i];
var ca = AllAnalysis[i];
bool g345 = ca.Info.Generation is 3 or 4 or 5;
var id = g345 ? cp.EncryptionConstant : cp.PID;
var id = g345 ? cp.Entity.EncryptionConstant : cp.Entity.PID;
if (!dict.TryGetValue(id, out var pa))
var cr = new CombinedReference(cp, ca);
if (!dict.TryGetValue(id, out var pr) || pr is null)
{
dict.Add(id, ca);
dict.Add(id, cr);
continue;
}
VerifyPIDShare(pa, ca);
VerifyPIDShare(pr, cr);
}
}
private class CombinedReference
{
public readonly SlotCache Slot;
public readonly LegalityAnalysis Analysis;
public CombinedReference(SlotCache slot, LegalityAnalysis analysis)
{
Slot = slot;
Analysis = analysis;
}
}
private void CheckIDReuse()
{
var dict = new Dictionary<int, LegalityAnalysis>();
var dict = new Dictionary<int, CombinedReference>();
for (int i = 0; i < AllData.Count; i++)
{
if (CloneFlags[i])
continue; // already flagged
var cp = AllData[i];
var cs = AllData[i];
var ca = AllAnalysis[i];
var id = cp.TID + (cp.SID << 16);
Debug.Assert(cp.TID <= ushort.MaxValue);
var id = cs.Entity.TID + (cs.Entity.SID << 16);
Debug.Assert(cs.Entity.TID <= ushort.MaxValue);
if (!dict.TryGetValue(id, out var pa))
if (!dict.TryGetValue(id, out var pr) || pr is null)
{
dict.Add(id, ca);
var r = new CombinedReference(cs, ca);
dict.Add(id, r);
continue;
}
var pa = pr.Analysis;
// ignore GB era collisions
// a 16bit TID can reasonably occur for multiple trainers, and versions
if (ca.Info.Generation <= 2 && pa.Info.Generation <= 2)
continue;
var pp = pa.pkm;
if (VerifyIDReuse(pp, pa, cp, ca))
var ps = pr.Slot;
if (VerifyIDReuse(ps, pa, cs, ca))
continue;
// egg encounters can be traded before hatching
// store the current loop pkm if it's a better reference
if (pp.WasTradedEgg && !cp.WasTradedEgg)
dict[id] = ca;
if (ps.Entity.WasTradedEgg && !cs.Entity.WasTradedEgg)
dict[id] = new CombinedReference(cs, ca);
}
}
private void VerifyECShare(LegalityAnalysis pa, LegalityAnalysis ca)
private void VerifyECShare(CombinedReference pr, CombinedReference cr)
{
var ps = pr.Slot;
var pa = pr.Analysis;
var cs = cr.Slot;
var ca = cr.Analysis;
const CheckIdentifier ident = PID;
int gen = pa.Info.Generation;
bool gbaNDS = gen is 3 or 4 or 5;
@ -247,10 +263,10 @@ namespace PKHeX.Core
{
if (ca.Info.Generation != gen)
{
AddLine(pa.pkm, ca.pkm, "EC sharing across generations detected.", ident);
AddLine(ps, cs, "EC sharing across generations detected.", ident);
return;
}
AddLine(pa.pkm, ca.pkm, "EC sharing for 3DS-onward origin detected.", ident);
AddLine(ps, cs, "EC sharing for 3DS-onward origin detected.", ident);
return;
}
@ -262,24 +278,29 @@ namespace PKHeX.Core
if (eggMysteryCurrent != eggMysteryPrevious)
{
AddLine(pa.pkm, ca.pkm, "EC sharing across RNG encounters detected.", ident);
AddLine(ps, cs, "EC sharing across RNG encounters detected.", ident);
}
}
private void VerifyPIDShare(LegalityAnalysis pa, LegalityAnalysis ca)
private void VerifyPIDShare(CombinedReference pr, CombinedReference cr)
{
var ps = pr.Slot;
var pa = pr.Analysis;
var cs = cr.Slot;
var ca = cr.Analysis;
const CheckIdentifier ident = PID;
int gen = pa.Info.Generation;
if (ca.Info.Generation != gen)
{
AddLine(pa.pkm, ca.pkm, "PID sharing across generations detected.", ident);
AddLine(ps, cs, "PID sharing across generations detected.", ident);
return;
}
bool gbaNDS = gen is 3 or 4 or 5;
if (!gbaNDS)
{
AddLine(pa.pkm, ca.pkm, "PID sharing for 3DS-onward origin detected.", ident);
AddLine(ps, cs, "PID sharing for 3DS-onward origin detected.", ident);
return;
}
@ -291,11 +312,11 @@ namespace PKHeX.Core
if (eggMysteryCurrent != eggMysteryPrevious)
{
AddLine(pa.pkm, ca.pkm, "PID sharing across RNG encounters detected.", ident);
AddLine(ps, cs, "PID sharing across RNG encounters detected.", ident);
}
}
private bool VerifyIDReuse(PKM pp, LegalityAnalysis pa, PKM cp, LegalityAnalysis ca)
private bool VerifyIDReuse(SlotCache ps, LegalityAnalysis pa, SlotCache cs, LegalityAnalysis ca)
{
if (pa.EncounterMatch is MysteryGift {EggEncounter: false})
return false;
@ -304,11 +325,14 @@ namespace PKHeX.Core
const CheckIdentifier ident = CheckIdentifier.Trainer;
var pp = ps.Entity;
var cp = cs.Entity;
// 32bit ID-SID should only occur for one generation
// Trainer-ID-SID should only occur for one version
if (IsSharedVersion(pp, pa, cp, ca))
{
AddLine(pa.pkm, ca.pkm, "TID sharing across versions detected.", ident);
AddLine(ps, cs, "TID sharing across versions detected.", ident);
return true;
}
@ -316,7 +340,7 @@ namespace PKHeX.Core
if (pp.OT_Name != cp.OT_Name)
{
var severity = ca.Info.Generation == 4 ? Severity.Fishy : Severity.Invalid;
AddLine(pa.pkm, ca.pkm, "TID sharing across different trainer names detected.", ident, severity);
AddLine(ps, cs, "TID sharing across different trainer names detected.", ident, severity);
}
return false;
@ -339,11 +363,15 @@ namespace PKHeX.Core
return true;
}
private static IReadOnlyList<LegalityAnalysis> GetIndividualAnalysis(IReadOnlyList<PKM> pkms)
private static IReadOnlyList<LegalityAnalysis> GetIndividualAnalysis(IReadOnlyList<SlotCache> pkms)
{
var results = new LegalityAnalysis[pkms.Count];
for (int i = 0; i < pkms.Count; i++)
results[i] = new LegalityAnalysis(pkms[i]);
{
var entry = pkms[i];
var pk = entry.Entity;
results[i] = new LegalityAnalysis(pk);
}
return results;
}
}

View file

@ -49,7 +49,7 @@ namespace PKHeX.Core
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
if (pkm.WasBredEgg)
if (Locations.IsEggLocationBred4(pkm.Egg_Location, (GameVersion)pkm.Version))
{
foreach (var z in GenerateEggs(pkm, 4))
yield return z;
@ -60,9 +60,7 @@ namespace PKHeX.Core
IEncounterable? deferred = null;
IEncounterable? partial = null;
bool sport = pkm.Ball == (int)Ball.Sport; // never static encounters (conflict with non bcc / bcc)
bool safari = pkm.Ball == (int)Ball.Safari; // never static encounters
bool safariSport = safari || sport;
bool safariSport = pkm.Ball is (int)Ball.Sport or (int)Ball.Safari; // never static encounters
if (!safariSport)
{
foreach (var z in GetValidStaticEncounter(pkm, chain))

View file

@ -16,14 +16,14 @@ namespace PKHeX.Core
int ctr = 0;
var chain = EncounterOrigin.GetOriginChain(pkm);
if (pkm.WasEvent || pkm.WasEventEgg)
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
if (pkm.WasBredEgg)
if (Locations.IsEggLocationBred5(pkm.Egg_Location))
{
foreach (var z in GenerateEggs(pkm, 5))
{ yield return z; ++ctr; }

View file

@ -20,7 +20,7 @@ namespace PKHeX.Core
IEncounterable? deferred = null;
IEncounterable? partial = null;
if (pkm.WasEvent || pkm.WasEventEgg || pkm.WasLink)
if (pkm.FatefulEncounter || pkm.Met_Location == Locations.LinkGift6)
{
foreach (var z in GetValidGifts(pkm, chain))
{
@ -46,7 +46,7 @@ namespace PKHeX.Core
}
}
if (pkm.WasBredEgg)
if (Locations.IsEggLocationBred6(pkm.Egg_Location))
{
foreach (var z in GenerateEggs(pkm, 6))
{ yield return z; ++ctr; }

View file

@ -50,7 +50,7 @@ namespace PKHeX.Core
private static IEnumerable<IEncounterable> GetEncountersGG(PKM pkm, IReadOnlyList<EvoCriteria> chain)
{
int ctr = 0;
if (pkm.WasEvent)
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
{ yield return z; ++ctr; }
@ -105,14 +105,14 @@ namespace PKHeX.Core
private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, IReadOnlyList<EvoCriteria> chain)
{
int ctr = 0;
if (pkm.WasEvent || pkm.WasEventEgg)
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
if (pkm.WasBredEgg)
if (Locations.IsEggLocationBred6(pkm.Egg_Location))
{
foreach (var z in GenerateEggs(pkm, 7))
{ yield return z; ++ctr; }

View file

@ -24,14 +24,14 @@ namespace PKHeX.Core
private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, IReadOnlyList<EvoCriteria> chain)
{
int ctr = 0;
if (pkm.WasEvent || pkm.WasEventEgg)
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
if (pkm.WasBredEgg)
if (Locations.IsEggLocationBred6(pkm.Egg_Location))
{
foreach (var z in GenerateEggs(pkm, 8))
{ yield return z; ++ctr; }

View file

@ -123,22 +123,48 @@ namespace PKHeX.Core
private static void VerifyWithoutEncounter(PKM pkm, LegalInfo info)
{
info.EncounterMatch = new EncounterInvalid(pkm);
string hint = GetHintWhyNotFound(pkm);
string hint = GetHintWhyNotFound(pkm, info.EncounterMatch.Generation);
info.Parse.Add(new CheckResult(Severity.Invalid, hint, CheckIdentifier.Encounter));
VerifyRelearnMoves.VerifyRelearn(pkm, info.EncounterOriginal, info.Relearn);
info.Moves = VerifyCurrentMoves.VerifyMoves(pkm, info);
}
private static string GetHintWhyNotFound(PKM pkm)
private static string GetHintWhyNotFound(PKM pkm, int gen)
{
if (pkm.WasGiftEgg)
if (WasGiftEgg(pkm, gen, pkm.Egg_Location))
return LEncGift;
if (pkm.WasEventEgg)
if (WasEventEgg(pkm, gen))
return LEncGiftEggEvent;
if (pkm.WasEvent)
if (WasEvent(pkm, gen))
return LEncGiftNotFound;
return LEncInvalid;
}
private static bool WasGiftEgg(PKM pkm, int gen, int loc) => !pkm.FatefulEncounter && gen switch
{
3 => pkm.IsEgg && pkm.Met_Location == 253, // Gift Egg, indistinguible from normal eggs after hatch
4 => Legal.GiftEggLocation4.Contains(loc) || (pkm.Format != 4 && (loc == Locations.Faraway4 && pkm.HGSS)),
5 => loc is Locations.Breeder5,
_ => loc is Locations.Breeder6,
};
private static bool WasEventEgg(PKM pkm, int gen) => gen switch
{
// Event Egg, indistinguible from normal eggs after hatch
// can't tell after transfer
3 => pkm.Format == 3 && pkm.IsEgg && pkm.Met_Location == 255,
// Manaphy was the only generation 4 released event egg
_ => pkm.Egg_Location is not 0 && pkm.FatefulEncounter,
};
private static bool WasEvent(PKM pkm, int gen) => pkm.FatefulEncounter || gen switch
{
3 => (pkm.Met_Location == 255 && pkm.Format == 3),
4 => (Locations.IsEventLocation4(pkm.Met_Location) && pkm.Format == 4),
>=5 => Locations.IsEventLocation5(pkm.Met_Location),
_ => false,
};
}
}

View file

@ -510,12 +510,13 @@ namespace PKHeX.Core
else if (enc is not EncounterEgg)
{
// Event eggs cannot inherit moves from parents; they are not bred.
var gift = enc is EncounterStatic {Gift: true}; // otherwise, EncounterInvalid
foreach (int m in RegularEggMovesLearned)
{
if (learnInfo.EggMovesLearned.Contains(m))
res[m] = new CheckMoveResult(res[m], Invalid, pkm.WasGiftEgg ? LMoveEggMoveGift : LMoveEggInvalidEvent, CurrentMove);
res[m] = new CheckMoveResult(res[m], Invalid, gift ? LMoveEggMoveGift : LMoveEggInvalidEvent, CurrentMove);
else if (learnInfo.LevelUpEggMoves.Contains(m))
res[m] = new CheckMoveResult(res[m], Invalid, pkm.WasGiftEgg ? LMoveEggInvalidEventLevelUpGift : LMoveEggInvalidEventLevelUp, CurrentMove);
res[m] = new CheckMoveResult(res[m], Invalid, gift ? LMoveEggInvalidEventLevelUpGift : LMoveEggInvalidEventLevelUp, CurrentMove);
}
}
}

View file

@ -46,6 +46,8 @@ namespace PKHeX.Core
/// </remarks>
public IEncounterable EncounterOriginal => Info.EncounterOriginal;
public readonly SlotOrigin SlotOrigin;
/// <summary>
/// Indicates if all checks ran to completion.
/// </summary>
@ -67,23 +69,27 @@ namespace PKHeX.Core
/// </summary>
/// <param name="pk">Input data to check</param>
/// <param name="table"><see cref="SaveFile"/> specific personal data</param>
public LegalityAnalysis(PKM pk, PersonalTable table) : this(pk, table.GetFormEntry(pk.Species, pk.Form)) { }
/// <param name="source">Details about where the <see cref="pk"/> originated from.</param>
public LegalityAnalysis(PKM pk, PersonalTable table, SlotOrigin source = SlotOrigin.Party) : this(pk, table.GetFormEntry(pk.Species, pk.Form), source) { }
/// <summary>
/// Checks the input <see cref="PKM"/> data for legality.
/// </summary>
/// <param name="pk">Input data to check</param>
public LegalityAnalysis(PKM pk) : this(pk, pk.PersonalInfo) { }
/// <param name="source">Details about where the <see cref="pk"/> originated from.</param>
public LegalityAnalysis(PKM pk, SlotOrigin source = SlotOrigin.Party) : this(pk, pk.PersonalInfo, source) { }
/// <summary>
/// Checks the input <see cref="PKM"/> data for legality.
/// </summary>
/// <param name="pk">Input data to check</param>
/// <param name="pi">Personal info to parse with</param>
public LegalityAnalysis(PKM pk, PersonalInfo pi)
/// <param name="source">Details about where the <see cref="pk"/> originated from.</param>
public LegalityAnalysis(PKM pk, PersonalInfo pi, SlotOrigin source = SlotOrigin.Party)
{
pkm = pk;
PersonalInfo = pi;
SlotOrigin = source;
if (pkm.Format <= 2) // prior to storing GameVersion
pkm.TradebackStatus = GBRestrictions.GetTradebackStatusInitial(pkm);
@ -121,14 +127,16 @@ namespace PKHeX.Core
System.Diagnostics.Debug.WriteLine(e.Message);
Valid = false;
var moves = Info.Moves;
// Moves and Relearn arrays can potentially be empty on error.
// ReSharper disable once ConstantNullCoalescingCondition
for (int i = 0; i < Info.Moves.Length; i++)
Info.Moves[i] ??= new CheckMoveResult(MoveSource.None, pkm.Format, Severity.Indeterminate, L_AError, CheckIdentifier.CurrentMove);
for (int i = 0; i < moves.Length; i++)
moves[i] ??= new CheckMoveResult(MoveSource.None, pkm.Format, Severity.Indeterminate, L_AError, CheckIdentifier.CurrentMove);
var relearn = Info.Relearn;
// ReSharper disable once ConstantNullCoalescingCondition
for (int i = 0; i < Info.Relearn.Length; i++)
Info.Relearn[i] ??= new CheckResult(Severity.Indeterminate, L_AError, CheckIdentifier.CurrentMove);
for (int i = 0; i < relearn.Length; i++)
relearn[i] ??= new CheckResult(Severity.Indeterminate, L_AError, CheckIdentifier.RelearnMove);
AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc);
}

View file

@ -18,6 +18,9 @@
public const int LinkTrade5NPC = 30002;
public const int LinkTrade6NPC = 30001;
public const int Breeder5 = 60003;
public const int Breeder6 = 60004;
public const int PokeWalker4 = 233;
public const int Ranger4 = 3001;
public const int Faraway4 = 3002;
@ -109,6 +112,7 @@
public static bool IsPtHGSSLocation(int location) => location is > 111 and < 2000;
public static bool IsPtHGSSLocationEgg(int location) => location is > 2010 and < 3000;
public static bool IsEventLocation4(int location) => location is >= 3000 and <= 3076;
public static bool IsEventLocation5(int location) => location is > 40000 and < 50000;
private const int SafariLocation_RSE = 57;
@ -117,5 +121,15 @@
private const int MarshLocation_DPPt = 52;
public static bool IsSafariZoneLocation3(int loc) => loc is SafariLocation_RSE or SafariLocation_FRLG;
public static bool IsSafariZoneLocation4(int loc) => loc is MarshLocation_DPPt or SafariLocation_HGSS;
public static bool IsEggLocationBred4(int loc, GameVersion ver)
{
if (loc is Daycare4 or LinkTrade4)
return true;
return loc == Faraway4 && ver is GameVersion.Pt or GameVersion.HG or GameVersion.SS;
}
public static bool IsEggLocationBred5(int loc) => loc is Daycare5 or LinkTrade5;
public static bool IsEggLocationBred6(int loc) => loc is Daycare5 or LinkTrade6;
}
}

View file

@ -274,29 +274,5 @@ namespace PKHeX.Core
_ => Locations.Transfer4
};
}
internal static int[] RemoveMovesHM45(int[] moves)
{
var banned = GetFavorableHMBanlist(moves);
for (int i = 0; i < 4; i++)
{
if (banned.Contains(moves[i]))
moves[i] = 0;
}
return moves;
}
/// <summary>
/// Transfer via advantageous game
/// </summary>
/// <param name="moves">Current moves</param>
/// <returns>Preferred move ban list</returns>
private static ICollection<int> GetFavorableHMBanlist(int[] moves)
{
// if has defog, return ban list with whirlpool
return moves.Contains((int)Move.Defog) ? HM_HGSS : HM_DPPt;
}
}
}

View file

@ -154,7 +154,7 @@ namespace PKHeX.Core
case Shaymin:
case Furfrou:
case Hoopa:
if (form != 0 && pkm.Box > -1 && pkm.Format <= 6) // has form but stored in box
if (form != 0 && data.SlotOrigin is not SlotOrigin.Party && pkm.Format <= 6) // has form but stored in box
return GetInvalid(LFormParty);
break;
}

View file

@ -314,7 +314,7 @@ namespace PKHeX.Core
data.AddLine(commonResult);
}
private static bool WasTradedSWSHEgg(PKM pkm) => pkm.Gen8 && pkm.WasBredEgg;
private static bool WasTradedSWSHEgg(PKM pkm) => pkm.SWSH && pkm.Egg_Location is Locations.LinkTrade6;
private void VerifyHTMemoryTransferTo7(LegalityAnalysis data, PKM pkm, LegalInfo Info)
{

View file

@ -273,12 +273,20 @@ namespace PKHeX.Core
data.AddLine(GetInvalid(LPIDTypeMismatch, PID));
}
var result = pkm.FatefulEncounter != pkm.WasLink
bool shouldHave = GetFatefulState(g);
var result = pkm.FatefulEncounter == shouldHave
? GetValid(LFatefulMystery, Fateful)
: GetInvalid(LFatefulMysteryMissing, Fateful);
data.AddLine(result);
}
private static bool GetFatefulState(MysteryGift g)
{
if (g is WC6 {IsLinkGift: true})
return false; // Pokémon Link fake-gifts do not have Fateful
return true;
}
private static void VerifyReceivability(LegalityAnalysis data, MysteryGift g)
{
var pkm = data.pkm;

View file

@ -264,6 +264,8 @@ namespace PKHeX.Core
}
}
public bool IsLinkGift => MetLocation == Locations.LinkGift6;
public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
{
if (!IsPokémon)
@ -329,7 +331,7 @@ namespace PKHeX.Core
OT_Memory = OT_Memory,
OT_TextVar = OT_TextVar,
OT_Feeling = OT_Feeling,
FatefulEncounter = MetLocation != 30011, // Link gifts do not set fateful encounter
FatefulEncounter = !IsLinkGift, // Link gifts do not set fateful encounter
EVs = EVs,
};

View file

@ -44,7 +44,7 @@ namespace PKHeX.Core
public BK4() : this(new byte[PokeCrypto.SIZE_4STORED]) { }
public override PKM Clone() => new BK4((byte[])Data.Clone()){Identifier = Identifier};
public override PKM Clone() => new BK4((byte[])Data.Clone());
public string GetString(int Offset, int Count) => StringConverter4.GetBEString4(Data, Offset, Count);
private static byte[] SetString(string value, int maxLength) => StringConverter4.SetBEString4(value, maxLength);

View file

@ -23,7 +23,7 @@ namespace PKHeX.Core
public override PersonalInfo PersonalInfo => PersonalTable.RS[Species];
public CK3(byte[] data) : base(data) { }
public CK3() : this(new byte[PokeCrypto.SIZE_3CSTORED]) { }
public override PKM Clone() => new CK3((byte[])Data.Clone()) {Identifier = Identifier};
public override PKM Clone() => new CK3((byte[])Data.Clone());
private string GetString(int Offset, int Count) => StringConverter3.GetBEString3(Data, Offset, Count);
private static byte[] SetString(string value, int maxLength) => StringConverter3.SetBEString3(value, maxLength);

View file

@ -39,7 +39,7 @@ namespace PKHeX.Core
return data;
}
public override PKM Clone() => new PB7((byte[])Data.Clone()){Identifier = Identifier};
public override PKM Clone() => new PB7((byte[])Data.Clone());
private string GetString(int Offset, int Count) => StringConverter.GetString7b(Data, Offset, Count);
private static byte[] SetString(string value, int maxLength) => StringConverter.SetString7b(value, maxLength);

View file

@ -28,7 +28,6 @@ namespace PKHeX.Core
public override PKM Clone() => new PK1((byte[])Data.Clone(), Japanese)
{
Identifier = Identifier,
OT_Trash = RawOT,
Nickname_Trash = RawNickname,
};

View file

@ -28,7 +28,6 @@ namespace PKHeX.Core
public override PKM Clone() => new PK2((byte[])Data.Clone(), Japanese)
{
Identifier = Identifier,
OT_Trash = RawOT,
Nickname_Trash = RawNickname,
IsEgg = IsEgg,

View file

@ -32,7 +32,7 @@ namespace PKHeX.Core
{
// Don't use the byte[] constructor, the DecryptIfEncrypted call is based on checksum.
// An invalid checksum will shuffle the data; we already know it's un-shuffled. Set up manually.
var pk = new PK3 {Identifier = Identifier};
var pk = new PK3();
Data.CopyTo(pk.Data, 0);
return pk;
}

View file

@ -29,7 +29,7 @@ namespace PKHeX.Core
return data;
}
public override PKM Clone() => new PK4((byte[])Data.Clone()){Identifier = Identifier};
public override PKM Clone() => new PK4((byte[])Data.Clone());
private string GetString(int Offset, int Count) => StringConverter4.GetString4(Data, Offset, Count);
private static byte[] SetString(string value, int maxLength) => StringConverter4.SetString4(value, maxLength);
@ -416,7 +416,13 @@ namespace PKHeX.Core
pk5.Met_Level = pk5.CurrentLevel;
// Remove HM moves; Defog should be kept if both are learned.
pk5.Moves = Legal.RemoveMovesHM45(pk5.Moves);
// if has defog, remove whirlpool.
bool hasDefog = HasMove((int) Move.Defog);
var banned = hasDefog ? Legal.HM_HGSS : Legal.HM_DPPt;
if (banned.Contains(Move1)) Move1 = 0;
if (banned.Contains(Move2)) Move2 = 0;
if (banned.Contains(Move3)) Move3 = 0;
if (banned.Contains(Move4)) Move4 = 0;
pk5.FixMoves();
pk5.RefreshChecksum();

View file

@ -33,7 +33,7 @@ namespace PKHeX.Core
return data;
}
public override PKM Clone() => new PK5((byte[])Data.Clone()){Identifier = Identifier};
public override PKM Clone() => new PK5((byte[])Data.Clone());
private string GetString(int Offset, int Count) => StringConverter.GetString5(Data, Offset, Count);
private static byte[] SetString(string value, int maxLength) => StringConverter.SetString5(value, maxLength);

View file

@ -27,7 +27,7 @@ namespace PKHeX.Core
return data;
}
public override PKM Clone() => new PK6((byte[])Data.Clone()){Identifier = Identifier};
public override PKM Clone() => new PK6((byte[])Data.Clone());
private string GetString(int Offset, int Count) => StringConverter.GetString6(Data, Offset, Count);
private static byte[] SetString(string value, int maxLength) => StringConverter.SetString6(value, maxLength);

View file

@ -28,7 +28,7 @@ namespace PKHeX.Core
return data;
}
public override PKM Clone() => new PK7((byte[])Data.Clone()){Identifier = Identifier};
public override PKM Clone() => new PK7((byte[])Data.Clone());
private string GetString(int Offset, int Count) => StringConverter.GetString7(Data, Offset, Count);
private byte[] SetString(string value, int maxLength, bool chinese = false) => StringConverter.SetString7(value, maxLength, Language, chinese: chinese);

View file

@ -57,7 +57,7 @@ namespace PKHeX.Core
set { if (CurrentHandler == 0) OT_Friendship = value; else HT_Friendship = value; }
}
public override PKM Clone() => new PK8((byte[])Data.Clone()) { Identifier = Identifier };
public override PKM Clone() => new PK8((byte[])Data.Clone());
private string GetString(int Offset, int Count) => StringConverter.GetString7b(Data, Offset, Count);
private static byte[] SetString(string value, int maxLength) => StringConverter.SetString7b(value, maxLength);
@ -69,10 +69,7 @@ namespace PKHeX.Core
public override byte[] Nickname_Trash { get => GetData(0x58, 24); set { if (value.Length == 24) value.CopyTo(Data, 0x58); } }
public override byte[] HT_Trash { get => GetData(0xA8, 24); set { if (value.Length == 24) value.CopyTo(Data, 0xA8); } }
public override byte[] OT_Trash { get => GetData(0xF8, 24); set { if (value.Length == 24) value.CopyTo(Data, 0xF8); } }
public override bool WasLink => Met_Location == Locations.LinkGift6 && Gen6;
public override bool WasEvent => Locations.IsEventLocation5(Met_Location) || FatefulEncounter;
public override bool WasEventEgg => Generation < 5 ? base.WasEventEgg : (Locations.IsEventLocation5(Egg_Location) || (FatefulEncounter && Egg_Location == Locations.LinkTrade6)) && Met_Level == 1;
// Maximums
public override int MaxIV => 31;
public override int MaxEV => 252;

View file

@ -19,9 +19,6 @@ namespace PKHeX.Core
// Internal Attributes set on creation
public readonly byte[] Data; // Raw Storage
public string? Identifier; // User or Form Custom Attribute
public int Box { get; set; } = -1; // Batch Editor
public int Slot { get; set; } = -1; // Batch Editor
protected PKM(byte[] data) => Data = data;
protected PKM(int size) => Data = new byte[size];
@ -248,8 +245,6 @@ namespace PKHeX.Core
public int SpecForm { get => Species + (Form << 11); set { Species = value & 0x7FF; Form = value >> 11; } }
public virtual int SpriteItem => HeldItem;
public virtual bool IsShiny => TSV == PSV;
public StorageSlotFlag StorageFlags { get; internal set; }
public bool Locked => StorageFlags.HasFlagFast(StorageSlotFlag.Locked);
public int TrainerID7 { get => (int)((uint)(TID | (SID << 16)) % 1000000); set => SetID7(TrainerSID7, value); }
public int TrainerSID7 { get => (int)((uint)(TID | (SID << 16)) / 1000000); set => SetID7(value, TrainerID7); }
@ -298,7 +293,7 @@ namespace PKHeX.Core
public bool LGPE => Version is (int)GP or (int)GE;
public bool SWSH => Version is (int)SW or (int)SH;
protected bool PtHGSS => Pt || HGSS;
protected internal bool PtHGSS => Pt || HGSS;
public bool GO_LGPE => GO && Met_Location == Locations.GO7;
public bool GO_HOME => GO && Met_Location == Locations.GO8;
public bool VC => VC1 || VC2;
@ -506,81 +501,16 @@ namespace PKHeX.Core
public TradebackType TradebackStatus { get; set; } = TradebackType.Any;
public bool Gen1_NotTradeback => TradebackStatus == TradebackType.Gen1_NotTradeback;
public bool Gen2_NotTradeback => TradebackStatus == TradebackType.Gen2_NotTradeback;
public virtual bool WasLink => false;
public bool WasEgg
{
get
{
int loc = Egg_Location;
return Generation switch
{
4 => (Legal.EggLocations4.Contains(loc) || (Species == (int) Core.Species.Manaphy && loc == Locations.Ranger4) || (loc == Locations.Faraway4 && PtHGSS)), // faraway
5 => Legal.EggLocations5.Contains(loc),
6 => Legal.EggLocations6.Contains(loc),
7 => Legal.EggLocations7.Contains(loc),
8 => Legal.EggLocations8.Contains(loc),
// Gen 1/2 and pal park Gen 3
_ => false
};
}
}
public bool WasBredEgg
{
get
{
int loc = Egg_Location;
return Generation switch
{
4 => loc is Locations.Daycare4 or Locations.LinkTrade4 || (loc == Locations.Faraway4 && PtHGSS),
5 => loc is Locations.Daycare5 or Locations.LinkTrade5,
6 or 7 or 8 => loc is Locations.Daycare5 or Locations.LinkTrade6,
_ => false,// Gen 1/2 and pal park Gen 3
};
}
}
public virtual bool WasGiftEgg
{
get
{
if (!WasEgg)
return false;
int loc = Egg_Location;
return Generation switch
{
4 => Legal.GiftEggLocation4.Contains(loc) || (loc == Locations.Faraway4 && HGSS),
5 => loc == 60003,
6 or 7 or 8 => loc == 60004,
_ => false,
};
}
}
public virtual bool WasEvent => Locations.IsEventLocation5(Met_Location) || FatefulEncounter;
public virtual bool WasEventEgg
{
get
{
if (Gen4)
return WasEgg && Species == (int) Core.Species.Manaphy;
// Gen5+
if (Met_Level != 1)
return false;
int loc = Egg_Location;
return Locations.IsEventLocation5(loc) || (FatefulEncounter && loc != 0);
}
}
// Misc Egg Facts
public bool WasEgg => IsEgg || Egg_Location != 0;
public bool WasTradedEgg => Egg_Location == GetTradedEggLocation();
public bool IsTradedEgg => Met_Location == GetTradedEggLocation();
private int GetTradedEggLocation() => Locations.TradedEggLocation(Generation);
public virtual bool IsUntraded => false;
public bool IsNative => Generation == Format;
public bool IsOriginValid => Species <= Legal.GetMaxSpeciesOrigin(Format);
public bool IsOriginValid => Species <= MaxSpeciesID;
/// <summary>
/// Checks if the <see cref="PKM"/> could inhabit a set of games.

View file

@ -26,7 +26,6 @@ namespace PKHeX.Core
public override PKM Clone() => new SK2((byte[])Data.Clone(), Japanese)
{
Identifier = Identifier,
IsEgg = IsEgg,
};

View file

@ -32,6 +32,7 @@ namespace PKHeX.Core.Searching
public CloneDetectionMethod SearchClones { private get; set; }
public IList<string> BatchInstructions { private get; init; } = Array.Empty<string>();
private StringInstruction[] BatchFilters { get; set; } = Array.Empty<StringInstruction>();
public readonly List<int> Moves = new();
@ -59,80 +60,123 @@ namespace PKHeX.Core.Searching
/// <returns>Search results that match all criteria</returns>
public IEnumerable<PKM> Search(IEnumerable<PKM> list)
{
var result = SearchSimple(list);
result = SearchIntermediate(result);
result = SearchComplex(result);
foreach (var filter in ExtraFilters)
result = result.Where(filter);
BatchFilters = StringInstruction.GetFilters(BatchInstructions).ToArray();
var result = SearchInner(list);
if (SearchClones != CloneDetectionMethod.None)
result = SearchUtil.GetClones(result, SearchClones);
{
var method = SearchUtil.GetCloneDetectMethod(SearchClones);
result = SearchUtil.GetExtraClones(result, method);
}
return result;
}
private IEnumerable<PKM> SearchSimple(IEnumerable<PKM> res)
/// <summary>
/// Searches the input list, filtering out entries as specified by the settings.
/// </summary>
/// <param name="list">List of entries to search</param>
/// <returns>Search results that match all criteria</returns>
public IEnumerable<SlotCache> Search(IEnumerable<SlotCache> list)
{
if (Format > 0)
res = SearchUtil.FilterByFormat(res, Format, SearchFormat);
if (Species > -1)
res = res.Where(pk => pk.Species == Species);
if (Ability > -1)
res = res.Where(pk => pk.Ability == Ability);
if (Nature > -1)
res = res.Where(pk => pk.Nature == Nature);
if (Item > -1)
res = res.Where(pk => pk.HeldItem == Item);
if (Version > -1)
res = res.Where(pk => pk.Version == Version);
BatchFilters = StringInstruction.GetFilters(BatchInstructions).ToArray();
var result = SearchInner(list);
return res;
if (SearchClones != CloneDetectionMethod.None)
{
var method = SearchUtil.GetCloneDetectMethod(SearchClones);
string GetHash(SlotCache z) => method(z.Entity);
result = SearchUtil.GetExtraClones(result, GetHash);
}
return result;
}
private IEnumerable<PKM> SearchIntermediate(IEnumerable<PKM> res)
private IEnumerable<PKM> SearchInner(IEnumerable<PKM> list)
{
if (Generation > 0)
res = SearchUtil.FilterByGeneration(res, Generation);
if (Moves.Count > 0)
res = SearchUtil.FilterByMoves(res, Moves);
if (HiddenPowerType > -1)
res = res.Where(pk => pk.HPType == HiddenPowerType);
if (SearchShiny != null)
res = res.Where(pk => pk.IsShiny == SearchShiny);
if (IVType > 0)
res = SearchUtil.FilterByIVs(res, IVType);
if (EVType > 0)
res = SearchUtil.FilterByEVs(res, EVType);
return res;
foreach (var pk in list)
{
if (IsSearchMatch(pk))
continue;
yield return pk;
}
}
private IEnumerable<PKM> SearchComplex(IEnumerable<PKM> res)
private IEnumerable<SlotCache> SearchInner(IEnumerable<SlotCache> list)
{
if (SearchEgg != null)
res = FilterResultEgg(res);
if (Level is not null or 0)
res = SearchUtil.FilterByLevel(res, SearchLevel, (int)Level);
if (SearchLegal != null)
res = res.Where(pk => new LegalityAnalysis(pk).Valid == SearchLegal);
if (BatchInstructions.Count != 0)
res = SearchUtil.FilterByBatchInstruction(res, BatchInstructions);
return res;
foreach (var entry in list)
{
var pk = entry.Entity;
if (IsSearchMatch(pk))
continue;
yield return entry;
}
}
private IEnumerable<PKM> FilterResultEgg(IEnumerable<PKM> res)
private bool IsSearchMatch(PKM pk)
{
if (!SearchSimple(pk))
return false;
if (!SearchIntermediate(pk))
return false;
if (!SearchComplex(pk))
return false;
foreach (var filter in ExtraFilters)
{
if (!filter(pk))
return false;
}
return true;
}
private bool SearchSimple(PKM pk)
{
if (Format > 0 && !SearchUtil.SatisfiesFilterFormat(pk, Format, SearchFormat))
return false;
if (Species > -1 && pk.Species != Species)
return false;
if (Ability > -1 && pk.Ability != Ability)
return false;
if (Nature > -1 && pk.StatNature != Nature)
return false;
if (Item > -1 && pk.HeldItem != Item)
return false;
if (Version > -1 && pk.Version != Version)
return false;
return true;
}
private bool SearchIntermediate(PKM pk)
{
if (Generation > 0 && !SearchUtil.SatisfiesFilterGeneration(pk, Generation)) return false;
if (Moves.Count > 0 && SearchUtil.SatisfiesFilterMoves(pk, Moves)) return false;
if (HiddenPowerType > -1 && pk.HPType != HiddenPowerType) return false;
if (SearchShiny != null && pk.IsShiny != SearchShiny) return false;
if (IVType > 0 && SearchUtil.SatisfiesFilterIVs(pk, IVType)) return false;
if (EVType > 0 && SearchUtil.SatisfiesFilterEVs(pk, EVType)) return false;
return true;
}
private bool SearchComplex(PKM pk)
{
if (SearchEgg != null && !FilterResultEgg(pk)) return false;
if (Level is not null or 0 && !SearchUtil.SatisfiesFilterLevel(pk, SearchLevel, (int) Level)) return false;
if (SearchLegal != null && new LegalityAnalysis(pk).Valid != SearchLegal) return false;
if (BatchFilters.Length != 0 && !SearchUtil.SatisfiesFilterBatchInstruction(pk, BatchFilters)) return false;
return true;
}
private bool FilterResultEgg(PKM pk)
{
if (SearchEgg == false)
return res.Where(pk => !pk.IsEgg);
return !pk.IsEgg;
if (ESV != null)
return res.Where(pk => pk.IsEgg && pk.PSV == ESV);
return res.Where(pk => pk.IsEgg);
return pk.IsEgg && pk.PSV == ESV;
return pk.IsEgg;
}
public IReadOnlyList<GameVersion> GetVersions(SaveFile sav) => GetVersions(sav, GetFallbackVersion(sav));

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core.Searching
{
@ -9,90 +8,79 @@ namespace PKHeX.Core.Searching
/// </summary>
public static class SearchUtil
{
public static IEnumerable<PKM> FilterByFormat(IEnumerable<PKM> res, int format, SearchComparison formatOperand)
public static bool SatisfiesFilterFormat(PKM pk, int format, SearchComparison formatOperand)
{
switch (formatOperand)
{
case SearchComparison.GreaterThanEquals:
res = res.Where(pk => pk.Format >= format); break;
case SearchComparison.Equals:
res = res.Where(pk => pk.Format == format); break;
case SearchComparison.LessThanEquals:
res = res.Where(pk => pk.Format <= format); break;
default:
return res; /* Do nothing */
case SearchComparison.GreaterThanEquals when pk.Format < format:
case SearchComparison.Equals when pk.Format == format:
case SearchComparison.LessThanEquals when pk.Format > format:
return false;
}
// Might need to clamp down further for generations that cannot exist in the current format.
return format switch
{
<= 2 => res.Where(pk => pk.Format <= 2), // 1-2
<= 6 => res.Where(pk => pk.Format >= 3), // 3-6
_ => res
<= 2 => pk.Format <= 2, // 1-2
<= 6 => pk.Format >= 3, // 3-6
_ => true
};
}
public static IEnumerable<PKM> FilterByGeneration(IEnumerable<PKM> res, int generation) => generation switch
public static bool SatisfiesFilterGeneration(PKM pk, int generation) => generation switch
{
1 => res.Where(pk => pk.VC || pk.Format < 3),
2 => res.Where(pk => pk.VC || pk.Format < 3),
_ => res.Where(pk => pk.Generation == generation)
1 => pk.VC || pk.Format < 3,
2 => pk.VC || pk.Format < 3,
_ => pk.Generation == generation,
};
public static IEnumerable<PKM> FilterByLevel(IEnumerable<PKM> res, SearchComparison option, int level)
public static bool SatisfiesFilterLevel(PKM pk, SearchComparison option, int level)
{
if (level > 100)
return res;
return true; // why???
return option switch
{
SearchComparison.LessThanEquals => res.Where(pk => pk.Stat_Level <= level),
SearchComparison.Equals => res.Where(pk => pk.Stat_Level == level),
SearchComparison.GreaterThanEquals => res.Where(pk => pk.Stat_Level >= level),
_ => res
SearchComparison.LessThanEquals => pk.Stat_Level <= level,
SearchComparison.Equals => pk.Stat_Level == level,
SearchComparison.GreaterThanEquals => pk.Stat_Level >= level,
_ => true
};
}
public static IEnumerable<PKM> FilterByEVs(IEnumerable<PKM> res, int option) => option switch
public static bool SatisfiesFilterEVs(PKM pk, int option) => option switch
{
1 => res.Where(pk => pk.EVTotal == 0), // None (0)
2 => res.Where(pk => pk.EVTotal is (not 0) and < 128), // Some (127-1)
3 => res.Where(pk => pk.EVTotal is >= 128 and < 508), // Half (128-507)
4 => res.Where(pk => pk.EVTotal >= 508), // Full (508+)
_ => res
1 => pk.EVTotal == 0, // None (0)
2 => pk.EVTotal is (not 0) and < 128, // Some (127-1)
3 => pk.EVTotal is >= 128 and < 508, // Half (128-507)
4 => pk.EVTotal >= 508, // Full (508+)
_ => true
};
public static IEnumerable<PKM> FilterByIVs(IEnumerable<PKM> res, int option) => option switch
public static bool SatisfiesFilterIVs(PKM pk, int option) => option switch
{
1 => res.Where(pk => pk.IVTotal <= 90), // <= 90
2 => res.Where(pk => pk.IVTotal is > 90 and <= 120), // 91-120
3 => res.Where(pk => pk.IVTotal is > 120 and <= 150), // 121-150
4 => res.Where(pk => pk.IVTotal is > 150 and < 180), // 151-179
5 => res.Where(pk => pk.IVTotal >= 180), // 180+
6 => res.Where(pk => pk.IVTotal == 186), // == 186
_ => res
1 => pk.IVTotal <= 90, // <= 90
2 => pk.IVTotal is > 90 and <= 120, // 91-120
3 => pk.IVTotal is > 120 and <= 150, // 121-150
4 => pk.IVTotal is > 150 and < 180, // 151-179
5 => pk.IVTotal >= 180, // 180+
6 => pk.IVTotal == 186, // == 186
_ => true
};
public static IEnumerable<PKM> FilterByMoves(IEnumerable<PKM> res, IEnumerable<int> requiredMoves)
public static bool SatisfiesFilterMoves(PKM pk, IEnumerable<int> requiredMoves)
{
var moves = new HashSet<int>(requiredMoves);
int count = moves.Count;
return res.Where(pk =>
pk.Moves.Where(z => z > 0)
.Count(moves.Contains) == count
);
foreach (var m in requiredMoves)
{
if (!pk.HasMove(m))
return false;
}
return true;
}
public static IEnumerable<PKM> FilterByBatchInstruction(IEnumerable<PKM> res, IList<string> inputInstructions)
public static bool SatisfiesFilterBatchInstruction(PKM pk, IReadOnlyList<StringInstruction> filters)
{
if (inputInstructions.All(string.IsNullOrWhiteSpace))
return res; // none specified;
var lines = inputInstructions.Where(z => !string.IsNullOrWhiteSpace(z));
var filters = StringInstruction.GetFilters(lines).ToArray();
BatchEditing.ScreenStrings(filters);
return res.Where(pkm => BatchEditing.IsFilterMatch(filters, pkm)); // Compare across all filters
return BatchEditing.IsFilterMatch(filters, pk); // Compare across all filters
}
public static Func<PKM, string> GetCloneDetectMethod(CloneDetectionMethod method) => method switch
@ -118,25 +106,22 @@ namespace PKHeX.Core.Searching
public static IEnumerable<PKM> GetClones(IEnumerable<PKM> res, CloneDetectionMethod type = CloneDetectionMethod.HashDetails)
{
var method = GetCloneDetectMethod(type);
return GetClones(res, method);
return GetExtraClones(res, method);
}
public static IEnumerable<PKM> GetClones(IEnumerable<PKM> res, Func<PKM, string> method)
public static IEnumerable<T> GetExtraClones<T>(IEnumerable<T> db, Func<T, string> method)
{
return res
.GroupBy(method)
.Where(grp => grp.Count() > 1)
.SelectMany(z => z);
}
public static IEnumerable<PKM> GetExtraClones(IEnumerable<PKM> db)
{
return GetExtraClones(db, HashByDetails);
}
public static IEnumerable<PKM> GetExtraClones(IEnumerable<PKM> db, Func<PKM, string> method)
{
return db.GroupBy(method).Where(grp => grp.Count() > 1).SelectMany(z => z.Skip(1));
var hs = new HashSet<string>();
var result = new List<T>();
foreach (var t in db)
{
var hash = method(t);
if (hs.Contains(hash))
continue;
hs.Add(hash);
result.Add(t);
}
return result;
}
}
}

View file

@ -24,9 +24,6 @@
public sealed override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 3);
public sealed override int TSV => (TID ^ SID) >> 3;
public sealed override bool Japanese => Language == (int)LanguageID.Japanese;
public sealed override bool WasEvent => Met_Location == 255; // Fateful
public sealed override bool WasGiftEgg => IsEgg && Met_Location == 253; // Gift Egg, indistinguible from normal eggs after hatch
public sealed override bool WasEventEgg => IsEgg && Met_Location == 255; // Event Egg, indistinguible from normal eggs after hatch
public sealed override int Ability { get => ((PersonalInfoG3)PersonalInfo).GetAbility(AbilityBit); set { } }
public sealed override uint EncryptionConstant { get => PID; set { } }

View file

@ -48,10 +48,6 @@
public sealed override int CurrentHandler { get => 0; set { } }
public sealed override int AbilityNumber { get => 1 << PIDAbility; set { } }
// Legality Extensions
public sealed override bool WasEvent => (Met_Location is >= 3000 and <= 3076) || FatefulEncounter;
public sealed override bool WasEventEgg => WasEgg && Species == (int)Core.Species.Manaphy; // Manaphy was the only generation 4 released event egg
public abstract int ShinyLeaf { get; set; }
#region Ribbons

View file

@ -110,12 +110,7 @@ namespace PKHeX.Core
protected abstract bool TradeOT(ITrainerInfo tr);
protected abstract void TradeHT(ITrainerInfo tr);
// Legality Properties
public sealed override bool WasLink => Met_Location == Locations.LinkGift6 && Gen6;
public sealed override bool WasEvent => Locations.IsEventLocation5(Met_Location) || FatefulEncounter;
public sealed override bool WasEventEgg => Generation < 5 ? base.WasEventEgg : (Locations.IsEventLocation5(Egg_Location) || (FatefulEncounter && Egg_Location == Locations.LinkTrade6)) && Met_Level == 1;
// Maximums
public sealed override int MaxIV => 31;
public sealed override int MaxEV => 252;

View file

@ -118,6 +118,33 @@ namespace PKHeX.Core
.FinalSortBy();
}
/// <summary>
/// Sorts an <see cref="Enumerable"/> list of <see cref="PKM"/> objects based on the provided filter operations.
/// </summary>
/// <param name="list">Source list to sort</param>
/// <param name="sav">Save file destination</param>
/// <param name="check">Position check</param>
/// <param name="start">Starting position</param>
/// <returns>Enumerable list that is sorted</returns>
public static IEnumerable<PKM> BubbleUp(this IEnumerable<PKM> list, SaveFile sav, Func<int, bool> check, int start)
{
var matches = new List<PKM>();
var failures = new List<PKM>();
var ctr = start;
foreach (var x in list)
{
while (sav.IsSlotOverwriteProtected(ctr))
ctr++;
bool isMatch = check(ctr);
var arr = isMatch ? matches : failures;
arr.Add(x);
ctr++;
}
var result = matches.Concat(failures);
return result.InitialSortBy();
}
/// <summary>
/// Sorts an <see cref="Enumerable"/> list of <see cref="PKM"/> objects based on the provided filter operations.
/// </summary>

View file

@ -22,7 +22,7 @@ namespace PKHeX.Core
public override PersonalInfo PersonalInfo => PersonalTable.RS[Species];
public XK3(byte[] data) : base(data) { }
public XK3() : base(PokeCrypto.SIZE_3XSTORED) { }
public override PKM Clone() => new XK3((byte[])Data.Clone()){Identifier = Identifier, Purification = Purification};
public override PKM Clone() => new XK3((byte[])Data.Clone()){Purification = Purification};
private string GetString(int Offset, int Count) => StringConverter3.GetBEString3(Data, Offset, Count);
private static byte[] SetString(string value, int maxLength) => StringConverter3.SetBEString3(value, maxLength);

View file

@ -538,9 +538,9 @@ namespace PKHeX.Core
int skipped = 0;
for (int slot = 0; slot < BoxSlotCount; slot++)
{
var pk = value[index + slot];
if (!pk.StorageFlags.IsOverwriteProtected())
SetBoxSlotAtIndex(pk, box, slot);
var flags = GetSlotFlags(box, slot);
if (!flags.IsOverwriteProtected())
SetBoxSlotAtIndex(value[index + slot], box, slot);
else
++skipped;
}
@ -557,15 +557,10 @@ namespace PKHeX.Core
public void AddBoxData(IList<PKM> data, int box, int index)
{
var boxName = GetBoxName(box);
for (int slot = 0; slot < BoxSlotCount; slot++)
{
int i = slot + index;
data[i] = GetBoxSlotAtIndex(box, slot);
data[i].Identifier = $"{boxName}:{slot + 1:00}";
data[i].Box = box + 1;
data[i].Slot = slot + 1;
data[i].StorageFlags = GetSlotFlags(box, slot);
}
}
#endregion
@ -579,7 +574,6 @@ namespace PKHeX.Core
public bool IsSlotLocked(int index) => GetSlotFlags(index).HasFlagFast(StorageSlotFlag.Locked);
public bool IsSlotOverwriteProtected(int box, int slot) => GetSlotFlags(box, slot).IsOverwriteProtected();
public bool IsSlotOverwriteProtected(int index) => GetSlotFlags(index).IsOverwriteProtected();
public bool IsSlotOverwriteProtected(PKM pkm) => GetSlotFlags(pkm.Box, pkm.Slot).IsOverwriteProtected();
private const int StorageFullValue = -1;
public bool IsStorageFull => NextOpenBoxSlot() == StorageFullValue;
@ -754,7 +748,7 @@ namespace PKHeX.Core
/// <param name="sortMethod">Sorting logic required to order a <see cref="PKM"/> with respect to its peers; if not provided, will use a default sorting method.</param>
/// <param name="reverse">Reverse the sorting order</param>
/// <returns>Count of repositioned <see cref="PKM"/> slots.</returns>
public int SortBoxes(int BoxStart = 0, int BoxEnd = -1, Func<IEnumerable<PKM>, IEnumerable<PKM>>? sortMethod = null, bool reverse = false)
public int SortBoxes(int BoxStart = 0, int BoxEnd = -1, Func<IEnumerable<PKM>, int, IEnumerable<PKM>>? sortMethod = null, bool reverse = false)
{
var BD = BoxData;
int start = BoxSlotCount * BoxStart;
@ -762,9 +756,10 @@ namespace PKHeX.Core
if (BoxEnd >= BoxStart)
Section = Section.Take(BoxSlotCount * (BoxEnd - BoxStart + 1));
Func<PKM, bool> skip = IsSlotOverwriteProtected;
Section = Section.Where(z => !skip(z));
var Sorted = (sortMethod ?? PKMSorting.OrderBySpecies)(Section);
Func<int, bool> skip = IsSlotOverwriteProtected;
Section = Section.Where((_, i) => !skip(start + i));
var method = sortMethod ?? ((z, _) => z.OrderBySpecies());
var Sorted = method(Section, start);
if (reverse)
Sorted = Sorted.ReverseSort();
@ -921,6 +916,7 @@ namespace PKHeX.Core
var BD = BoxData;
var entryLength = GetDataForBox(BlankPKM).Length;
var pkdata = ArrayUtil.EnumerateSplit(data, entryLength);
pkdata.Select(GetPKM).CopyTo(BD, IsSlotOverwriteProtected, start);
BoxData = BD;
return true;

View file

@ -47,7 +47,7 @@ namespace PKHeX.Core
if (newIndex < 0)
continue;
Debug.WriteLine($"Repointing {pk.Nickname} from ({pk.Box}|{pk.Slot}) to {newIndex}");
Debug.WriteLine($"Re-pointing {pk.Nickname} from {index} to {newIndex}");
Debug.WriteLine($"{result[newIndex]}");
p[i] = start + newIndex;
}

View file

@ -31,30 +31,35 @@ namespace PKHeX.Core
{
get => new[]
{
GetTeam(0, 0),
GetTeam(0, 6 * PokeCrypto.SIZE_3PARTY),
GetTeam(0),
GetTeam(1),
};
set
{
SetTeam(value[0], 0);
SetTeam(value[1], 6 * PokeCrypto.SIZE_3PARTY);
SetTeam(value[1], 1);
}
}
public PK3[] GetTeam(int teamIndex, int ofs)
public PK3[] GetTeam(int teamIndex)
{
if ((uint)teamIndex > 2)
throw new ArgumentOutOfRangeException(nameof(teamIndex));
var ofs = (6 * PokeCrypto.SIZE_3PARTY) * teamIndex;
var team = new PK3[6];
for (int p = 0; p < 6; p++)
{
int offset = ofs + (PokeCrypto.SIZE_3PARTY * p);
team[p] = new PK3(Data.Slice(offset, PokeCrypto.SIZE_3PARTY)) { Identifier = $"Team {teamIndex}, Slot {p}" };
team[p] = new PK3(Data.Slice(offset, PokeCrypto.SIZE_3PARTY));
}
return team;
}
public void SetTeam(IReadOnlyList<PK3> team, int ofs)
public void SetTeam(IReadOnlyList<PK3> team, int teamIndex)
{
var ofs = (6 * PokeCrypto.SIZE_3PARTY) * teamIndex;
for (int p = 0; p < 6; p++)
{
int offset = ofs + (PokeCrypto.SIZE_3PARTY * p);

View file

@ -96,7 +96,7 @@ namespace PKHeX.Core
{
int offset = start + (PokeCrypto.SIZE_6PARTY * ((t * 6) + p));
offset += 8 * (((t * 6) + p) / 6); // 8 bytes padding between teams
team[p] = new PK6(Data.Slice(offset, PokeCrypto.SIZE_6PARTY)) { Identifier = $"Team {t}, Slot {p}" };
team[p] = new PK6(Data.Slice(offset, PokeCrypto.SIZE_6PARTY));
}
return team;

View file

@ -43,7 +43,7 @@ namespace PKHeX.Core
for (int p = 0; p < 6; p++)
{
int offset = ofs + (PokeCrypto.SIZE_6PARTY * p);
team[p] = new PK7(Data.Slice(offset, PokeCrypto.SIZE_6STORED)) { Identifier = $"Team {teamIndex}, Slot {p}" };
team[p] = new PK7(Data.Slice(offset, PokeCrypto.SIZE_6STORED));
}
return team;

View file

@ -123,15 +123,16 @@ namespace PKHeX.Core
return SAV.GetBoxSlotOffset(position);
}
public int GetPartyIndex(int box, int slot)
private int GetPartyIndex(int slotIndex)
{
int slotIndex = slot + (SAV.BoxSlotCount * box);
if ((uint)slotIndex >= MAX_SLOTS)
if ((uint) slotIndex >= MAX_SLOTS)
return MAX_SLOTS;
var index = Array.IndexOf(PokeListInfo, slotIndex);
return index >= 0 ? index : MAX_SLOTS;
}
public bool IsParty(int slotIndex) => GetPartyIndex(slotIndex) != MAX_SLOTS;
public bool CompressStorage()
{
// Box Data is stored as a list, instead of an array. Empty interstitials are not legal.

View file

@ -18,7 +18,7 @@ namespace PKHeX.Core
for (int i = 0; i < data.Length; i++)
{
var bytes = SAV.GetData(GetResortSlotOffset(i), PokeCrypto.SIZE_6STORED);
data[i] = new PK7(bytes) { Identifier = $"Resort Slot {i}" };
data[i] = new PK7(bytes);
}
return data;
}

View file

@ -24,16 +24,19 @@ namespace PKHeX.Core
return -1;
var boxData = sav.BoxData;
int boxSlotCount = sav.BoxSlotCount;
var ctr = 0;
foreach (var pk in boxData)
for (var slot = 0; slot < boxData.Count; slot++)
{
var pk = boxData[slot];
var box = slot / boxSlotCount;
if (pk.Species == 0 || !pk.Valid)
continue;
var boxFolder = path;
if (boxFolders)
{
var boxName = Util.CleanFileName(sav.GetBoxName(pk.Box - 1));
var boxName = Util.CleanFileName(sav.GetBoxName(box));
boxFolder = Path.Combine(path, boxName);
Directory.CreateDirectory(boxFolder);
}
@ -62,10 +65,13 @@ namespace PKHeX.Core
return -1;
var boxData = sav.BoxData;
int boxSlotCount = sav.BoxSlotCount;
var ctr = 0;
foreach (var pk in boxData)
for (var slot = 0; slot < boxData.Count; slot++)
{
if (pk.Species == 0 || !pk.Valid || pk.Box - 1 != currentBox)
var pk = boxData[slot];
var box = slot / boxSlotCount;
if (pk.Species == 0 || !pk.Valid || box != currentBox)
continue;
var fileName = Path.Combine(path, Util.CleanFileName(pk.FileName));

View file

@ -84,7 +84,7 @@ namespace PKHeX.Core
/// <param name="skip">Criteria for skipping a slot</param>
/// <param name="start">Starting point to copy to</param>
/// <returns>Count of <see cref="T"/> copied.</returns>
public static int CopyTo<T>(this IEnumerable<T> list, IList<T> dest, Func<T, bool> skip, int start = 0)
public static int CopyTo<T>(this IEnumerable<T> list, IList<T> dest, Func<int, bool> skip, int start = 0)
{
int ctr = start;
int skipped = 0;
@ -101,14 +101,14 @@ namespace PKHeX.Core
return ctr - start - skipped;
}
public static int FindNextValidIndex<T>(IList<T> dest, Func<T, bool> skip, int ctr)
public static int FindNextValidIndex<T>(IList<T> dest, Func<int, bool> skip, int ctr)
{
while (true)
{
if ((uint)ctr >= dest.Count)
return -1;
var exist = dest[ctr];
if (exist == null || !skip(exist))
if (exist == null || !skip(ctr))
return ctr;
ctr++;
}

View file

@ -135,9 +135,7 @@ namespace PKHeX.Drawing
if (!empty && flagIllegal)
{
if (box >= 0)
pk.Box = box;
var la = new LegalityAnalysis(pk, sav.Personal);
var la = new LegalityAnalysis(pk, sav.Personal, box != -1 ? SlotOrigin.Box : SlotOrigin.Party);
if (!la.Valid)
sprite = ImageUtil.LayerImage(sprite, Resources.warn, 0, FlagIllegalShiftY);
else if (pk.Format >= 8 && pk.Moves.Any(Legal.DummiedMoves_SWSH.Contains))

View file

@ -14,8 +14,8 @@ namespace PKHeX.WinForms.Controls
public SaveDataEditor<PictureBox> Editor { private get; set; } = null!;
public SlotChangeManager Manager { get; set; } = null!;
public event LegalityRequest? RequestEditorLegality;
public delegate void LegalityRequest(object sender, EventArgs e, PKM pkm);
public Action<LegalityAnalysis>? RequestEditorLegality;
public delegate void LegalityRequest(object sender, EventArgs e, LegalityAnalysis la);
public void OmniClick(object sender, EventArgs e, Keys z)
{
@ -108,7 +108,9 @@ namespace PKHeX.WinForms.Controls
var info = GetSenderInfo(ref sender);
var sav = info.View.SAV;
var pk = info.Slot.Read(sav);
RequestEditorLegality?.Invoke(sender, e, pk);
var type = info.Slot is SlotInfoBox ? SlotOrigin.Box : SlotOrigin.Party;
var la = new LegalityAnalysis(pk, sav.Personal, type);
RequestEditorLegality?.Invoke(la);
}
private void MenuOpening(object sender, CancelEventArgs e)

View file

@ -147,7 +147,7 @@ namespace PKHeX.WinForms
mnu.RequestEditorQR += (o, args) => ClickQR(mnu, args);
mnu.RequestEditorSaveAs += (o, args) => MainMenuSave(mnu, args);
dragout.ContextMenuStrip = mnu.mnuL;
C_SAV.menu.RequestEditorLegality += ShowLegality;
C_SAV.menu.RequestEditorLegality = DisplayLegalityReport;
}
private void FormLoadInitialFiles(string[] args)
@ -353,7 +353,9 @@ namespace PKHeX.WinForms
var report = new ReportGrid();
report.Show();
report.PopulateData(C_SAV.SAV.BoxData);
var list = new List<SlotCache>();
SlotInfoLoader.AddFromSaveFile(C_SAV.SAV, list);
report.PopulateData(list);
}
private void MainMenuDatabase(object sender, EventArgs e)
@ -1024,14 +1026,13 @@ namespace PKHeX.WinForms
if (pk.Species == 0 || !pk.ChecksumValid)
{ SystemSounds.Hand.Play(); return; }
ShowLegality(sender, e, pk);
var la = new LegalityAnalysis(pk, C_SAV.SAV.Personal);
PKME_Tabs.UpdateLegality(la);
DisplayLegalityReport(la);
}
private void ShowLegality(object sender, EventArgs e, PKM pk)
private static void DisplayLegalityReport(LegalityAnalysis la)
{
var la = new LegalityAnalysis(pk, C_SAV.SAV.Personal);
if (pk.Slot < 0)
PKME_Tabs.UpdateLegality(la);
bool verbose = ModifierKeys == Keys.Control;
var report = la.Report(verbose);
if (verbose)

View file

@ -10,9 +10,11 @@ namespace PKHeX.WinForms
public sealed class EntitySummaryImage : EntitySummary
{
public Image Sprite => pkm.Sprite();
public override string Position { get; }
public EntitySummaryImage(PKM p, GameStrings strings) : base(p, strings)
public EntitySummaryImage(PKM p, GameStrings strings, string position) : base(p, strings)
{
Position = position;
}
}
}

View file

@ -192,22 +192,29 @@ namespace PKHeX.WinForms
var files = Directory.GetFiles(source, "*", SearchOption.AllDirectories);
SetupProgressBar(files.Length * sets.Count);
foreach (var set in sets)
ProcessFolder(files, set.Filters, set.Instructions, destination);
ProcessFolder(files, destination, set.Filters, set.Instructions);
}
private void RunBatchEditSaveFile(IReadOnlyCollection<StringInstructionSet> sets, bool boxes = false, bool party = false)
{
IList<PKM> data;
if (party && SAV.HasParty && process(data = SAV.PartyData))
SAV.PartyData = data;
if (boxes && SAV.HasBox && process(data = SAV.BoxData))
SAV.BoxData = data;
bool process(IList<PKM> d)
var data = new List<SlotCache>();
if (party)
{
SlotInfoLoader.AddPartyData(SAV, data);
process(data);
SAV.PartyData = data.ConvertAll(z => z.Entity);
}
if (boxes)
{
SlotInfoLoader.AddBoxData(SAV, data);
process(data);
SAV.BoxData = data.ConvertAll(z => z.Entity);
}
void process(IList<SlotCache> d)
{
SetupProgressBar(d.Count * sets.Count);
foreach (var set in sets)
ProcessSAV(d, set.Filters, set.Instructions);
return d.Count != 0;
}
}
@ -232,25 +239,42 @@ namespace PKHeX.WinForms
// Mass Editing
private Core.BatchEditor editor = new();
private void ProcessSAV(IList<PKM> data, IReadOnlyList<StringInstruction> Filters, IReadOnlyList<StringInstruction> Instructions)
private void ProcessSAV(IList<SlotCache> data, IReadOnlyList<StringInstruction> Filters, IReadOnlyList<StringInstruction> Instructions)
{
var filterMeta = Filters.Where(f => BatchFilters.FilterMeta.Any(z => z.IsMatch(f.PropertyName))).ToArray();
if (filterMeta.Length != 0)
Filters = Filters.Except(filterMeta).ToArray();
for (int i = 0; i < data.Count; i++)
{
editor.Process(data[i], Filters, Instructions);
var entry = data[i];
var pk = data[i].Entity;
if (entry.Source is SlotInfoBox info && SAV.GetSlotFlags(info.Box, info.Slot).IsOverwriteProtected())
editor.AddSkipped();
else if(!BatchEditing.IsFilterMatchMeta(filterMeta, entry))
editor.AddSkipped();
else
editor.Process(pk, Filters, Instructions);
b.ReportProgress(i);
}
}
private void ProcessFolder(IReadOnlyList<string> files, IReadOnlyList<StringInstruction> Filters, IReadOnlyList<StringInstruction> Instructions, string destPath)
private void ProcessFolder(IReadOnlyList<string> files, string destDir, IReadOnlyList<StringInstruction> pkFilters, IReadOnlyList<StringInstruction> instructions)
{
var filterMeta = pkFilters.Where(f => BatchFilters.FilterMeta.Any(z => z.IsMatch(f.PropertyName))).ToArray();
if (filterMeta.Length != 0)
pkFilters = pkFilters.Except(filterMeta).ToArray();
for (int i = 0; i < files.Count; i++)
{
TryProcess(Filters, Instructions, files[i], destPath);
TryProcess(files[i], destDir, filterMeta, pkFilters, instructions);
b.ReportProgress(i);
}
}
private void TryProcess(IReadOnlyList<StringInstruction> Filters, IReadOnlyList<StringInstruction> Instructions, string source, string dest)
private void TryProcess(string source, string destDir, IReadOnlyList<StringInstruction> metaFilters, IReadOnlyList<StringInstruction> pkFilters, IReadOnlyList<StringInstruction> instructions)
{
var fi = new FileInfo(source);
if (!PKX.IsPKM(fi.Length))
@ -262,8 +286,13 @@ namespace PKHeX.WinForms
if (pk == null)
return;
if (editor.Process(pk, Filters, Instructions))
File.WriteAllBytes(Path.Combine(dest, Path.GetFileName(source)), pk.DecryptedPartyData);
var info = new SlotInfoFile(source);
var entry = new SlotCache(info, pk);
if (BatchEditing.IsFilterMatchMeta(metaFilters, entry))
editor.Process(pk, pkFilters, instructions);
if (editor.Process(pk, pkFilters, instructions))
File.WriteAllBytes(Path.Combine(destDir, Path.GetFileName(source)), pk.DecryptedPartyData);
}
}
}

View file

@ -51,16 +51,17 @@ namespace PKHeX.WinForms
private sealed class PokemonList<T> : SortableBindingList<T> where T : class { }
public void PopulateData(IList<PKM> Data)
public void PopulateData(IList<SlotCache> Data)
{
SuspendLayout();
BoxBar.Step = 1;
var PL = new PokemonList<EntitySummaryImage>();
var strings = GameInfo.Strings;
foreach (PKM pkm in Data.Where(pkm => pkm.ChecksumValid && pkm.Species != 0))
foreach (var entry in Data)
{
pkm.Stat_Level = Experience.GetLevel(pkm.EXP, pkm.PersonalInfo.EXPGrowth); // recalc Level
PL.Add(new EntitySummaryImage(pkm, strings));
var pkm = entry.Entity;
pkm.Stat_Level = pkm.CurrentLevel; // recalc Level
PL.Add(new EntitySummaryImage(pkm, strings, entry.Identify()));
BoxBar.PerformStep();
}

View file

@ -36,6 +36,7 @@
this.Menu_Tools = new System.Windows.Forms.ToolStripMenuItem();
this.Menu_SearchSettings = new System.Windows.Forms.ToolStripMenuItem();
this.Menu_SearchBoxes = new System.Windows.Forms.ToolStripMenuItem();
this.Menu_SearchBackups = new System.Windows.Forms.ToolStripMenuItem();
this.Menu_SearchDatabase = new System.Windows.Forms.ToolStripMenuItem();
this.Menu_SearchLegal = new System.Windows.Forms.ToolStripMenuItem();
this.Menu_SearchIllegal = new System.Windows.Forms.ToolStripMenuItem();
@ -170,6 +171,7 @@
this.Menu_SearchSettings.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.Menu_SearchBoxes,
this.Menu_SearchDatabase,
this.Menu_SearchBackups,
this.Menu_SearchLegal,
this.Menu_SearchIllegal,
this.Menu_SearchClones});
@ -187,6 +189,15 @@
this.Menu_SearchBoxes.Size = new System.Drawing.Size(198, 22);
this.Menu_SearchBoxes.Text = "Search Within Boxes";
//
// Menu_SearchBackups
//
this.Menu_SearchBackups.Checked = true;
this.Menu_SearchBackups.CheckOnClick = true;
this.Menu_SearchBackups.CheckState = System.Windows.Forms.CheckState.Checked;
this.Menu_SearchBackups.Name = "Menu_SearchBackups";
this.Menu_SearchBackups.Size = new System.Drawing.Size(198, 22);
this.Menu_SearchBackups.Text = "Search Within Backups";
//
// Menu_SearchDatabase
//
this.Menu_SearchDatabase.Checked = true;
@ -1094,5 +1105,6 @@
private System.Windows.Forms.TabPage Tab_General;
private System.Windows.Forms.TabPage Tab_Advanced;
private System.Windows.Forms.RichTextBox RTB_Instructions;
private System.Windows.Forms.ToolStripMenuItem Menu_SearchBackups;
}
}

View file

@ -102,8 +102,8 @@ namespace PKHeX.WinForms
private readonly PictureBox[] PKXBOXES;
private readonly string DatabasePath = Main.DatabasePath;
private List<PKM> Results = new();
private List<PKM> RawDB = new();
private List<SlotCache> Results = new();
private List<SlotCache> RawDB = new();
private int slotSelected = -1; // = null;
private Image? slotColor;
private const int RES_MAX = 66;
@ -124,11 +124,14 @@ namespace PKHeX.WinForms
return;
}
PKME_Tabs.PopulateFields(Results[index], false);
if (sender == mnu)
mnu.Hide();
slotSelected = index;
slotColor = SpriteUtil.Spriter.View;
FillPKXBoxes(SCR_Box.Value);
L_Viewed.Text = string.Format(Viewed, Results[index].Identifier);
L_Viewed.Text = string.Format(Viewed, Results[index].Identify());
PKME_Tabs.PopulateFields(Results[index].Entity, false);
}
private void ClickDelete(object sender, EventArgs e)
@ -141,27 +144,21 @@ namespace PKHeX.WinForms
return;
}
var pk = Results[index];
var path = pk.Identifier;
var entry = Results[index];
var pk = entry.Entity;
#if LOADALL
if (path.StartsWith(EXTERNAL_SAV))
{
WinFormsUtil.Alert(MsgDBDeleteFailBackup);
return;
}
#endif
if (path?.Contains(Path.DirectorySeparatorChar) == true)
if (entry.Source is SlotInfoFile f)
{
// Data from Database: Delete file from disk
var path = f.Path;
if (File.Exists(path))
File.Delete(path);
}
else
else if (entry.Source is SlotInfoBox b && entry.SAV == SAV)
{
// Data from Box: Delete from save file
int box = pk.Box-1;
int slot = pk.Slot-1;
int box = b.Box-1;
int slot = b.Slot-1;
var change = new SlotInfoBox(box, slot);
var pkSAV = change.Read(SAV);
@ -172,9 +169,14 @@ namespace PKHeX.WinForms
}
BoxView.EditEnv.Slots.Delete(change);
}
else
{
WinFormsUtil.Error(MsgDBDeleteFailBackup, MsgDBDeleteFailWarning);
return;
}
// Remove from database.
RawDB.Remove(pk);
Results.Remove(pk);
RawDB.Remove(entry);
Results.Remove(entry);
// Refresh database view.
L_Count.Text = string.Format(Counter, Results.Count);
slotSelected = -1;
@ -193,22 +195,17 @@ namespace PKHeX.WinForms
string path = Path.Combine(DatabasePath, Util.CleanFileName(pk.FileName));
if (RawDB.Any(p => p.Identifier == path))
if (File.Exists(path))
{
WinFormsUtil.Alert(MsgDBAddFailExistsFile);
return;
}
File.WriteAllBytes(path, pk.DecryptedBoxData);
pk.Identifier = path;
int pre = RawDB.Count;
RawDB.Add(pk);
RawDB = new List<PKM>(RawDB);
int post = RawDB.Count;
if (pre == post)
{ WinFormsUtil.Alert(MsgDBAddFailExistsPKM); return; }
Results.Add(pk);
var info = new SlotInfoFile(path);
var entry = new SlotCache(info, pk);
Results.Add(entry);
// Refresh database view.
L_Count.Text = string.Format(Counter, Results.Count);
@ -313,7 +310,7 @@ namespace PKHeX.WinForms
ReportGrid reportGrid = new();
reportGrid.Show();
reportGrid.PopulateData(Results.ToArray());
reportGrid.PopulateData(Results);
}
private void LoadDatabase()
@ -329,10 +326,10 @@ namespace PKHeX.WinForms
RawDB = LoadPKMSaves(DatabasePath, SAV, otherPaths, settings.EntityDb.SearchExtraSavesDeep);
// Load stats for pkm who do not have any
foreach (var pk in RawDB.Where(z => z.Stat_Level == 0))
foreach (var entry in RawDB)
{
pk.Stat_Level = pk.CurrentLevel;
pk.SetStats(pk.GetStats(pk.PersonalInfo));
var pk = entry.Entity;
pk.ForcePartyData();
}
try
@ -345,99 +342,47 @@ namespace PKHeX.WinForms
#pragma warning restore CA1031 // Do not catch general exception types
}
private static List<PKM> LoadPKMSaves(string pkmdb, SaveFile SAV, IEnumerable<string> otherPaths, bool otherDeep)
private static List<SlotCache> LoadPKMSaves(string pkmdb, SaveFile SAV, IEnumerable<string> otherPaths, bool otherDeep)
{
var dbTemp = new ConcurrentBag<PKM>();
var dbTemp = new ConcurrentBag<SlotCache>();
var extensions = new HashSet<string>(PKM.Extensions.Select(z => $".{z}"));
var files = Directory.EnumerateFiles(pkmdb, "*", SearchOption.AllDirectories);
Parallel.ForEach(files, file => TryAddPKMsFromFolder(dbTemp, file, SAV, extensions));
Parallel.ForEach(files, file => SlotInfoLoader.AddFromLocalFile(file, dbTemp, SAV, extensions));
foreach (var folder in otherPaths)
{
if (!SaveUtil.GetSavesFromFolder(folder, otherDeep, out IEnumerable<string> result))
if (!SaveUtil.GetSavesFromFolder(folder, otherDeep, out IEnumerable<string> paths))
continue;
var prefix = Path.GetDirectoryName(folder) + Path.DirectorySeparatorChar;
Parallel.ForEach(result, file => TryAddPKMsFromSaveFilePath(dbTemp, file, prefix));
Parallel.ForEach(paths, file => TryAddPKMsFromSaveFilePath(dbTemp, file));
}
// Fetch from save file
var savpkm = SAV.BoxData.Where(pk => pk.Species != 0);
var bakpkm = dbTemp.Where(pk => pk.Species != 0).OrderBy(pk => pk.Identifier);
var db = bakpkm.Concat(savpkm).Where(pk => pk.ChecksumValid && pk.Sanity == 0);
SlotInfoLoader.AddFromSaveFile(SAV, dbTemp);
var result = new List<SlotCache>(dbTemp);
result.RemoveAll(z => !z.IsDataValid());
if (Main.Settings.EntityDb.FilterUnavailableSpecies)
{
db = SAV is SAV8SWSH
? db.Where(z => z is PK8 || ((PersonalInfoSWSH) PersonalTable.SWSH.GetFormEntry(z.Species, z.Form)).IsPresentInGame)
: db.Where(z => z is not PK8);
if (SAV is SAV8SWSH)
result.RemoveAll(z => !(z.Entity is PK8 || ((PersonalInfoSWSH) PersonalTable.SWSH.GetFormEntry(z.Entity.Species, z.Entity.Form)).IsPresentInGame));
}
// Finalize the Database
return new List<PKM>(db);
return result;
}
private static void TryAddPKMsFromFolder(ConcurrentBag<PKM> dbTemp, string file, ITrainerInfo dest, ICollection<string> validExtensions)
{
var fi = new FileInfo(file);
if (!validExtensions.Contains(fi.Extension) || !PKX.IsPKM(fi.Length))
return;
var data = File.ReadAllBytes(file);
var prefer = PKX.GetPKMFormatFromExtension(fi.Extension, dest.Generation);
var pk = PKMConverter.GetPKMfromBytes(data, prefer);
if (pk?.Species is not > 0)
return;
pk.Identifier = file;
dbTemp.Add(pk);
}
private static void TryAddPKMsFromSaveFilePath(ConcurrentBag<PKM> dbTemp, string file, string externalFilePrefix)
private static void TryAddPKMsFromSaveFilePath(ConcurrentBag<SlotCache> dbTemp, string file)
{
var sav = SaveUtil.GetVariantSAV(file);
if (sav == null)
{
Console.WriteLine("Unable to load SaveFile: " + file);
Debug.WriteLine("Unable to load SaveFile: " + file);
return;
}
var path = externalFilePrefix + Path.GetFileName(file);
if (sav.HasBox)
{
foreach (var pk in sav.BoxData)
{
if (pk.Species == 0)
continue;
pk.Identifier = Path.Combine(path, pk.Identifier ?? string.Empty);
dbTemp.Add(pk);
}
}
if (sav.HasParty)
{
foreach (var pk in sav.PartyData)
{
if (pk.Species == 0)
continue;
pk.Identifier = Path.Combine(path, pk.Identifier ?? string.Empty);
dbTemp.Add(pk);
}
}
var extra = sav.GetExtraSlots(true);
foreach (var x in extra)
{
var pk = x.Read(sav);
if (pk.Species == 0)
continue;
pk.Identifier = Path.Combine(path, pk.Identifier ?? x.Type.ToString());
dbTemp.Add(pk);
}
SlotInfoLoader.AddFromSaveFile(sav, dbTemp);
}
// IO Usage
@ -462,7 +407,7 @@ namespace PKHeX.WinForms
string path = fbd.SelectedPath;
Directory.CreateDirectory(path);
foreach (PKM pkm in Results)
foreach (var pkm in Results.Select(z => z.Entity))
File.WriteAllBytes(Path.Combine(path, Util.CleanFileName(pkm.FileName)), pkm.DecryptedPartyData);
}
@ -472,7 +417,7 @@ namespace PKHeX.WinForms
return;
int box = BoxView.Box.CurrentBox;
int ctr = SAV.LoadBoxes(Results, out var result, box, clearAll, overwrite, noSetb);
int ctr = SAV.LoadBoxes(Results.Select(z => z.Entity), out var result, box, clearAll, overwrite, noSetb);
if (ctr <= 0)
return;
@ -482,22 +427,19 @@ namespace PKHeX.WinForms
}
// View Updates
private IEnumerable<PKM> SearchDatabase()
private IEnumerable<SlotCache> SearchDatabase()
{
var settings = GetSearchSettings();
IEnumerable<PKM> res = RawDB;
IEnumerable<SlotCache> res = RawDB;
// pre-filter based on the file path (if specified)
if (!Menu_SearchBoxes.Checked)
res = res.Where(pk => pk.Identifier?.StartsWith(DatabasePath + Path.DirectorySeparatorChar, StringComparison.Ordinal) == true);
res = res.Where(z => z.SAV != SAV);
if (!Menu_SearchDatabase.Checked)
{
res = res.Where(pk => pk.Identifier?.StartsWith(DatabasePath + Path.DirectorySeparatorChar, StringComparison.Ordinal) == false);
#if LOADALL
res = res.Where(pk => pk.Identifier?.StartsWith(EXTERNAL_SAV, StringComparison.Ordinal) == false);
#endif
}
res = res.Where(z => !IsIndividualFilePKMDB(z));
if (!Menu_SearchBackups.Checked)
res = res.Where(z => !IsBackupSaveFile(z));
// return filtered results
return settings.Search(res);
@ -571,7 +513,7 @@ namespace PKHeX.WinForms
if (results.Count == 0)
{
if (!Menu_SearchBoxes.Checked && !Menu_SearchDatabase.Checked)
if (!Menu_SearchBoxes.Checked && !Menu_SearchDatabase.Checked && !Menu_SearchBackups.Checked)
WinFormsUtil.Alert(MsgDBSearchFail, MsgDBSearchNone);
else
WinFormsUtil.Alert(MsgDBSearchNone);
@ -587,7 +529,7 @@ namespace PKHeX.WinForms
FillPKXBoxes(e.NewValue);
}
private void SetResults(List<PKM> res)
private void SetResults(List<SlotCache> res)
{
Results = res;
ShowSet.Clear();
@ -617,7 +559,7 @@ namespace PKHeX.WinForms
int begin = start*RES_MIN;
int end = Math.Min(RES_MAX, Results.Count - begin);
for (int i = 0; i < end; i++)
PKXBOXES[i].Image = Results[i + begin].Sprite(SAV, -1, -1, true);
PKXBOXES[i].Image = Results[i + begin].Entity.Sprite(SAV, -1, -1, true);
for (int i = end; i < RES_MAX; i++)
PKXBOXES[i].Image = null;
@ -688,19 +630,21 @@ namespace PKHeX.WinForms
return;
var deleted = 0;
var db = RawDB.Where(pk => pk.Identifier?.StartsWith(DatabasePath + Path.DirectorySeparatorChar, StringComparison.Ordinal) == true)
.OrderByDescending(file => File.GetLastWriteTimeUtc(file.Identifier!));
var db = RawDB.Where(IsIndividualFilePKMDB)
.OrderByDescending(GetRevisedTime);
var clones = SearchUtil.GetExtraClones(db);
foreach (var pk in clones)
var hasher = SearchUtil.GetCloneDetectMethod(CloneDetectionMethod.HashDetails);
var duplicates = SearchUtil.GetExtraClones(db, z => hasher(z.Entity));
foreach (var entry in duplicates)
{
var path = pk.Identifier;
if (path == null || !File.Exists(path))
var src = entry.Source;
var path = ((SlotInfoFile)src).Path;
if (!File.Exists(path))
continue;
try { File.Delete(path); ++deleted; }
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex) { WinFormsUtil.Error(MsgDBDeleteCloneFail + Environment.NewLine + ex.Message + Environment.NewLine + pk.Identifier); }
catch (Exception ex) { WinFormsUtil.Error(MsgDBDeleteCloneFail + Environment.NewLine + ex.Message + Environment.NewLine + path); }
#pragma warning restore CA1031 // Do not catch general exception types
}
@ -711,6 +655,24 @@ namespace PKHeX.WinForms
Close();
}
private static DateTime GetRevisedTime(SlotCache arg)
{
var src = arg.Source;
if (src is not SlotInfoFile f)
return DateTime.Now;
return File.GetLastWriteTimeUtc(f.Path);
}
private bool IsBackupSaveFile(SlotCache pk)
{
return pk.SAV is not FakeSaveFile && pk.SAV != SAV;
}
private bool IsIndividualFilePKMDB(SlotCache pk)
{
return pk.Source is SlotInfoFile f && f.Path.StartsWith(DatabasePath + Path.DirectorySeparatorChar, StringComparison.Ordinal);
}
private void L_Viewed_MouseEnter(object sender, EventArgs e) => hover.SetToolTip(L_Viewed, L_Viewed.Text);
private void ShowHoverTextForSlot(object sender, EventArgs e)
@ -720,7 +682,7 @@ namespace PKHeX.WinForms
if (!GetShiftedIndex(ref index))
return;
ShowSet.Show(pb, Results[index]);
ShowSet.Show(pb, Results[index].Entity);
}
}
}

View file

@ -102,7 +102,7 @@ namespace PKHeX.WinForms
comboBox.DroppedDown = true;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch { Console.WriteLine("Failed to modify item."); }
catch { System.Diagnostics.Debug.WriteLine("Failed to modify item."); }
#pragma warning restore CA1031 // Do not catch general exception types
}

View file

@ -172,7 +172,7 @@ namespace PKHeX.WinForms
{
var constructors = t.GetConstructors();
if (constructors.Length == 0)
{ System.Console.WriteLine($"No constructors: {t.Name}"); continue; }
{ Debug.WriteLine($"No constructors: {t.Name}"); continue; }
var argCount = constructors[0].GetParameters().Length;
try
{