using System; using System.Linq; namespace PKHeX.Core { /// /// Contains extension logic for modifying data. /// public static class CommonEdits { /// /// Setting which enables/disables automatic manipulation of when importing from a . /// public static bool ShowdownSetIVMarkings { get; set; } = true; /// /// Setting which causes the to the in Gen8+ formats. /// public static bool ShowdownSetBehaviorNature { get; set; } /// /// Sets the to the provided value. /// /// Pokémon to modify. /// to set. If no nickname is provided, the is set to the default value for its current language and format. public static void SetNickname(this PKM pk, string nick) { if (string.IsNullOrWhiteSpace(nick)) { pk.ClearNickname(); return; } pk.IsNicknamed = true; pk.Nickname = nick; } /// /// Clears the to the default value. /// /// public static string ClearNickname(this PKM pk) { pk.IsNicknamed = false; string nick = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format); pk.Nickname = nick; if (pk is GBPKM pk12) pk12.SetNotNicknamed(); return nick; } /// /// Sets the value, with special consideration for values which derive the value. /// /// Pokémon to modify. /// Desired value to set. public static void SetForm(this PKM pk, int form) { switch (pk.Format) { case 2: while (pk.Form != form) pk.SetRandomIVs(); break; case 3: pk.SetPIDUnown3(form); break; default: pk.Form = form; break; } } /// /// Sets the value by sanity checking the provided against the possible pool of abilities. /// /// Pokémon to modify. /// Desired to set. public static void SetAbility(this PKM pk, int abil) { if (abil < 0) return; var index = pk.PersonalInfo.GetAbilityIndex(abil); index = Math.Max(0, index); pk.SetAbilityIndex(index); } /// /// Sets the value based on the provided ability index (0-2) /// /// Pokémon to modify. /// Desired (shifted by 1) to set. public static void SetAbilityIndex(this PKM pk, int index) { if (pk is PK5 pk5 && index == 2) pk5.HiddenAbility = true; else if (pk.Format <= 5) pk.PID = PKX.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001)); pk.RefreshAbility(index); } /// /// Sets a Random value. The is not updated if the value should match the instead. /// /// Accounts for Wurmple evolutions. /// Pokémon to modify. public static void SetRandomEC(this PKM pk) { int gen = pk.Generation; if (gen is 3 or 4 or 5) { pk.EncryptionConstant = pk.PID; return; } int wIndex = WurmpleUtil.GetWurmpleEvoGroup(pk.Species); if (wIndex != -1) { pk.EncryptionConstant = WurmpleUtil.GetWurmpleEncryptionConstant(wIndex); return; } pk.EncryptionConstant = Util.Rand32(); } /// /// Sets the derived value. /// /// Pokémon to modify. /// Desired state to set. /// public static bool SetIsShiny(this PKM pk, bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny(); /// /// Makes a shiny. /// /// Pokémon to modify. /// Shiny type to force. Only use Always* or Random /// Returns true if the data was modified. public static bool SetShiny(PKM pk, Shiny type = Shiny.Random) { if (pk.IsShiny && type.IsValid(pk)) return false; if (type == Shiny.Random || pk.FatefulEncounter || pk.Version == (int)GameVersion.GO || pk.Format <= 2) { pk.SetShiny(); return true; } do { pk.SetShiny(); } while (!type.IsValid(pk)); return true; } /// /// Makes a not-shiny. /// /// Pokémon to modify. /// Returns true if the data was modified. public static bool SetUnshiny(this PKM pk) { if (!pk.IsShiny) return false; pk.SetPIDGender(pk.Gender); return true; } /// /// Sets the value, with special consideration for the values which derive the value. /// /// Pokémon to modify. /// Desired value to set. public static void SetNature(this PKM pk, int nature) { var value = Math.Min((int)Nature.Quirky, Math.Max((int)Nature.Hardy, nature)); var format = pk.Format; if (format >= 8) pk.StatNature = value; else if (format is 3 or 4) pk.SetPIDNature(value); else pk.Nature = value; } /// /// Copies details to the . /// /// Pokémon to modify. /// details to copy from. public static void ApplySetDetails(this PKM pk, IBattleTemplate Set) { pk.Species = Math.Min(pk.MaxSpeciesID, Set.Species); pk.SetMoves(Set.Moves, true); pk.ApplyHeldItem(Set.HeldItem, Set.Format); pk.CurrentLevel = Set.Level; pk.CurrentFriendship = Set.Friendship; pk.IVs = Set.IVs; if (pk is GBPKM gb) { // In Generation 1/2 Format sets, when IVs are not specified with a Hidden Power set, we might not have the hidden power type. // Under this scenario, just force the Hidden Power type. if (Set.Moves.Contains(237) && pk.HPType != Set.HiddenPowerType && Set.IVs.Any(z => z >= 30)) pk.SetHiddenPower(Set.HiddenPowerType); // In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead! // Under this scenario, just apply maximum EVs (65535). if (Set.EVs.All(z => z == 0)) gb.MaxEVs(); else pk.EVs = Set.EVs; } else { pk.EVs = Set.EVs; } // IVs have no side effects such as hidden power type in gen 8 // therefore all specified IVs are deliberate and should not be Hyper Trained for pokemon met in gen 8 if (!pk.Gen8) pk.SetSuggestedHyperTrainingData(Set.IVs); if (ShowdownSetIVMarkings) pk.SetMarkings(); pk.SetNickname(Set.Nickname); pk.SetForm(Set.Form); pk.SetSaneGender(Set.Gender); pk.SetMaximumPPUps(Set.Moves); if (pk.Format >= 3) { pk.SetAbility(Set.Ability); pk.SetNature(Set.Nature); } pk.SetIsShiny(Set.Shiny); pk.SetRandomEC(); if (pk is IAwakened a) { a.SetSuggestedAwakenedValues(pk); if (pk is PB7 b) { for (int i = 0; i < 6; i++) b.SetEV(i, 0); b.ResetCalculatedValues(); } } if (pk is IGigantamax c) c.CanGigantamax = Set.CanGigantamax; if (pk is IDynamaxLevel d) d.DynamaxLevel = d.CanHaveDynamaxLevel(pk) ? (byte)10 : (byte)0; pk.ClearRecordFlags(); pk.SetRecordFlags(Set.Moves); if (ShowdownSetBehaviorNature && pk.Format >= 8) pk.Nature = pk.StatNature; var legal = new LegalityAnalysis(pk); if (legal.Parsed && legal.Info.Relearn.Any(z => !z.Valid)) pk.SetRelearnMoves(legal.GetSuggestedRelearnMoves()); pk.ResetPartyStats(); pk.RefreshChecksum(); } /// /// Sets the value depending on the current format and the provided item index & format. /// /// Pokémon to modify. /// Held Item to apply /// Format required for importing public static void ApplyHeldItem(this PKM pk, int item, int format) { item = ItemConverter.GetItemForFormat(item, format, pk.Format); pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item; } /// /// Sets one of the based on its index within the array. /// /// Pokémon to modify. /// Index to set to /// Value to set public static int SetEV(this PKM pk, int index, int value) => index switch { 0 => pk.EV_HP = value, 1 => pk.EV_ATK = value, 2 => pk.EV_DEF = value, 3 => pk.EV_SPE = value, 4 => pk.EV_SPA = value, 5 => pk.EV_SPD = value, _ => throw new ArgumentOutOfRangeException(nameof(index)), }; /// /// Sets one of the based on its index within the array. /// /// Pokémon to modify. /// Index to set to /// Value to set public static int SetIV(this PKM pk, int index, int value) => index switch { 0 => pk.IV_HP = value, 1 => pk.IV_ATK = value, 2 => pk.IV_DEF = value, 3 => pk.IV_SPE = value, 4 => pk.IV_SPA = value, 5 => pk.IV_SPD = value, _ => throw new ArgumentOutOfRangeException(nameof(index)), }; /// /// Fetches the highest value the provided index can be while considering others. /// /// Pokémon to modify. /// Index to fetch for /// Highest value the value can be. public static int GetMaximumEV(this PKM pk, int index) { if (pk.Format < 3) return ushort.MaxValue; var sum = pk.EVTotal - pk.GetEV(index); int remaining = 510 - sum; return Math.Min(Math.Max(remaining, 0), 252); } /// /// Fetches the highest value the provided . /// /// Pokémon to modify. /// Index to fetch for /// Causes the returned value to be dropped down -1 if the value is already at a maximum. /// Highest value the value can be. public static int GetMaximumIV(this PKM pk, int index, bool allow30 = false) { if (pk.GetIV(index) == pk.MaxIV && allow30) return pk.MaxIV - 1; return pk.MaxIV; } /// /// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game. /// /// PKM to apply hatch details to /// Re-hatch already hatched inputs public static void ForceHatchPKM(this PKM pk, bool reHatch = false) { if (!pk.IsEgg && !reHatch) return; pk.IsEgg = false; pk.ClearNickname(); pk.CurrentFriendship = pk.PersonalInfo.BaseFriendship; if (pk.IsTradedEgg) pk.Egg_Location = pk.Met_Location; var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pk); if (loc >= 0) pk.Met_Location = loc; pk.MetDate = DateTime.Today; if (pk.Gen6) pk.SetHatchMemory6(); } /// /// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game. /// /// PKM to apply hatch details to /// Game the egg originated from /// Game the egg is currently present on public static void SetEggMetData(this PKM pk, GameVersion origin, GameVersion dest) { bool traded = origin != dest; var today = pk.MetDate = DateTime.Today; pk.Egg_Location = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(pk.Generation, origin, traded); pk.EggMetDate = today; } /// /// Maximizes the . If the , the hatch counter is set to 1. /// /// PKM to apply hatch details to public static void MaximizeFriendship(this PKM pk) { if (pk.IsEgg) pk.OT_Friendship = 1; else pk.CurrentFriendship = byte.MaxValue; if (pk is PB7 pb) pb.ResetCP(); } /// /// Maximizes the . If the , the is ignored. /// /// PKM to apply hatch details to public static void MaximizeLevel(this PKM pk) { if (pk.IsEgg) return; pk.CurrentLevel = 100; if (pk is PB7 pb) pb.ResetCP(); } /// /// Sets the to its default value. /// /// Pokémon to modify. /// Precomputed optional public static void SetDefaultNickname(this PKM pk, LegalityAnalysis la) { if (la.Parsed && la.EncounterOriginal is EncounterTrade {HasNickname: true} t) pk.SetNickname(t.GetNickname(pk.Language)); else pk.ClearNickname(); } /// /// Sets the to its default value. /// /// Pokémon to modify. public static void SetDefaultNickname(this PKM pk) => pk.SetDefaultNickname(new LegalityAnalysis(pk)); private static readonly string[] PotentialUnicode = { "★☆☆☆", "★★☆☆", "★★★☆", "★★★★" }; private static readonly string[] PotentialNoUnicode = { "+", "++", "+++", "++++" }; /// /// Gets the Potential evaluation of the input . /// /// Pokémon to analyze. /// Returned value is unicode or not /// Potential string public static string GetPotentialString(this PKM pk, bool unicode = true) { var arr = unicode ? PotentialUnicode : PotentialNoUnicode; return arr[pk.PotentialRating]; } // Extensions /// /// Gets the Location Name for the /// /// PKM to fetch data for /// Location requested is the egg obtained location, not met location. /// Location string public static string GetLocationString(this PKM pk, bool eggmet) { if (pk.Format < 2) return string.Empty; int location = eggmet ? pk.Egg_Location : pk.Met_Location; return GameInfo.GetLocationName(eggmet, location, pk.Format, pk.Generation, (GameVersion)pk.Version); } } }