using static PKHeX.Core.EvolutionType; namespace PKHeX.Core; /// /// Criteria for evolving to this branch in the /// /// Evolution Method /// Evolve to Species /// Destination Form /// Conditional Argument (different from ) /// Conditional Argument (different from ) /// Indicates if a level up is required to trigger evolution. public readonly record struct EvolutionMethod(EvolutionType Method, ushort Species, byte Form = 0, ushort Argument = 0, byte Level = 0, byte LevelUp = 0) : ISpeciesForm { /// Evolve to Species public ushort Species { get; } = Species; /// Conditional Argument (different from ) public ushort Argument { get; } = Argument; /// Evolution Method public EvolutionType Method { get; } = Method; /// Destination Form public byte Form { get; } = Form; /// Conditional Argument (different from ) public byte Level { get; } = Level; /// Indicates if a level up is required to trigger evolution. public byte LevelUp { get; } = LevelUp; public override string ToString() => $"{(Species)Species}-{Form} [{Argument}] @ {Level}{(RequiresLevelUp ? "X" : "")}"; /// Is if the evolved form isn't modified. Special consideration for , which forces 1. private const byte AnyForm = byte.MaxValue; public bool RequiresLevelUp => LevelUp != 0; /// /// Returns the form that the Pokémon will have after evolution. /// /// Un-evolved Form ID public byte GetDestinationForm(byte form) { if (Method == 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 pk, byte lvl, bool skipChecks) { if (!Method.IsLevelUpRequired()) return ValidNotLevelUp(pk, skipChecks); if (!IsLevelUpMethodSecondarySatisfied(pk, skipChecks)) return false; // Level Up (any); the above Level Up (with condition) cases will reach here if they were valid if (!RequiresLevelUp) return lvl >= Level; if (Level == 0 && lvl < 2) return false; if (lvl < Level) return false; if (skipChecks) return lvl >= Level; // Check Met Level for extra validity return HasMetLevelIncreased(pk, lvl); } private bool IsLevelUpMethodSecondarySatisfied(PKM pk, bool skipChecks) => Method switch { // Special Level Up Cases -- return false if invalid LevelUpMale when pk.Gender != 0 => false, LevelUpFemale when pk.Gender != 1 => false, LevelUpFormFemale1 when pk.Gender != 1 || pk.Form != 1 => false, // Permit the evolution if we're exploring for mistakes. LevelUpBeauty when pk is IContestStats s && s.CNT_Beauty < Argument => skipChecks, LevelUpNatureAmped or LevelUpNatureLowKey when GetAmpLowKeyResult(pk.Nature) != pk.Form => skipChecks, // Version checks come in pairs, check for any pair match LevelUpVersion or LevelUpVersionDay or LevelUpVersionNight when ((pk.Version & 1) != (Argument & 1) && pk.IsUntraded) => skipChecks, _ => true, }; private bool ValidNotLevelUp(PKM pk, bool skipChecks) => Method switch { UseItemMale or RecoilDamageMale => pk.Gender == 0, UseItemFemale or RecoilDamageFemale => pk.Gender == 1, Trade or TradeHeldItem or TradeShelmetKarrablast => !pk.IsUntraded || skipChecks, _ => true, // no conditions }; private bool HasMetLevelIncreased(PKM pk, int lvl) { int origin = pk.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 => pk.Format > origin || lvl > pk.Met_Level, // 5=>6 and later transfers keep current level >=5 => lvl >= Level && (!pk.IsNative || lvl > pk.Met_Level), _ => false, }; } public EvoCriteria GetEvoCriteria(ushort species, byte form, byte lvl) => new() { Species = species, Form = form, LevelMax = lvl, LevelMin = 0, 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; } }