mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
Split PKMConverter into multiple classes
EntityFormat -> detects byte[] -> ctor() PKM EntityConverter -> converts between pkm formats RecentTrainerCache -> used by other classes (not within EntityConverter!) EntityBlank -> creates blank PKM objects indirectly
This commit is contained in:
parent
d3749bd4b6
commit
e6cf5d0baf
30 changed files with 634 additions and 525 deletions
|
@ -111,7 +111,7 @@ namespace PKHeX.Core
|
|||
Met_Level = Level,
|
||||
MetDate = DateTime.Now,
|
||||
};
|
||||
PKMConverter.SetConsoleRegionData3DS(pk);
|
||||
RecentTrainerCache.SetConsoleRegionData3DS(pk, sav);
|
||||
|
||||
pk.RefreshAbility(AbilityIndex >> 1);
|
||||
pk.ForcePartyData();
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace PKHeX.Core
|
|||
public int Species { get; private set; } = -1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Format { get; private set; } = PKMConverter.Format;
|
||||
public int Format { get; private set; } = RecentTrainerCache.Format;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace PKHeX.Core
|
|||
if (f == null)
|
||||
return null;
|
||||
|
||||
var result = PKMConverter.ConvertToType(f, destType, out _);
|
||||
var result = EntityConverter.ConvertToType(f, destType, out _);
|
||||
if (result == null)
|
||||
return null;
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace PKHeX.Core
|
|||
{
|
||||
int gen = Generation;
|
||||
var version = Version;
|
||||
var pk = PKMConverter.GetBlank(gen, version);
|
||||
var pk = EntityBlank.GetBlank(gen, version);
|
||||
|
||||
sav.ApplyTo(pk);
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace PKHeX.Core
|
|||
|
||||
public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
|
||||
{
|
||||
var pk = PKMConverter.GetBlank(Generation, Version);
|
||||
var pk = EntityBlank.GetBlank(Generation, Version);
|
||||
sav.ApplyTo(pk);
|
||||
ApplyDetails(sav, criteria, pk);
|
||||
return pk;
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace PKHeX.Core
|
|||
internal const int FormVivillon = 30;
|
||||
//protected const int FormRandom = 31;
|
||||
|
||||
protected virtual PKM GetBlank(ITrainerInfo tr) => PKMConverter.GetBlank(Generation, Version);
|
||||
protected virtual PKM GetBlank(ITrainerInfo tr) => EntityBlank.GetBlank(Generation, Version);
|
||||
|
||||
public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted);
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace PKHeX.Core
|
|||
|
||||
public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
|
||||
{
|
||||
var pk = PKMConverter.GetBlank(Generation, Version);
|
||||
var pk = EntityBlank.GetBlank(Generation, Version);
|
||||
sav.ApplyTo(pk);
|
||||
|
||||
ApplyDetails(sav, criteria, pk);
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace PKHeX.Core
|
|||
if (moves.Any(z => z < 0))
|
||||
return Array.Empty<IEncounterable>();
|
||||
|
||||
var blank = PKMConverter.GetBlank(PKX.Generation);
|
||||
var blank = EntityBlank.GetBlank(PKX.Generation);
|
||||
blank.Species = species;
|
||||
blank.Form = form;
|
||||
|
||||
|
|
|
@ -184,13 +184,13 @@ namespace PKHeX.Core
|
|||
IsNicknamed = false,
|
||||
|
||||
CurrentHandler = 1,
|
||||
HT_Name = PKMConverter.OT_Name,
|
||||
HT_Gender = PKMConverter.OT_Gender,
|
||||
HT_Name = RecentTrainerCache.OT_Name,
|
||||
HT_Gender = RecentTrainerCache.OT_Gender,
|
||||
};
|
||||
PKMConverter.SetConsoleRegionData3DS(pk7);
|
||||
PKMConverter.SetFirstCountryRegion(pk7);
|
||||
RecentTrainerCache.SetConsoleRegionData3DS(pk7);
|
||||
RecentTrainerCache.SetFirstCountryRegion(pk7);
|
||||
pk7.HealPP();
|
||||
var lang = TransferLanguage(PKMConverter.Language);
|
||||
var lang = TransferLanguage(RecentTrainerCache.Language);
|
||||
pk7.Language = lang;
|
||||
pk7.Nickname = SpeciesName.GetSpeciesNameGeneration(pk7.Species, lang, pk7.Format);
|
||||
if (RawOT[0] == StringConverter12.G1TradeOTCode) // In-game Trade
|
||||
|
|
|
@ -158,13 +158,13 @@ namespace PKHeX.Core
|
|||
Form = Form,
|
||||
|
||||
CurrentHandler = 1,
|
||||
HT_Name = PKMConverter.OT_Name,
|
||||
HT_Gender = PKMConverter.OT_Gender,
|
||||
HT_Name = RecentTrainerCache.OT_Name,
|
||||
HT_Gender = RecentTrainerCache.OT_Gender,
|
||||
};
|
||||
PKMConverter.SetConsoleRegionData3DS(pk7);
|
||||
PKMConverter.SetFirstCountryRegion(pk7);
|
||||
RecentTrainerCache.SetConsoleRegionData3DS(pk7);
|
||||
RecentTrainerCache.SetFirstCountryRegion(pk7);
|
||||
pk7.HealPP();
|
||||
var lang = TransferLanguage(PKMConverter.Language);
|
||||
var lang = TransferLanguage(RecentTrainerCache.Language);
|
||||
pk7.Language = lang;
|
||||
pk7.Nickname = SpeciesName.GetSpeciesNameGeneration(pk7.Species, lang, pk7.Format);
|
||||
|
||||
|
|
|
@ -444,16 +444,16 @@ namespace PKHeX.Core
|
|||
|
||||
// Write the Memories, Friendship, and Origin!
|
||||
CurrentHandler = 1,
|
||||
HT_Name = PKMConverter.OT_Name,
|
||||
HT_Gender = PKMConverter.OT_Gender,
|
||||
HT_Name = RecentTrainerCache.OT_Name,
|
||||
HT_Gender = RecentTrainerCache.OT_Gender,
|
||||
HT_Intensity = 1,
|
||||
HT_Memory = 4,
|
||||
HT_Feeling = MemoryContext6.GetRandomFeeling6(4, 10),
|
||||
};
|
||||
|
||||
// Write Transfer Location - location is dependent on 3DS system that transfers.
|
||||
PKMConverter.SetConsoleRegionData3DS(pk6);
|
||||
PKMConverter.SetFirstCountryRegion(pk6);
|
||||
RecentTrainerCache.SetConsoleRegionData3DS(pk6);
|
||||
RecentTrainerCache.SetFirstCountryRegion(pk6);
|
||||
|
||||
// Apply trash bytes for species name of current app language -- default to PKM's language if no match
|
||||
int curLang = SpeciesName.GetSpeciesNameLanguage(Species, Nickname, 5);
|
||||
|
|
|
@ -486,12 +486,13 @@ namespace PKHeX.Core
|
|||
}
|
||||
|
||||
pk7.SetTradeMemoryHT6(true); // oh no, memories on gen7 pkm
|
||||
PKMConverter.SetFirstCountryRegion(pk7);
|
||||
RecentTrainerCache.SetFirstCountryRegion(pk7);
|
||||
|
||||
// Bank-accurate data zeroing
|
||||
for (var i = 0x94; i < 0x9E; i++) pk7.Data[i] = 0; /* Geolocations. */
|
||||
for (var i = 0xAA; i < 0xB0; i++) pk7.Data[i] = 0; /* Unused/Amie Fullness & Enjoyment. */
|
||||
for (var i = 0xE4; i < 0xE8; i++) pk7.Data[i] = 0; /* Unused. */
|
||||
var span = pk7.Data.AsSpan();
|
||||
span[0x94..0x9E].Clear(); /* Geolocations. */
|
||||
span[0xAA..0xB0].Clear(); /* Unused/Amie Fullness & Enjoyment. */
|
||||
span[0xE4..0xE8].Clear(); /* Unused. */
|
||||
pk7.Data[0x72] &= 0xFC; /* Clear lower two bits of Super training flags. */
|
||||
pk7.Data[0xDE] = 0; /* Gen IV encounter type. */
|
||||
|
||||
|
|
43
PKHeX.Core/PKM/Util/EntityBlank.cs
Normal file
43
PKHeX.Core/PKM/Util/EntityBlank.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Reflection utility to create blank <see cref="PKM"/> without specifying a constructor.
|
||||
/// </summary>
|
||||
public static class EntityBlank
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a Blank <see cref="PKM"/> object of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of <see cref="PKM"/> instance desired.</param>
|
||||
/// <returns>New instance of a blank <see cref="PKM"/> object.</returns>
|
||||
public static PKM GetBlank(Type type)
|
||||
{
|
||||
var constructors = type.GetTypeInfo().DeclaredConstructors.Where(z => !z.IsStatic);
|
||||
var argCount = constructors.Min(z => z.GetParameters().Length);
|
||||
return (PKM)Activator.CreateInstance(type, new object[argCount]);
|
||||
}
|
||||
|
||||
public static PKM GetBlank(int gen, GameVersion ver) => gen switch
|
||||
{
|
||||
1 when ver == GameVersion.BU => new PK1(true),
|
||||
7 when GameVersion.Gen7b.Contains(ver) => new PB7(),
|
||||
8 when GameVersion.BDSP.Contains(ver) => new PB8(),
|
||||
8 when GameVersion.PLA == ver => new PA8(),
|
||||
_ => GetBlank(gen),
|
||||
};
|
||||
|
||||
public static PKM GetBlank(int gen, int ver) => GetBlank(gen, (GameVersion)ver);
|
||||
|
||||
public static PKM GetBlank(int gen)
|
||||
{
|
||||
var type = Type.GetType($"PKHeX.Core.PK{gen}");
|
||||
if (type is null)
|
||||
throw new InvalidCastException($"Unable to get the type for PK{gen}.");
|
||||
|
||||
return GetBlank(type);
|
||||
}
|
||||
}
|
270
PKHeX.Core/PKM/Util/EntityConverter.cs
Normal file
270
PKHeX.Core/PKM/Util/EntityConverter.cs
Normal file
|
@ -0,0 +1,270 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using static PKHeX.Core.MessageStrings;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for converting a <see cref="PKM"/> from one generation specific format to another.
|
||||
/// </summary>
|
||||
public static class EntityConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// If a conversion method does not officially (legally) exist, then the program can try to convert via other means (illegal).
|
||||
/// </summary>
|
||||
public static bool AllowIncompatibleConversion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input <see cref="PKM"/> file is capable of being converted to the desired format.
|
||||
/// </summary>
|
||||
/// <param name="pk"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <returns>True if can be converted to the requested format value.</returns>
|
||||
public static bool IsConvertibleToFormat(PKM pk, int format)
|
||||
{
|
||||
if (pk.Format >= 3 && pk.Format > format)
|
||||
return false; // pk3->upward can't go backwards
|
||||
if (pk.Format <= 2 && format is > 2 and < 7)
|
||||
return false; // pk1/2->upward has to be 7 or greater
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a PKM from one Generation format to another. If it matches the destination format, the conversion will automatically return.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to convert</param>
|
||||
/// <param name="destType">Format/Type to convert to</param>
|
||||
/// <param name="comment">Comments regarding the transfer's success/failure</param>
|
||||
/// <returns>Converted PKM</returns>
|
||||
public static PKM? ConvertToType(PKM pk, Type destType, out string comment)
|
||||
{
|
||||
Type fromType = pk.GetType();
|
||||
if (fromType == destType)
|
||||
{
|
||||
comment = "No need to convert, current format matches requested format.";
|
||||
return pk;
|
||||
}
|
||||
|
||||
var pkm = ConvertPKM(pk, destType, fromType, out comment);
|
||||
if (!AllowIncompatibleConversion || pkm != null)
|
||||
return pkm;
|
||||
|
||||
if (pk is PK8 && destType == typeof(PB8))
|
||||
return new PB8(pk.Data);
|
||||
if (pk is PB8 && destType == typeof(PK8))
|
||||
return new PK8(pk.Data);
|
||||
|
||||
// Try Incompatible Conversion
|
||||
pkm = EntityBlank.GetBlank(destType);
|
||||
pk.TransferPropertiesWithReflection(pkm);
|
||||
if (!IsCompatibleWithModifications(pkm))
|
||||
return null;
|
||||
comment = "Converted via reflection.";
|
||||
return pkm;
|
||||
}
|
||||
|
||||
private static PKM? ConvertPKM(PKM pk, Type destType, Type srcType, out string comment)
|
||||
{
|
||||
if (IsNotTransferable(pk, out comment))
|
||||
return null;
|
||||
|
||||
string destName = destType.Name;
|
||||
string srcName = srcType.Name;
|
||||
Debug.WriteLine($"Trying to convert {srcName} to {destName}.");
|
||||
|
||||
// All types that inherit PKM have the generation specifier as the last char in their class name.
|
||||
int destGeneration = destName[^1] - '0';
|
||||
var pkm = ConvertPKM(pk, destType, destGeneration, ref comment);
|
||||
var msg = pkm == null ? MsgPKMConvertFailFormat : MsgPKMConvertSuccess;
|
||||
var formatted = string.Format(msg, srcName, destName);
|
||||
comment = comment.Length != 0 ? formatted : string.Concat(formatted, Environment.NewLine, comment);
|
||||
return pkm;
|
||||
}
|
||||
|
||||
private static PKM? ConvertPKM(PKM pk, Type destType, int destGeneration, ref string comment)
|
||||
{
|
||||
PKM? pkm = pk.Clone();
|
||||
if (pkm.IsEgg)
|
||||
pkm.ForceHatchPKM();
|
||||
while (true)
|
||||
{
|
||||
pkm = IntermediaryConvert(pkm, destType, destGeneration, ref comment);
|
||||
if (pkm == null) // fail convert
|
||||
return null;
|
||||
if (pkm.GetType() == destType) // finish convert
|
||||
return pkm;
|
||||
}
|
||||
}
|
||||
|
||||
private static PKM? IntermediaryConvert(PKM pk, Type destType, int destGeneration, ref string comment)
|
||||
{
|
||||
switch (pk)
|
||||
{
|
||||
// Non-sequential
|
||||
case PK1 pk1 when destGeneration > 2: return pk1.ConvertToPK7();
|
||||
case PK2 pk2 when destGeneration > 2: return pk2.ConvertToPK7();
|
||||
case PK2 pk2 when destType == typeof(SK2): return pk2.ConvertToSK2();
|
||||
case PK3 pk3 when destType == typeof(CK3): return pk3.ConvertToCK3();
|
||||
case PK3 pk3 when destType == typeof(XK3): return pk3.ConvertToXK3();
|
||||
case PK4 pk4 when destType == typeof(BK4): return pk4.ConvertToBK4();
|
||||
|
||||
// Invalid
|
||||
case PK2 pk2 when pk.Species > Legal.MaxSpeciesID_1:
|
||||
var lang = pk2.Japanese ? (int)LanguageID.Japanese : (int)LanguageID.English;
|
||||
var name = SpeciesName.GetSpeciesName(pk2.Species, lang);
|
||||
comment = string.Format(MsgPKMConvertFailFormat, name, destType.Name);
|
||||
return null;
|
||||
|
||||
// Sequential
|
||||
case PK1 pk1: return pk1.ConvertToPK2();
|
||||
case PK2 pk2: return pk2.ConvertToPK1();
|
||||
case PK3 pk3: return pk3.ConvertToPK4();
|
||||
case PK4 pk4: return pk4.ConvertToPK5();
|
||||
case PK5 pk5: return pk5.ConvertToPK6();
|
||||
case PK6 pk6: return pk6.ConvertToPK7();
|
||||
case PK7 pk7: return pk7.ConvertToPK8();
|
||||
case PB7 pb7: return pb7.ConvertToPK8();
|
||||
|
||||
// Side-Formats back to Mainline
|
||||
case SK2 sk2: return sk2.ConvertToPK2();
|
||||
case CK3 ck3: return ck3.ConvertToPK3();
|
||||
case XK3 xk3: return xk3.ConvertToPK3();
|
||||
case BK4 bk4: return bk4.ConvertToPK4();
|
||||
|
||||
// None
|
||||
default:
|
||||
comment = MsgPKMConvertFailNoMethod;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a PKM is transferable relative to in-game restrictions and <see cref="PKM.Form"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to convert</param>
|
||||
/// <param name="comment">Comment indicating why the <see cref="PKM"/> is not transferable.</param>
|
||||
/// <returns>Indication if Not Transferable</returns>
|
||||
private static bool IsNotTransferable(PKM pk, out string comment)
|
||||
{
|
||||
switch (pk)
|
||||
{
|
||||
case PK4 { Species: (int)Species.Pichu } pk4 when pk4.Form != 0:
|
||||
case PK6 { Species: (int)Species.Pikachu } pk6 when pk6.Form != 0:
|
||||
case PB7 { Species: (int)Species.Pikachu } pika when pika.Form != 0:
|
||||
case PB7 { Species: (int)Species.Eevee } eevee when eevee.Form != 0:
|
||||
comment = MsgPKMConvertFailForm;
|
||||
return true;
|
||||
default:
|
||||
comment = string.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="PKM"/> is compatible with the input <see cref="PKM"/>, and makes any necessary modifications to force compatibility.
|
||||
/// </summary>
|
||||
/// <remarks>Should only be used when forcing a backwards conversion to sanitize the PKM fields to the target format.
|
||||
/// If the PKM is compatible, some properties may be forced to sanitized values.</remarks>
|
||||
/// <param name="pk">PKM input that is to be sanity checked.</param>
|
||||
/// <param name="limit">Value clamps for the destination format</param>
|
||||
/// <returns>Indication whether or not the PKM is compatible.</returns>
|
||||
public static bool IsCompatibleWithModifications(PKM pk, IGameValueLimit limit)
|
||||
{
|
||||
if (pk.Species > limit.MaxSpeciesID)
|
||||
return false;
|
||||
|
||||
MakeCompatible(pk, limit);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void MakeCompatible(PKM pk, IGameValueLimit limit)
|
||||
{
|
||||
if (pk.HeldItem > limit.MaxItemID)
|
||||
pk.HeldItem = 0;
|
||||
|
||||
if (pk.Nickname.Length > limit.NickLength)
|
||||
pk.Nickname = pk.Nickname[..pk.NickLength];
|
||||
|
||||
if (pk.OT_Name.Length > limit.OTLength)
|
||||
pk.OT_Name = pk.OT_Name[..pk.OTLength];
|
||||
|
||||
if (pk.Move1 > limit.MaxMoveID || pk.Move2 > limit.MaxMoveID || pk.Move3 > limit.MaxMoveID || pk.Move4 > limit.MaxMoveID)
|
||||
pk.ClearInvalidMoves();
|
||||
|
||||
int maxEV = pk.MaxEV;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (pk.GetEV(i) > maxEV)
|
||||
pk.SetEV(i, maxEV);
|
||||
}
|
||||
|
||||
int maxIV = pk.MaxIV;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (pk.GetIV(i) > maxIV)
|
||||
pk.SetIV(i, maxIV);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsCompatibleWithModifications(PKM, IGameValueLimit)"/>
|
||||
public static bool IsCompatibleWithModifications(PKM pk) => IsCompatibleWithModifications(pk, pk);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input <see cref="PKM"/> is compatible with the target <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Input to check -> update/sanitize</param>
|
||||
/// <param name="target">Target type PKM with misc properties accessible for checking.</param>
|
||||
/// <param name="c">Comment output</param>
|
||||
/// <param name="pkm">Output compatible PKM</param>
|
||||
/// <returns>Indication if the input is (now) compatible with the target.</returns>
|
||||
public static bool TryMakePKMCompatible(PKM pk, PKM target, out string c, out PKM pkm)
|
||||
{
|
||||
if (!IsConvertibleToFormat(pk, target.Format))
|
||||
{
|
||||
pkm = target;
|
||||
c = string.Format(MsgPKMConvertFailBackwards, pk.GetType().Name, target.Format);
|
||||
if (!AllowIncompatibleConversion)
|
||||
return false;
|
||||
}
|
||||
if (IsIncompatibleGB(target, target.Japanese, pk.Japanese))
|
||||
{
|
||||
pkm = target;
|
||||
c = GetIncompatibleGBMessage(pk, target.Japanese);
|
||||
return false;
|
||||
}
|
||||
var convert = ConvertToType(pk, target.GetType(), out c);
|
||||
if (convert == null)
|
||||
{
|
||||
pkm = target;
|
||||
return false;
|
||||
}
|
||||
|
||||
pkm = convert;
|
||||
Debug.WriteLine(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an error string to indicate that a <see cref="GBPKM"/> is incompatible.
|
||||
/// </summary>
|
||||
public static string GetIncompatibleGBMessage(PKM pk, bool destJapanese)
|
||||
{
|
||||
var src = destJapanese ? MsgPKMConvertInternational : MsgPKMConvertJapanese;
|
||||
var dest = !destJapanese ? MsgPKMConvertInternational : MsgPKMConvertJapanese;
|
||||
return string.Format(MsgPKMConvertIncompatible, src, pk.GetType().Name, dest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a <see cref="GBPKM"/> is incompatible with the Generation 1/2 destination environment.
|
||||
/// </summary>
|
||||
public static bool IsIncompatibleGB(PKM pk, bool destJapanese, bool srcJapanese)
|
||||
{
|
||||
if (pk.Format > 2)
|
||||
return false;
|
||||
if (destJapanese == srcJapanese)
|
||||
return false;
|
||||
if (pk is SK2 sk2 && sk2.IsPossible(srcJapanese))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
197
PKHeX.Core/PKM/Util/EntityFormat.cs
Normal file
197
PKHeX.Core/PKM/Util/EntityFormat.cs
Normal file
|
@ -0,0 +1,197 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.PokeCrypto;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
using static PKHeX.Core.EntityFormatDetected;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static class EntityFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the generation of the Pokemon data.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data representing a Pokemon.</param>
|
||||
/// <returns>An integer indicating the generation of the PKM file, or -1 if the data is invalid.</returns>
|
||||
public static EntityFormatDetected GetFormat(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (!PKX.IsPKM(data.Length))
|
||||
return None;
|
||||
|
||||
return GetFormatInternal(data);
|
||||
}
|
||||
|
||||
private static EntityFormatDetected GetFormatInternal(ReadOnlySpan<byte> data) => data.Length switch
|
||||
{
|
||||
SIZE_1JLIST or SIZE_1ULIST => FormatPK1,
|
||||
SIZE_2JLIST or SIZE_2ULIST => FormatPK2,
|
||||
SIZE_2STADIUM => FormatSK2,
|
||||
SIZE_3PARTY or SIZE_3STORED => FormatPK3,
|
||||
SIZE_3CSTORED => FormatCK3,
|
||||
SIZE_3XSTORED => FormatXK3,
|
||||
SIZE_4PARTY or SIZE_4STORED => GetFormat45(data),
|
||||
SIZE_5PARTY => FormatPK5,
|
||||
SIZE_6STORED => GetFormat67(data),
|
||||
SIZE_6PARTY => GetFormat67_PGT(data),
|
||||
SIZE_8PARTY or SIZE_8STORED => GetFormat8(data),
|
||||
SIZE_8APARTY or SIZE_8ASTORED => FormatPA8,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
private static EntityFormatDetected GetFormat67_PGT(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (!IsFormat67(data))
|
||||
return None; // PGT collision, same size.
|
||||
return GetFormat67(data);
|
||||
}
|
||||
|
||||
private static bool IsFormat67(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (ReadUInt16LittleEndian(data[0x04..]) != 0) // Bad Sanity?
|
||||
return false; // non-zero ItemID
|
||||
if (ReadUInt16LittleEndian(data[0x06..]) == GetCHK(data, SIZE_6STORED))
|
||||
return true;
|
||||
if (ReadUInt16LittleEndian(data[0x58..]) != 0) // Not encrypted terminator
|
||||
return false;
|
||||
|
||||
// PGT files have the last 0x10 bytes 00; PK6/etc will have data here.
|
||||
var tail = data[..^0x10];
|
||||
foreach (var b in tail)
|
||||
{
|
||||
if (b != 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// assumes decrypted state
|
||||
private static EntityFormatDetected GetFormat45(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (ReadUInt16LittleEndian(data[0x4..]) != 0)
|
||||
return FormatBK4; // BK4 non-zero sanity
|
||||
if (data[0x5F] < 0x10 && ReadUInt16LittleEndian(data[0x80..]) < 0x3333)
|
||||
return FormatPK4; // gen3/4 version origin, not Transporter
|
||||
if (ReadUInt16LittleEndian(data[0x46..]) != 0)
|
||||
return FormatPK4; // PK4.Met_LocationExtended (unused in PK5)
|
||||
return FormatPK5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input PK6 file is really a PK7, if so, updates the object.
|
||||
/// </summary>
|
||||
/// <returns>Updated PKM if actually PK7</returns>
|
||||
private static EntityFormatDetected GetFormat67(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var pk = new PK6(data.ToArray());
|
||||
return IsFormatReally7(pk);
|
||||
}
|
||||
|
||||
// assumes decrypted state
|
||||
private static EntityFormatDetected GetFormat8(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data[0xDE] is (byte)GameVersion.BD or (byte)GameVersion.SP)
|
||||
return FormatPB8;
|
||||
return FormatPK8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="PKM"/> from the given data.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data of the Pokemon file.</param>
|
||||
/// <param name="prefer">Optional identifier for the preferred generation. Usually the generation of the destination save file.</param>
|
||||
/// <returns>An instance of <see cref="PKM"/> created from the given <paramref name="data"/>, or null if <paramref name="data"/> is invalid.</returns>
|
||||
public static PKM? GetFromBytes(byte[] data, int prefer = 7)
|
||||
{
|
||||
var format = GetFormat(data);
|
||||
return GetFromBytes(data, format, prefer);
|
||||
}
|
||||
|
||||
private static PKM? GetFromBytes(byte[] data, EntityFormatDetected format, int prefer) => format switch
|
||||
{
|
||||
FormatPK1 => new PokeList1(data)[0],
|
||||
FormatPK2 => new PokeList2(data)[0],
|
||||
FormatSK2 => new SK2(data),
|
||||
FormatPK3 => new PK3(data),
|
||||
FormatCK3 => new CK3(data),
|
||||
FormatXK3 => new XK3(data),
|
||||
FormatPK4 => new PK4(data),
|
||||
FormatBK4 => new BK4(data),
|
||||
FormatPK5 => new PK5(data),
|
||||
FormatPK6 => new PK6(data),
|
||||
FormatPK7 => new PK7(data),
|
||||
FormatPB7 => new PB7(data),
|
||||
FormatPK8 => new PK8(data),
|
||||
FormatPA8 => new PA8(data),
|
||||
FormatPB8 => new PB8(data),
|
||||
Format6or7 => prefer == 6 ? new PK6(data) : new PK7(data),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input PK6 file is really a PK7.
|
||||
/// </summary>
|
||||
/// <param name="pk">PK6 to check</param>
|
||||
/// <returns>Boolean is a PK7</returns>
|
||||
private static EntityFormatDetected IsFormatReally7(PK6 pk)
|
||||
{
|
||||
if (pk.Version > Legal.MaxGameID_6)
|
||||
{
|
||||
if (pk.Version is ((int)GameVersion.GP or (int)GameVersion.GE))
|
||||
return FormatPB7;
|
||||
return FormatPK7;
|
||||
}
|
||||
|
||||
// Check Ranges
|
||||
if (pk.Species > Legal.MaxSpeciesID_6)
|
||||
return FormatPK7;
|
||||
if (pk.Moves.Any(move => move > Legal.MaxMoveID_6_AO))
|
||||
return FormatPK7;
|
||||
if (pk.RelearnMoves.Any(move => move > Legal.MaxMoveID_6_AO))
|
||||
return FormatPK7;
|
||||
if (pk.Ability > Legal.MaxAbilityID_6_AO)
|
||||
return FormatPK7;
|
||||
if (pk.HeldItem > Legal.MaxItemID_6_AO)
|
||||
return FormatPK7;
|
||||
|
||||
// Ground Tile property is replaced with Hyper Training PK6->PK7
|
||||
var et = pk.GroundTile;
|
||||
if (et != 0)
|
||||
{
|
||||
if (pk.CurrentLevel < 100) // can't be hyper trained
|
||||
return FormatPK6;
|
||||
|
||||
if (!pk.Gen4) // can't have GroundTile
|
||||
return FormatPK7;
|
||||
if (et > GroundTileType.Max_Pt) // invalid gen4 GroundTile
|
||||
return FormatPK7;
|
||||
}
|
||||
|
||||
int mb = ReadUInt16LittleEndian(pk.Data.AsSpan(0x16));
|
||||
if (mb > 0xAAA)
|
||||
return FormatPK6;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if ((mb >> (i << 1) & 3) == 3) // markings are 10 or 01 (or 00), never 11
|
||||
return FormatPK6;
|
||||
}
|
||||
|
||||
if (pk.Data[0x2A] > 20) // ResortEventStatus is always < 20
|
||||
return FormatPK6;
|
||||
|
||||
return Format6or7;
|
||||
}
|
||||
}
|
||||
|
||||
public enum EntityFormatDetected
|
||||
{
|
||||
None = -1,
|
||||
|
||||
FormatPK1,
|
||||
FormatPK2, FormatSK2,
|
||||
FormatPK3, FormatCK3, FormatXK3,
|
||||
FormatPK4, FormatBK4, FormatPK5,
|
||||
FormatPK6, FormatPK7, FormatPB7,
|
||||
FormatPK8, FormatPA8, FormatPB8,
|
||||
|
||||
Format6or7,
|
||||
}
|
|
@ -41,7 +41,7 @@ namespace PKHeX.Core
|
|||
}
|
||||
|
||||
// this is a hack; depends on currently loaded SaveFile's Game ID
|
||||
private static bool IsGG() => PKMConverter.Game is (int)GameVersion.GP or (int)GameVersion.GE;
|
||||
private static bool IsGG() => RecentTrainerCache.Game is (int)GameVersion.GP or (int)GameVersion.GE;
|
||||
|
||||
private static readonly string[] EMPTY = { string.Empty };
|
||||
private const string Starter = nameof(Starter);
|
||||
|
|
|
@ -1,472 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
using static PKHeX.Core.MessageStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic for converting a <see cref="PKM"/> from one generation specific format to another.
|
||||
/// </summary>
|
||||
public static class PKMConverter
|
||||
{
|
||||
public static void SetPrimaryTrainer(ITrainerInfo t)
|
||||
{
|
||||
Trainer = t;
|
||||
if (t is IRegionOrigin o)
|
||||
Trainer67 = o;
|
||||
}
|
||||
|
||||
private static ITrainerInfo Trainer { get; set; } = new SimpleTrainerInfo();
|
||||
private static IRegionOrigin Trainer67 { get; set; } = new SimpleTrainerInfo(GameVersion.SN);
|
||||
public static string OT_Name => Trainer.OT;
|
||||
public static int OT_Gender => Trainer.Gender;
|
||||
public static int Language => Trainer.Language;
|
||||
public static int Format => Trainer.Generation;
|
||||
public static int Game => Trainer.Game;
|
||||
public static bool AllowIncompatibleConversion { private get; set; }
|
||||
|
||||
public static void SetConsoleRegionData3DS(IRegionOrigin pkm)
|
||||
{
|
||||
var trainer = Trainer is IRegionOrigin r ? r : Trainer67;
|
||||
pkm.ConsoleRegion = trainer.ConsoleRegion;
|
||||
pkm.Country = trainer.Country;
|
||||
pkm.Region = trainer.Region;
|
||||
}
|
||||
|
||||
public static void SetFirstCountryRegion(IGeoTrack pkm)
|
||||
{
|
||||
var trainer = Trainer is IRegionOrigin r ? r : Trainer67;
|
||||
pkm.Geo1_Country = trainer.Country;
|
||||
pkm.Geo1_Region = trainer.Region;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the generation of the Pokemon data.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data representing a Pokemon.</param>
|
||||
/// <returns>An integer indicating the generation of the PKM file, or -1 if the data is invalid.</returns>
|
||||
public static int GetPKMDataFormat(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (!PKX.IsPKM(data.Length))
|
||||
return -1;
|
||||
|
||||
switch (data.Length)
|
||||
{
|
||||
case PokeCrypto.SIZE_1JLIST or PokeCrypto.SIZE_1ULIST:
|
||||
return 1;
|
||||
case PokeCrypto.SIZE_2JLIST or PokeCrypto.SIZE_2ULIST:
|
||||
case PokeCrypto.SIZE_2STADIUM:
|
||||
return 2;
|
||||
case PokeCrypto.SIZE_3PARTY or PokeCrypto.SIZE_3STORED:
|
||||
case PokeCrypto.SIZE_3CSTORED:
|
||||
case PokeCrypto.SIZE_3XSTORED:
|
||||
return 3;
|
||||
case PokeCrypto.SIZE_4PARTY or PokeCrypto.SIZE_4STORED:
|
||||
case PokeCrypto.SIZE_5PARTY:
|
||||
if ((ReadUInt16LittleEndian(data[0x4..]) == 0) && (ReadUInt16LittleEndian(data[0x80..]) >= 0x3333 || data[0x5F] >= 0x10) && ReadUInt16LittleEndian(data[0x46..]) == 0) // PK5
|
||||
return 5;
|
||||
return 4;
|
||||
case PokeCrypto.SIZE_6STORED:
|
||||
return 6;
|
||||
case PokeCrypto.SIZE_6PARTY: // collision with PGT, same size.
|
||||
if (ReadUInt16LittleEndian(data[0x4..]) != 0) // Bad Sanity?
|
||||
return -1;
|
||||
if (ReadUInt32LittleEndian(data[0x06..]) == PokeCrypto.GetCHK(data, PokeCrypto.SIZE_6STORED))
|
||||
return 6;
|
||||
if (ReadUInt16LittleEndian(data[0x58..]) != 0) // Encrypted?
|
||||
{
|
||||
for (int i = data.Length - 0x10; i < data.Length; i++) // 0x10 of 00's at the end != PK6
|
||||
{
|
||||
if (data[i] != 0)
|
||||
return 6;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
return 6;
|
||||
case PokeCrypto.SIZE_8PARTY or PokeCrypto.SIZE_8STORED:
|
||||
return 8;
|
||||
case PokeCrypto.SIZE_8APARTY or PokeCrypto.SIZE_8ASTORED:
|
||||
return 8;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="PKM"/> from the given data.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data of the Pokemon file.</param>
|
||||
/// <param name="prefer">Optional identifier for the preferred generation. Usually the generation of the destination save file.</param>
|
||||
/// <returns>An instance of <see cref="PKM"/> created from the given <paramref name="data"/>, or null if <paramref name="data"/> is invalid.</returns>
|
||||
public static PKM? GetPKMfromBytes(byte[] data, int prefer = 7)
|
||||
{
|
||||
int format = GetPKMDataFormat(data);
|
||||
return format switch
|
||||
{
|
||||
1 => new PokeList1(data)[0],
|
||||
2 => data.Length != PokeCrypto.SIZE_2STADIUM ? new PokeList2(data)[0] : new SK2(data),
|
||||
3 => data.Length switch
|
||||
{
|
||||
PokeCrypto.SIZE_3CSTORED => new CK3(data),
|
||||
PokeCrypto.SIZE_3XSTORED => new XK3(data),
|
||||
_ => new PK3(data),
|
||||
},
|
||||
4 => ReadUInt16LittleEndian(data.AsSpan(0x04)) == 0 ? new PK4(data) : new BK4(data),
|
||||
5 => new PK5(data),
|
||||
6 => CheckPKMFormat7(new PK6(data), prefer),
|
||||
8 => CheckPKMFormat8(data),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static PKM CheckPKMFormat8(byte[] data)
|
||||
{
|
||||
if (data.Length is PokeCrypto.SIZE_8ASTORED or PokeCrypto.SIZE_8APARTY)
|
||||
return new PA8(data);
|
||||
|
||||
var pk8 = new PB8(data);
|
||||
var ver = pk8.Version;
|
||||
if (GameVersion.BDSP.Contains(ver))
|
||||
return pk8;
|
||||
|
||||
return new PK8(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input PK6 file is really a PK7, if so, updates the object.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to check</param>
|
||||
/// <param name="prefer">Prefer a certain generation over another</param>
|
||||
/// <returns>Updated PKM if actually PK7</returns>
|
||||
private static G6PKM CheckPKMFormat7(PK6 pk, int prefer)
|
||||
{
|
||||
if (GameVersion.Gen7b.Contains(pk.Version))
|
||||
return new PB7(pk.Data);
|
||||
if (IsPK6FormatReallyPK7(pk, prefer))
|
||||
return new PK7(pk.Data);
|
||||
return pk;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input PK6 file is really a PK7.
|
||||
/// </summary>
|
||||
/// <param name="pk">PK6 to check</param>
|
||||
/// <param name="preferredFormat">Prefer a certain generation over another</param>
|
||||
/// <returns>Boolean is a PK7</returns>
|
||||
private static bool IsPK6FormatReallyPK7(PK6 pk, int preferredFormat)
|
||||
{
|
||||
if (pk.Version > Legal.MaxGameID_6)
|
||||
return true;
|
||||
|
||||
// Check Ranges
|
||||
if (pk.Species > Legal.MaxSpeciesID_6)
|
||||
return true;
|
||||
if (pk.Moves.Any(move => move > Legal.MaxMoveID_6_AO))
|
||||
return true;
|
||||
if (pk.RelearnMoves.Any(move => move > Legal.MaxMoveID_6_AO))
|
||||
return true;
|
||||
if (pk.Ability > Legal.MaxAbilityID_6_AO)
|
||||
return true;
|
||||
if (pk.HeldItem > Legal.MaxItemID_6_AO)
|
||||
return true;
|
||||
|
||||
// Ground Tile property is replaced with Hyper Training PK6->PK7
|
||||
var et = pk.GroundTile;
|
||||
if (et != 0)
|
||||
{
|
||||
if (pk.CurrentLevel < 100) // can't be hyper trained
|
||||
return false;
|
||||
|
||||
if (!pk.Gen4) // can't have GroundTile
|
||||
return true;
|
||||
if (et > GroundTileType.Max_Pt) // invalid gen4 GroundTile
|
||||
return true;
|
||||
}
|
||||
|
||||
int mb = ReadUInt16LittleEndian(pk.Data.AsSpan(0x16));
|
||||
if (mb > 0xAAA)
|
||||
return false;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if ((mb >> (i << 1) & 3) == 3) // markings are 10 or 01 (or 00), never 11
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pk.Data[0x2A] > 20) // ResortEventStatus is always < 20
|
||||
return false;
|
||||
|
||||
return preferredFormat > 6;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input <see cref="PKM"/> file is capable of being converted to the desired format.
|
||||
/// </summary>
|
||||
/// <param name="pk"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsConvertibleToFormat(PKM pk, int format)
|
||||
{
|
||||
if (pk.Format >= 3 && pk.Format > format)
|
||||
return false; // pk3->upward can't go backwards
|
||||
if (pk.Format <= 2 && format is > 2 and < 7)
|
||||
return false; // pk1/2->upward has to be 7 or greater
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a PKM from one Generation format to another. If it matches the destination format, the conversion will automatically return.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to convert</param>
|
||||
/// <param name="destType">Format/Type to convert to</param>
|
||||
/// <param name="comment">Comments regarding the transfer's success/failure</param>
|
||||
/// <returns>Converted PKM</returns>
|
||||
public static PKM? ConvertToType(PKM pk, Type destType, out string comment)
|
||||
{
|
||||
Type fromType = pk.GetType();
|
||||
if (fromType == destType)
|
||||
{
|
||||
comment = "No need to convert, current format matches requested format.";
|
||||
return pk;
|
||||
}
|
||||
|
||||
var pkm = ConvertPKM(pk, destType, fromType, out comment);
|
||||
if (!AllowIncompatibleConversion || pkm != null)
|
||||
return pkm;
|
||||
|
||||
if (pk is PK8 && destType == typeof(PB8))
|
||||
return new PB8(pk.Data);
|
||||
if (pk is PB8 && destType == typeof(PK8))
|
||||
return new PK8(pk.Data);
|
||||
|
||||
// Try Incompatible Conversion
|
||||
pkm = GetBlank(destType);
|
||||
pk.TransferPropertiesWithReflection(pkm);
|
||||
if (!IsPKMCompatibleWithModifications(pkm))
|
||||
return null;
|
||||
comment = "Converted via reflection.";
|
||||
return pkm;
|
||||
}
|
||||
|
||||
private static PKM? ConvertPKM(PKM pk, Type destType, Type srcType, out string comment)
|
||||
{
|
||||
if (IsNotTransferable(pk, out comment))
|
||||
return null;
|
||||
|
||||
string destName = destType.Name;
|
||||
string srcName = srcType.Name;
|
||||
Debug.WriteLine($"Trying to convert {srcName} to {destName}.");
|
||||
|
||||
// All types that inherit PKM have the generation specifier as the last char in their class name.
|
||||
int destGeneration = destName[^1] - '0';
|
||||
var pkm = ConvertPKM(pk, destType, destGeneration, ref comment);
|
||||
var msg = pkm == null ? MsgPKMConvertFailFormat : MsgPKMConvertSuccess;
|
||||
var formatted = string.Format(msg, srcName, destName);
|
||||
comment = comment.Length != 0 ? formatted : string.Concat(formatted, Environment.NewLine, comment);
|
||||
return pkm;
|
||||
}
|
||||
|
||||
private static PKM? ConvertPKM(PKM pk, Type destType, int destGeneration, ref string comment)
|
||||
{
|
||||
PKM? pkm = pk.Clone();
|
||||
if (pkm.IsEgg)
|
||||
pkm.ForceHatchPKM();
|
||||
while (true)
|
||||
{
|
||||
pkm = IntermediaryConvert(pkm, destType, destGeneration, ref comment);
|
||||
if (pkm == null) // fail convert
|
||||
return null;
|
||||
if (pkm.GetType() == destType) // finish convert
|
||||
return pkm;
|
||||
}
|
||||
}
|
||||
|
||||
private static PKM? IntermediaryConvert(PKM pk, Type destType, int destGeneration, ref string comment)
|
||||
{
|
||||
switch (pk)
|
||||
{
|
||||
// Non-sequential
|
||||
case PK1 pk1 when destGeneration > 2: return pk1.ConvertToPK7();
|
||||
case PK2 pk2 when destGeneration > 2: return pk2.ConvertToPK7();
|
||||
case PK2 pk2 when destType == typeof(SK2): return pk2.ConvertToSK2();
|
||||
case PK3 pk3 when destType == typeof(CK3): return pk3.ConvertToCK3();
|
||||
case PK3 pk3 when destType == typeof(XK3): return pk3.ConvertToXK3();
|
||||
case PK4 pk4 when destType == typeof(BK4): return pk4.ConvertToBK4();
|
||||
|
||||
// Invalid
|
||||
case PK2 pk2 when pk.Species > Legal.MaxSpeciesID_1:
|
||||
var lang = pk2.Japanese ? (int)LanguageID.Japanese : (int)LanguageID.English;
|
||||
var name = SpeciesName.GetSpeciesName(pk2.Species, lang);
|
||||
comment = string.Format(MsgPKMConvertFailFormat, name, destType.Name);
|
||||
return null;
|
||||
|
||||
// Sequential
|
||||
case PK1 pk1: return pk1.ConvertToPK2();
|
||||
case PK2 pk2: return pk2.ConvertToPK1();
|
||||
case PK3 pk3: return pk3.ConvertToPK4();
|
||||
case PK4 pk4: return pk4.ConvertToPK5();
|
||||
case PK5 pk5: return pk5.ConvertToPK6();
|
||||
case PK6 pk6: return pk6.ConvertToPK7();
|
||||
case PK7 pk7: return pk7.ConvertToPK8();
|
||||
case PB7 pb7: return pb7.ConvertToPK8();
|
||||
|
||||
// Side-Formats back to Mainline
|
||||
case SK2 sk2: return sk2.ConvertToPK2();
|
||||
case CK3 ck3: return ck3.ConvertToPK3();
|
||||
case XK3 xk3: return xk3.ConvertToPK3();
|
||||
case BK4 bk4: return bk4.ConvertToPK4();
|
||||
|
||||
// None
|
||||
default:
|
||||
comment = MsgPKMConvertFailNoMethod;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a PKM is transferable relative to in-game restrictions and <see cref="PKM.Form"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to convert</param>
|
||||
/// <param name="comment">Comment indicating why the <see cref="PKM"/> is not transferable.</param>
|
||||
/// <returns>Indication if Not Transferable</returns>
|
||||
private static bool IsNotTransferable(PKM pk, out string comment)
|
||||
{
|
||||
switch (pk)
|
||||
{
|
||||
case PK4 { Species: (int)Species.Pichu } pk4 when pk4.Form != 0:
|
||||
case PK6 { Species: (int)Species.Pikachu } pk6 when pk6.Form != 0:
|
||||
case PB7 { Species: (int)Species.Pikachu } pika when pika.Form != 0:
|
||||
case PB7 { Species: (int)Species.Eevee } eevee when eevee.Form != 0:
|
||||
comment = MsgPKMConvertFailForm;
|
||||
return true;
|
||||
default:
|
||||
comment = string.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="PKM"/> is compatible with the input <see cref="PKM"/>, and makes any necessary modifications to force compatibility.
|
||||
/// </summary>
|
||||
/// <remarks>Should only be used when forcing a backwards conversion to sanitize the PKM fields to the target format.
|
||||
/// If the PKM is compatible, some properties may be forced to sanitized values.</remarks>
|
||||
/// <param name="pk">PKM input that is to be sanity checked.</param>
|
||||
/// <returns>Indication whether or not the PKM is compatible.</returns>
|
||||
public static bool IsPKMCompatibleWithModifications(PKM pk) => IsPKMCompatibleWithModifications(pk, pk);
|
||||
|
||||
public static bool IsPKMCompatibleWithModifications(PKM pk, IGameValueLimit limit)
|
||||
{
|
||||
if (pk.Species > limit.MaxSpeciesID)
|
||||
return false;
|
||||
|
||||
if (pk.HeldItem > limit.MaxItemID)
|
||||
pk.HeldItem = 0;
|
||||
|
||||
if (pk.Nickname.Length > limit.NickLength)
|
||||
pk.Nickname = pk.Nickname[..pk.NickLength];
|
||||
|
||||
if (pk.OT_Name.Length > limit.OTLength)
|
||||
pk.OT_Name = pk.OT_Name[..pk.OTLength];
|
||||
|
||||
if (pk.Moves.Any(move => move > limit.MaxMoveID))
|
||||
pk.ClearInvalidMoves();
|
||||
|
||||
int maxEV = pk.MaxEV;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (pk.GetEV(i) > maxEV)
|
||||
pk.SetEV(i, maxEV);
|
||||
}
|
||||
|
||||
int maxIV = pk.MaxIV;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (pk.GetIV(i) > maxIV)
|
||||
pk.SetIV(i, maxIV);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input <see cref="PKM"/> is compatible with the target <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Input to check -> update/sanitize</param>
|
||||
/// <param name="target">Target type PKM with misc properties accessible for checking.</param>
|
||||
/// <param name="c">Comment output</param>
|
||||
/// <param name="pkm">Output compatible PKM</param>
|
||||
/// <returns>Indication if the input is (now) compatible with the target.</returns>
|
||||
public static bool TryMakePKMCompatible(PKM pk, PKM target, out string c, out PKM pkm)
|
||||
{
|
||||
if (!IsConvertibleToFormat(pk, target.Format))
|
||||
{
|
||||
pkm = target;
|
||||
c = string.Format(MsgPKMConvertFailBackwards, pk.GetType().Name, target.Format);
|
||||
if (!AllowIncompatibleConversion)
|
||||
return false;
|
||||
}
|
||||
if (IsIncompatibleGB(target, target.Japanese, pk.Japanese))
|
||||
{
|
||||
pkm = target;
|
||||
c = GetIncompatibleGBMessage(pk, target.Japanese);
|
||||
return false;
|
||||
}
|
||||
var convert = ConvertToType(pk, target.GetType(), out c);
|
||||
if (convert == null)
|
||||
{
|
||||
pkm = target;
|
||||
return false;
|
||||
}
|
||||
|
||||
pkm = convert;
|
||||
Debug.WriteLine(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetIncompatibleGBMessage(PKM pk, bool destJapanese)
|
||||
{
|
||||
var src = destJapanese ? MsgPKMConvertInternational : MsgPKMConvertJapanese;
|
||||
var dest = !destJapanese ? MsgPKMConvertInternational : MsgPKMConvertJapanese;
|
||||
return string.Format(MsgPKMConvertIncompatible, src, pk.GetType().Name, dest);
|
||||
}
|
||||
|
||||
public static bool IsIncompatibleGB(PKM pk, bool destJapanese, bool srcJapanese) => pk.Format <= 2 && destJapanese != srcJapanese && !(pk is SK2 sk2 && sk2.IsPossible(srcJapanese));
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Blank <see cref="PKM"/> object of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of <see cref="PKM"/> instance desired.</param>
|
||||
/// <returns>New instance of a blank <see cref="PKM"/> object.</returns>
|
||||
public static PKM GetBlank(Type type)
|
||||
{
|
||||
var constructors = type.GetTypeInfo().DeclaredConstructors.Where(z => !z.IsStatic);
|
||||
var argCount = constructors.Min(z => z.GetParameters().Length);
|
||||
return (PKM)Activator.CreateInstance(type, new object[argCount]);
|
||||
}
|
||||
|
||||
public static PKM GetBlank(int gen, GameVersion ver) => gen switch
|
||||
{
|
||||
1 when ver == GameVersion.BU => new PK1(true),
|
||||
7 when GameVersion.Gen7b.Contains(ver) => new PB7(),
|
||||
8 when GameVersion.BDSP.Contains(ver) => new PB8(),
|
||||
8 when GameVersion.PLA == ver => new PA8(),
|
||||
_ => GetBlank(gen),
|
||||
};
|
||||
|
||||
public static PKM GetBlank(int gen, int ver) => GetBlank(gen, (GameVersion) ver);
|
||||
|
||||
public static PKM GetBlank(int gen)
|
||||
{
|
||||
var type = Type.GetType($"PKHeX.Core.PK{gen}");
|
||||
if (type is null)
|
||||
throw new InvalidCastException($"Unable to get the type for PK{gen}.");
|
||||
|
||||
return GetBlank(type);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ namespace PKHeX.Core
|
|||
var data = DecodeMessagePKM(message);
|
||||
if (data == null)
|
||||
return null;
|
||||
return PKMConverter.GetPKMfromBytes(data, format);
|
||||
return EntityFormat.GetFromBytes(data, format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
70
PKHeX.Core/PKM/Util/RecentTrainerCache.cs
Normal file
70
PKHeX.Core/PKM/Util/RecentTrainerCache.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Caches a reference to the most recently loaded trainer data.
|
||||
/// </summary>
|
||||
/// <remarks>Useful for sourcing trainer info for the <see cref="PKM"/> ConvertTo methods.</remarks>
|
||||
public static class RecentTrainerCache
|
||||
{
|
||||
private static ITrainerInfo Trainer = new SimpleTrainerInfo();
|
||||
private static IRegionOrigin Trainer67 = new SimpleTrainerInfo(GameVersion.SN);
|
||||
|
||||
private static IRegionOrigin GetTrainer3DS(ITrainerInfo tr) => tr is IRegionOrigin r ? r : Trainer67;
|
||||
|
||||
/// <summary> Most recently loaded <see cref="ITrainerInfo.OT"/>. </summary>
|
||||
public static string OT_Name => Trainer.OT;
|
||||
|
||||
/// <summary> Most recently loaded <see cref="ITrainerInfo.Gender"/>. </summary>
|
||||
public static int OT_Gender => Trainer.Gender;
|
||||
|
||||
/// <summary> Most recently loaded <see cref="ITrainerInfo.Language"/>. </summary>
|
||||
public static int Language => Trainer.Language;
|
||||
|
||||
/// <summary> Most recently loaded <see cref="ITrainerInfo.Generation"/>. </summary>
|
||||
public static int Format => Trainer.Generation;
|
||||
|
||||
/// <summary> Most recently loaded <see cref="ITrainerInfo.Game"/>. </summary>
|
||||
public static int Game => Trainer.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the cache with the most recently loaded trainer reference.
|
||||
/// </summary>
|
||||
/// <param name="trainer"></param>
|
||||
public static void SetRecentTrainer(ITrainerInfo trainer)
|
||||
{
|
||||
Trainer = trainer;
|
||||
if (trainer is IRegionOrigin g67)
|
||||
Trainer67 = g67;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetConsoleRegionData3DS(IRegionOrigin, ITrainerInfo)"/>
|
||||
public static void SetConsoleRegionData3DS(IRegionOrigin pk) => SetConsoleRegionData3DS(pk, Trainer);
|
||||
|
||||
/// <inheritdoc cref="SetFirstCountryRegion(IGeoTrack, ITrainerInfo)"/>
|
||||
public static void SetFirstCountryRegion(IGeoTrack pk) => SetFirstCountryRegion(pk, Trainer);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches an <see cref="IRegionOrigin"/> trainer to apply details to the input <see cref="pk"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Entity to apply details to.</param>
|
||||
/// <param name="trainer">Trainer that is receiving the entity.</param>
|
||||
public static void SetConsoleRegionData3DS(IRegionOrigin pk, ITrainerInfo trainer)
|
||||
{
|
||||
var tr = GetTrainer3DS(trainer);
|
||||
pk.ConsoleRegion = tr.ConsoleRegion;
|
||||
pk.Country = tr.Country;
|
||||
pk.Region = tr.Region;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches an <see cref="IRegionOrigin"/> trainer to apply details to the input <see cref="pk"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Entity to apply details to.</param>
|
||||
/// <param name="trainer">Trainer that is receiving the entity.</param>
|
||||
public static void SetFirstCountryRegion(IGeoTrack pk, ITrainerInfo trainer)
|
||||
{
|
||||
var tr = GetTrainer3DS(trainer);
|
||||
pk.Geo1_Country = tr.Country;
|
||||
pk.Geo1_Region = tr.Region;
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace PKHeX.Core
|
|||
Box = start;
|
||||
SlotsPerBox = slotsPerBox;
|
||||
|
||||
blank = PKMConverter.GetBlank(t);
|
||||
blank = EntityBlank.GetBlank(t);
|
||||
var slots = (Data.Length - Box) / blank.SIZE_STORED;
|
||||
BoxCount = slots / SlotsPerBox;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace PKHeX.Core
|
|||
public sealed override Type PKMType => blank.GetType();
|
||||
public sealed override PKM BlankPKM => blank.Clone();
|
||||
|
||||
protected override PKM GetPKM(byte[] data) => PKMConverter.GetPKMfromBytes(data, prefer: Generation) ?? blank;
|
||||
protected override PKM GetPKM(byte[] data) => EntityFormat.GetFromBytes(data, prefer: Generation) ?? blank;
|
||||
protected override byte[] DecryptPKM(byte[] data) => GetPKM(data).Data;
|
||||
|
||||
protected override int SIZE_STORED => blank.SIZE_STORED;
|
||||
|
|
|
@ -176,7 +176,7 @@ namespace PKHeX.Core
|
|||
var result = files
|
||||
.Where(file => PKX.IsPKM(new FileInfo(file).Length))
|
||||
.Select(File.ReadAllBytes)
|
||||
.Select(data => PKMConverter.GetPKMfromBytes(data, prefer: generation));
|
||||
.Select(data => EntityFormat.GetFromBytes(data, prefer: generation));
|
||||
|
||||
foreach (var pkm in result)
|
||||
{
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace PKHeX.Core
|
|||
if (sav.PKMType != pk.GetType())
|
||||
return false;
|
||||
|
||||
if (sav is ILangDeviantSave il && PKMConverter.IsIncompatibleGB(pk, il.Japanese, pk.Japanese))
|
||||
if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(pk, il.Japanese, pk.Japanese))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -129,16 +129,16 @@ namespace PKHeX.Core
|
|||
|
||||
foreach (var temp in pks)
|
||||
{
|
||||
var pk = PKMConverter.ConvertToType(temp, savtype, out string c);
|
||||
var pk = EntityConverter.ConvertToType(temp, savtype, out string c);
|
||||
if (pk == null)
|
||||
{
|
||||
Debug.WriteLine(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sav is ILangDeviantSave il && PKMConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese))
|
||||
if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese))
|
||||
{
|
||||
c = PKMConverter.GetIncompatibleGBMessage(pk, il.Japanese);
|
||||
c = EntityConverter.GetIncompatibleGBMessage(pk, il.Japanese);
|
||||
Debug.WriteLine(c);
|
||||
continue;
|
||||
}
|
||||
|
@ -160,13 +160,13 @@ namespace PKHeX.Core
|
|||
public static PKM GetCompatiblePKM(this SaveFile sav, PKM pk)
|
||||
{
|
||||
if (pk.Format >= 3 || sav.Generation >= 7)
|
||||
return PKMConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM;
|
||||
return EntityConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM;
|
||||
// gen1-2 compatibility check
|
||||
if (pk.Japanese != ((ILangDeviantSave)sav).Japanese)
|
||||
return sav.BlankPKM;
|
||||
if (sav is SAV2 s2 && s2.Korean != pk.Korean)
|
||||
return sav.BlankPKM;
|
||||
return PKMConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM;
|
||||
return EntityConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -198,11 +198,11 @@ namespace PKHeX.Core
|
|||
if (!File.Exists(path) || !PKX.IsPKM(new FileInfo(path).Length))
|
||||
return LoadTemplateInternal(sav);
|
||||
|
||||
var pk = PKMConverter.GetPKMfromBytes(File.ReadAllBytes(path), prefer: sav.Generation);
|
||||
var pk = EntityFormat.GetFromBytes(File.ReadAllBytes(path), prefer: sav.Generation);
|
||||
if (pk?.Species is not > 0)
|
||||
return LoadTemplateInternal(sav);
|
||||
|
||||
return PKMConverter.ConvertToType(pk, sav.BlankPKM.GetType(), out _) ?? LoadTemplateInternal(sav);
|
||||
return EntityConverter.ConvertToType(pk, sav.BlankPKM.GetType(), out _) ?? LoadTemplateInternal(sav);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ namespace PKHeX.Core
|
|||
return false;
|
||||
}
|
||||
var format = PKX.GetPKMFormatFromExtension(ext, sav?.Generation ?? 6);
|
||||
pk = PKMConverter.GetPKMfromBytes(data, prefer: format);
|
||||
pk = EntityFormat.GetFromBytes(data, prefer: format);
|
||||
return pk != null;
|
||||
}
|
||||
|
||||
|
|
|
@ -280,7 +280,7 @@ namespace PKHeX.WinForms.Controls
|
|||
if (focus)
|
||||
Tab_Main.Focus();
|
||||
|
||||
if (!skipConversionCheck && !PKMConverter.TryMakePKMCompatible(pk, Entity, out string c, out pk))
|
||||
if (!skipConversionCheck && !EntityConverter.TryMakePKMCompatible(pk, Entity, out string c, out pk))
|
||||
{ WinFormsUtil.Alert(c); return; }
|
||||
|
||||
FieldsLoaded = false;
|
||||
|
|
|
@ -872,7 +872,7 @@ namespace PKHeX.WinForms.Controls
|
|||
continue;
|
||||
}
|
||||
|
||||
var convert = PKMConverter.ConvertToType(x, type, out _);
|
||||
var convert = EntityConverter.ConvertToType(x, type, out _);
|
||||
if (convert?.GetType() != type)
|
||||
{
|
||||
slotSkipped++;
|
||||
|
|
|
@ -290,7 +290,7 @@ namespace PKHeX.WinForms.Controls
|
|||
return true; // treat as handled
|
||||
}
|
||||
|
||||
var pk = PKMConverter.ConvertToType(temp, sav.PKMType, out string c);
|
||||
var pk = EntityConverter.ConvertToType(temp, sav.PKMType, out string c);
|
||||
if (pk == null)
|
||||
{
|
||||
WinFormsUtil.Error(c);
|
||||
|
@ -301,9 +301,9 @@ namespace PKHeX.WinForms.Controls
|
|||
if (badDest && (pk.Species == 0 || pk.IsEgg))
|
||||
return false;
|
||||
|
||||
if (sav is ILangDeviantSave il && PKMConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese))
|
||||
if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese))
|
||||
{
|
||||
c = PKMConverter.GetIncompatibleGBMessage(pk, il.Japanese);
|
||||
c = EntityConverter.GetIncompatibleGBMessage(pk, il.Japanese);
|
||||
WinFormsUtil.Error(c);
|
||||
Debug.WriteLine(c);
|
||||
return false;
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace PKHeX.WinForms
|
|||
|
||||
if (HaX)
|
||||
{
|
||||
PKMConverter.AllowIncompatibleConversion = true;
|
||||
EntityConverter.AllowIncompatibleConversion = true;
|
||||
WinFormsUtil.Alert(MsgProgramIllegalModeActive, MsgProgramIllegalModeBehave);
|
||||
}
|
||||
else if (showChangelog)
|
||||
|
@ -401,7 +401,7 @@ namespace PKHeX.WinForms
|
|||
C_SAV.M.Hover.GlowHover = settings.Hover.HoverSlotGlowEdges;
|
||||
ParseSettings.InitFromSettings(settings.Legality);
|
||||
PKME_Tabs.HideSecretValues = C_SAV.HideSecretDetails = settings.Privacy.HideSecretDetails;
|
||||
PKMConverter.AllowIncompatibleConversion = settings.Advanced.AllowIncompatibleConversion;
|
||||
EntityConverter.AllowIncompatibleConversion = settings.Advanced.AllowIncompatibleConversion;
|
||||
WinFormsUtil.DetectSaveFileOnFileOpen = settings.Startup.TryDetectRecentSave;
|
||||
|
||||
SpriteBuilder.LoadSettings(settings.Sprite);
|
||||
|
@ -588,7 +588,7 @@ namespace PKHeX.WinForms
|
|||
|
||||
private bool OpenPKM(PKM pk)
|
||||
{
|
||||
var tmp = PKMConverter.ConvertToType(pk, C_SAV.SAV.PKMType, out string c);
|
||||
var tmp = EntityConverter.ConvertToType(pk, C_SAV.SAV.PKMType, out string c);
|
||||
Debug.WriteLine(c);
|
||||
if (tmp == null)
|
||||
return false;
|
||||
|
@ -615,7 +615,7 @@ namespace PKHeX.WinForms
|
|||
}
|
||||
|
||||
var temp = tg.ConvertToPKM(C_SAV.SAV);
|
||||
var pk = PKMConverter.ConvertToType(temp, C_SAV.SAV.PKMType, out string c);
|
||||
var pk = EntityConverter.ConvertToType(temp, C_SAV.SAV.PKMType, out string c);
|
||||
|
||||
if (pk == null)
|
||||
{
|
||||
|
@ -724,7 +724,7 @@ namespace PKHeX.WinForms
|
|||
|
||||
PKME_Tabs.Focus(); // flush any pending changes
|
||||
StoreLegalSaveGameData(sav);
|
||||
PKMConverter.SetPrimaryTrainer(sav);
|
||||
RecentTrainerCache.SetRecentTrainer(sav);
|
||||
SpriteUtil.Initialize(sav); // refresh sprite generator
|
||||
dragout.Size = new Size(SpriteUtil.Spriter.Width, SpriteUtil.Spriter.Height);
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ namespace PKHeX.WinForms
|
|||
if (index < 0)
|
||||
return;
|
||||
var pk = Results[index].ConvertToPKM(SAV);
|
||||
pk = PKMConverter.ConvertToType(pk, SAV.PKMType, out var c);
|
||||
pk = EntityConverter.ConvertToType(pk, SAV.PKMType, out var c);
|
||||
if (pk == null)
|
||||
{
|
||||
WinFormsUtil.Error(c);
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace PKHeX.Tests.Legality
|
|||
var dn = fi.DirectoryName ?? string.Empty;
|
||||
ParseSettings.AllowGBCartEra = dn.Contains("GBCartEra");
|
||||
ParseSettings.AllowGen1Tradeback = dn.Contains("1 Tradeback");
|
||||
var pkm = PKMConverter.GetPKMfromBytes(data, prefer: format);
|
||||
var pkm = EntityFormat.GetFromBytes(data, prefer: format);
|
||||
pkm.Should().NotBeNull($"the PKM '{new FileInfo(file).Name}' should have been loaded");
|
||||
if (pkm == null)
|
||||
continue;
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace PKHeX.Tests.PKM
|
|||
[InlineData(26, 31, 31, 30, 31, 31, MoveType.Grass, 70, typeof(PK3))]
|
||||
public void HiddenPowerTest(int h, int a, int b, int c, int d, int s, MoveType type, int power, Type pkmType)
|
||||
{
|
||||
var pkm = PKMConverter.GetBlank(pkmType);
|
||||
var pkm = EntityBlank.GetBlank(pkmType);
|
||||
pkm.IV_HP = h;
|
||||
pkm.IV_ATK = a;
|
||||
pkm.IV_DEF = b;
|
||||
|
|
Loading…
Reference in a new issue