using System;
using System.Diagnostics;
using static PKHeX.Core.EntityConverterResult;
namespace PKHeX.Core;
///
/// Logic for converting a from one generation specific format to another.
///
public static class EntityConverter
{
///
/// If a conversion method does not officially (legally) exist, then the program can try to convert via other means (illegal).
///
public static bool AllowIncompatibleConversion { get; set; }
///
/// Checks if the input file is capable of being converted to the desired format.
///
///
///
/// True if can be converted to the requested format value.
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;
}
///
/// Converts a PKM from one Generation format to another. If it matches the destination format, the conversion will automatically return.
///
/// PKM to convert
/// Format/Type to convert to
/// Comments regarding the transfer's success/failure
/// Converted PKM
public static PKM? ConvertToType(PKM pk, Type destType, out EntityConverterResult result)
{
Type fromType = pk.GetType();
if (fromType == destType)
{
result = None;
return pk;
}
var pkm = ConvertPKM(pk, destType, fromType, out result);
if (!AllowIncompatibleConversion || pkm != null)
return pkm;
if (pk is PK8 && destType == typeof(PB8))
{
result = SuccessIncompatibleManual;
return new PB8((byte[])pk.Data.Clone());
}
if (pk is PB8 && destType == typeof(PK8))
{
result = SuccessIncompatibleManual;
return new PK8((byte[])pk.Data.Clone());
}
// Try Incompatible Conversion
pkm = EntityBlank.GetBlank(destType);
pk.TransferPropertiesWithReflection(pkm);
if (!IsCompatibleWithModifications(pkm))
return null; // NoTransferRoute
result = SuccessIncompatibleReflection;
return pkm;
}
private static PKM? ConvertPKM(PKM pk, Type destType, Type srcType, out EntityConverterResult result)
{
if (IsNotTransferable(pk, out result))
return null;
Debug.WriteLine($"Trying to convert {srcType.Name} to {destType.Name}.");
// All types that inherit PKM have the generation specifier as the last char in their class name.
return ConvertPKM(pk, destType, ref result);
}
private static PKM? ConvertPKM(PKM pk, Type destType, ref EntityConverterResult result)
{
PKM? pkm = pk.Clone();
if (pkm.IsEgg)
pkm.ForceHatchPKM();
while (true)
{
pkm = IntermediaryConvert(pkm, destType, ref result);
if (pkm == null) // fail convert
return null;
if (pkm.GetType() == destType) // finish convert
return pkm;
}
}
private static PKM? IntermediaryConvert(PKM pk, Type destType, ref EntityConverterResult result) => pk switch
{
// Non-sequential
PK1 pk1 when destType.Name[^1] - '0' > 2 => pk1.ConvertToPK7(),
PK2 pk2 when destType.Name[^1] - '0' > 2 => pk2.ConvertToPK7(),
PK2 pk2 when destType == typeof(SK2) => pk2.ConvertToSK2(),
PK3 pk3 when destType == typeof(CK3) => pk3.ConvertToCK3(),
PK3 pk3 when destType == typeof(XK3) => pk3.ConvertToXK3(),
PK4 pk4 when destType == typeof(BK4) => pk4.ConvertToBK4(),
// Invalid
PK2 { Species: > Legal.MaxSpeciesID_1 } => InvalidTransfer(out result, IncompatibleSpecies),
// Sequential
PK1 pk1 => pk1.ConvertToPK2(),
PK2 pk2 => pk2.ConvertToPK1(),
PK3 pk3 => pk3.ConvertToPK4(),
PK4 pk4 => pk4.ConvertToPK5(),
PK5 pk5 => pk5.ConvertToPK6(),
PK6 pk6 => pk6.ConvertToPK7(),
PK7 pk7 => pk7.ConvertToPK8(),
PB7 pb7 => pb7.ConvertToPK8(),
// Side-Formats back to Mainline
SK2 sk2 => sk2.ConvertToPK2(),
CK3 ck3 => ck3.ConvertToPK3(),
XK3 xk3 => xk3.ConvertToPK3(),
BK4 bk4 => bk4.ConvertToPK4(),
_ => InvalidTransfer(out result, NoTransferRoute),
};
private static PKM? InvalidTransfer(out EntityConverterResult result, EntityConverterResult value)
{
result = value;
return null;
}
///
/// Checks to see if a PKM is transferable relative to in-game restrictions and .
///
/// PKM to convert
/// Comment indicating why the is not transferable.
/// Indication if Not Transferable
private static bool IsNotTransferable(PKM pk, out EntityConverterResult result)
{
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:
result = IncompatibleForm;
return true;
default:
result = Success;
return false;
}
}
///
/// Checks if the is compatible with the input , and makes any necessary modifications to force compatibility.
///
/// 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.
/// PKM input that is to be sanity checked.
/// Value clamps for the destination format
/// Indication whether or not the PKM is compatible.
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);
}
}
///
public static bool IsCompatibleWithModifications(PKM pk) => IsCompatibleWithModifications(pk, pk);
///
/// Checks if the input is compatible with the target .
///
/// Input to check -> update/sanitize
/// Target type PKM with misc properties accessible for checking.
/// Comment output
/// Output compatible PKM
/// Indication if the input is (now) compatible with the target.
public static bool TryMakePKMCompatible(PKM pk, PKM target, out EntityConverterResult result, out PKM converted)
{
if (!IsConvertibleToFormat(pk, target.Format))
{
converted = target;
if (!AllowIncompatibleConversion)
{
result = NoTransferRoute;
return false;
}
}
if (IsIncompatibleGB(target, target.Japanese, pk.Japanese))
{
converted = target;
result = IncompatibleLanguageGB;
return false;
}
var convert = ConvertToType(pk, target.GetType(), out result);
if (convert == null)
{
converted = target;
return false;
}
converted = convert;
return true;
}
///
/// Checks if a is incompatible with the Generation 1/2 destination environment.
///
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;
}
}