using static PKHeX.Core.EvolutionType; namespace PKHeX.Core { /// /// Criteria for evolving to this branch in the /// public sealed class EvolutionMethod { /// /// Evolution Method /// public readonly int Method; /// /// Evolve to Species /// public readonly int Species; /// /// Conditional Argument (different from ) /// public readonly int Argument; /// /// Conditional Argument (different from ) /// public readonly int Level; /// /// Destination Form /// /// Is if the evolved form isn't modified. Special consideration for , which forces 1. public readonly int Form; private const int AnyForm = -1; // Not stored in binary data public bool RequiresLevelUp; // tracks if this method requires a Level Up, lazily set public EvolutionMethod(int method, int species, int argument = 0, int level = 0, int form = AnyForm) { Method = method; Species = species; Argument = argument; Form = form; Level = level; } public override string ToString() => $"{(Species) Species}-{Form} [{Argument}] @ {Level}{(RequiresLevelUp ? "X" : "")}"; /// /// Returns the form that the Pokémon will have after evolution. /// /// Un-evolved Form ID public int GetDestinationForm(int form) { if (Method == (int)LevelUpFormFemale1) return 1; if (Form == AnyForm) return form; return Form; } /// /// Checks the for validity by comparing against the data. /// /// Entity to check /// Current level /// Option to skip some comparisons to return a 'possible' evolution. /// True if a evolution criteria is valid. public bool Valid(PKM pkm, int lvl, bool skipChecks) { RequiresLevelUp = false; switch ((EvolutionType)Method) { case UseItem or UseItemWormhole: case Crit3 or HPDownBy49 or SpinType: case TowerOfDarkness or TowerOfWaters: return true; case UseItemMale: return pkm.Gender == 0; case UseItemFemale: return pkm.Gender == 1; case Trade or TradeHeldItem or TradeSpecies: return !pkm.IsUntraded || skipChecks; // Special Level Up Cases -- return false if invalid case LevelUpNatureAmped or LevelUpNatureLowKey when GetAmpLowKeyResult(pkm.Nature) != pkm.Form && !skipChecks: return false; case LevelUpBeauty when pkm is not IContestStats s || s.CNT_Beauty < Argument: return skipChecks; case LevelUpMale when pkm.Gender != 0: return false; case LevelUpFemale when pkm.Gender != 1: return false; case LevelUpFormFemale1 when pkm.Gender != 1 || pkm.Form != 1: return false; case LevelUpVersion or LevelUpVersionDay or LevelUpVersionNight when ((pkm.Version & 1) != (Argument & 1) && pkm.IsUntraded) || skipChecks: return skipChecks; // Version checks come in pairs, check for any pair match // Level Up (any); the above Level Up (with condition) cases will reach here if they were valid default: if (Level == 0 && lvl < 2) return false; if (lvl < Level) return false; RequiresLevelUp = true; if (skipChecks) return lvl >= Level; // Check Met Level for extra validity return HasMetLevelIncreased(pkm, lvl); } } private bool HasMetLevelIncreased(PKM pkm, int lvl) { int origin = pkm.Generation; return origin switch { // No met data in RBY; No met data in GS, Crystal met data can be reset 1 or 2 => true, // Pal Park / PokeTransfer updates Met Level 3 or 4 => pkm.Format > origin || pkm.Met_Level < lvl, // 5=>6 and later transfers keep current level >=5 => lvl >= Level && (!pkm.IsNative || pkm.Met_Level < lvl), _ => false, }; } public EvoCriteria GetEvoCriteria(int species, int form, int lvl) { return new(species, form) { Level = lvl, Method = Method, }; } public static int GetAmpLowKeyResult(int n) { var index = n - 1; if ((uint)index > 22) return 0; return (0b_0101_1011_1100_1010_0101_0001 >> index) & 1; } } }