mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
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:
parent
350383cf51
commit
3e7775fc44
72 changed files with 1114 additions and 652 deletions
|
@ -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()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
31
PKHeX.Core/Editing/Bulk/BatchFilters.cs
Normal file
31
PKHeX.Core/Editing/Bulk/BatchFilters.cs
Normal 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),
|
||||
};
|
||||
}
|
||||
}
|
79
PKHeX.Core/Editing/Bulk/BatchMods.cs
Normal file
79
PKHeX.Core/Editing/Bulk/BatchMods.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
11
PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilterMeta.cs
Normal file
11
PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilterMeta.cs
Normal 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);
|
||||
}
|
||||
}
|
22
PKHeX.Core/Editing/Bulk/ComplexSet/MetaFilter.cs
Normal file
22
PKHeX.Core/Editing/Bulk/ComplexSet/MetaFilter.cs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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())),
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
64
PKHeX.Core/Editing/Saves/Slots/Info/SlotCache.cs
Normal file
64
PKHeX.Core/Editing/Saves/Slots/Info/SlotCache.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs
Normal file
17
PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs
Normal 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;
|
||||
}
|
||||
}
|
163
PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs
Normal file
163
PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
8
PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs
Normal file
8
PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace PKHeX.Core
|
||||
{
|
||||
public enum SlotOrigin
|
||||
{
|
||||
Party = 0,
|
||||
Box = 1,
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -26,7 +26,6 @@ namespace PKHeX.Core
|
|||
|
||||
public override PKM Clone() => new SK2((byte[])Data.Clone(), Japanese)
|
||||
{
|
||||
Identifier = Identifier,
|
||||
IsEgg = IsEgg,
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 { } }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
12
PKHeX.WinForms/Subforms/SAV_Database.Designer.cs
generated
12
PKHeX.WinForms/Subforms/SAV_Database.Designer.cs
generated
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue