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 EntityCompatibilitySetting AllowIncompatibleConversion { get; set; } /// /// Toggles rejuvenating lost data if direct transfer does not know how to revert fields like Met Location and Ball. /// public static EntityRejuvenationSetting RejuvenateHOME { get; set; } = EntityRejuvenationSetting.MissingDataHOME; /// /// Post-conversion rejuvenation worker to restore lost values. /// public static IEntityRejuvenator RejuvenatorHOME { get; set; } = new LegalityRejuvenator(); /// /// 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 (pkm is not null) { if (RejuvenateHOME != EntityRejuvenationSetting.None) RejuvenatorHOME.Rejuvenate(pkm, pk); return pkm; } if (AllowIncompatibleConversion != EntityCompatibilitySetting.AllowIncompatibleAll) { if (result is not NoTransferRoute) return null; if (AllowIncompatibleConversion != EntityCompatibilitySetting.AllowIncompatibleSane) return null; } // 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) { result = CheckTransferOutbound(pk); if (result != Success) return null; result = CheckTransferInbound(pk, destType); if (result != Success) 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(), PB8 pb8 when destType == typeof(PK8) => pb8.ConvertToPK8(), PK8 pk8 when destType == typeof(PB8) => pk8.ConvertToPB8(), G8PKM pk8 when destType == typeof(PA8) => pk8.ConvertToPA8(), PA8 pa8 when destType == typeof(PK8) => pa8.ConvertToPK8(), PA8 pa8 when destType == typeof(PB8) => pa8.ConvertToPB8(), // 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 out of a specific format. /// /// PKM to convert /// Indication if Not Transferable private static EntityConverterResult CheckTransferOutbound(PKM pk) => pk switch { PK4 { Species: (int)Species.Pichu, Form: not 0 } => IncompatibleForm, PK6 { Species: (int)Species.Pikachu, Form: not 0 } => IncompatibleForm, PB7 { Species: (int)Species.Pikachu, Form: not 0 } => IncompatibleForm, PB7 { Species: (int)Species.Eevee, Form: not 0 } => IncompatibleForm, PB8 { Species: (int)Species.Spinda } => IncompatibleSpecies, // Incorrect arrangement of spots (PID endianness) PB8 { Species: (int)Species.Nincada } => IncompatibleSpecies, // Clone paranoia with Shedinja _ => Success, }; /// /// Checks to see if a PKM is transferable into a specific format. /// /// PKM to convert /// Type to convert to /// Indication if Not Transferable private static EntityConverterResult CheckTransferInbound(PKM pk, Type destType) { if (destType == typeof(PB8)) { return pk.Species switch { (int)Species.Nincada => IncompatibleSpecies, (int)Species.Spinda => IncompatibleSpecies, _ => Success, }; } if (destType.Name[^1] == '1' && pk.Species > Legal.MaxSpeciesID_1) return IncompatibleSpecies; return Success; } /// /// 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 == EntityCompatibilitySetting.DisallowIncompatible) { 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; } }