using System; using System.Collections.Generic; 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; /// /// Default when applying markings. /// // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global public static Func> MarkingMethod { get; set; } = FlagHighLow; /// /// 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 SetAltForm(this PKM pk, int form) { switch (pk.Format) { case 2: while (pk.AltForm != form) pk.SetRandomIVs(); break; case 3: pk.SetPIDUnown3(form); break; default: pk.AltForm = 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 abilities = pk.PersonalInfo.Abilities; int abilIndex = Array.IndexOf(abilities, abil); abilIndex = Math.Max(0, abilIndex); pk.SetAbilityIndex(abilIndex); } /// /// 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 abilIndex) { if (pk is PK5 pk5 && abilIndex == 2) pk5.HiddenAbility = true; else if (pk.Format <= 5) pk.PID = PKX.GetRandomPID(pk.Species, pk.Gender, pk.Version, pk.Nature, pk.AltForm, (uint)(abilIndex * 0x10001)); pk.RefreshAbility(abilIndex); } /// /// 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.GenNumber; if (2 < gen && gen < 6) { pk.EncryptionConstant = pk.PID; return; } int wIndex = WurmpleUtil.GetWurmpleEvoGroup(pk.Species); if (wIndex != -1) { pk.EncryptionConstant = WurmpleUtil.GetWurmpleEC(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. /// Square shiny /// Returns true if the data was modified. public static bool SetShiny(PKM pk, bool xor0 = false) { if (pk.IsShiny) return false; while (true) { pk.SetShiny(); if (pk.Format <= 7) return true; var xor = pk.ShinyXor; if (xor0 ? xor == 0 : xor != 0) 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)); if (pk.Format >= 8) pk.StatNature = value; else if (pk.Format <= 4) pk.SetPIDNature(value); else pk.Nature = value; } /// /// 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, Nature nature) => pk.SetNature((int) nature); /// /// Sets the individual PP Up count values depending if a Move is present in the moveslot or not. /// /// Pokémon to modify. /// to use (if already known). Will fetch the current if not provided. public static void SetMaximumPPUps(this PKM pk, int[] moves) { pk.Move1_PPUps = GetPPUpCount(moves[0]); pk.Move2_PPUps = GetPPUpCount(moves[1]); pk.Move3_PPUps = GetPPUpCount(moves[2]); pk.Move4_PPUps = GetPPUpCount(moves[3]); pk.SetMaximumPPCurrent(moves); static int GetPPUpCount(int moveID) => moveID > 0 ? 3 : 0; } /// /// Sets the individual PP Up count values depending if a Move is present in the moveslot or not. /// /// Pokémon to modify. public static void SetMaximumPPUps(this PKM pk) => pk.SetMaximumPPUps(pk.Moves); /// /// Updates the and updates the current PP counts. /// /// Pokémon to modify. /// to set. Will be resized if 4 entries are not present. /// Option to maximize PP Ups public static void SetMoves(this PKM pk, int[] moves, bool maxPP = false) { if (moves.Any(z => z > pk.MaxMoveID)) moves = moves.Where(z => z <= pk.MaxMoveID).ToArray(); if (moves.Length != 4) Array.Resize(ref moves, 4); pk.Moves = moves; if (maxPP) pk.SetMaximumPPUps(moves); else pk.SetMaximumPPCurrent(moves); pk.FixMoves(); } /// /// Updates the individual PP count values for each moveslot based on the maximum possible value. /// /// Pokémon to modify. /// to use (if already known). Will fetch the current if not provided. public static void SetMaximumPPCurrent(this PKM pk, int[] moves) { pk.Move1_PP = moves.Length == 0 ? 0 : pk.GetMovePP(moves[0], pk.Move1_PPUps); pk.Move2_PP = moves.Length <= 1 ? 0 : pk.GetMovePP(moves[1], pk.Move2_PPUps); pk.Move3_PP = moves.Length <= 2 ? 0 : pk.GetMovePP(moves[2], pk.Move3_PPUps); pk.Move4_PP = moves.Length <= 3 ? 0 : pk.GetMovePP(moves[3], pk.Move4_PPUps); } /// /// Updates the individual PP count values for each moveslot based on the maximum possible value. /// /// Pokémon to modify. public static void SetMaximumPPCurrent(this PKM pk) => pk.SetMaximumPPCurrent(pk.Moves); /// /// Sets the value, with special consideration for the values which derive the value. /// /// Pokémon to modify. /// Desired value to set. public static void SetGender(this PKM pk, string gender) { int g = string.IsNullOrEmpty(gender) ? pk.GetSaneGender() : PKX.GetGenderFromString(gender); pk.SetGender(g); } /// /// Sets the value, with special consideration for the values which derive the value. /// /// Pokémon to modify. /// Desired value to set. public static void SetGender(this PKM pk, int gender) { gender = Math.Min(2, Math.Max(0, gender)); if (pk.Format <= 2) { pk.SetATKIVGender(gender); } else if (pk.Format <= 5) { pk.SetPIDGender(gender); pk.Gender = gender; } else { pk.Gender = gender; } } /// /// Fetches based on the provided . /// /// Pokémon to modify. /// best suited for the current data. public static IReadOnlyList GetSuggestedRelearnMoves(this PKM pk) => new LegalityAnalysis(pk).GetSuggestedRelearnMoves(); /// /// Fetches based on the provided . /// /// which contains parsed information pertaining to legality. /// best suited for the current data. public static IReadOnlyList GetSuggestedRelearnMoves(this LegalityAnalysis legal) { var m = legal.GetSuggestedRelearn(); if (m.Any(z => z != 0)) return m; var enc = legal.EncounterMatch; if (enc is MysteryGift || enc is EncounterEgg) return m; var encounter = EncounterSuggestion.GetSuggestedMetInfo(legal.pkm); if (encounter is IRelearn r && r.Relearn.Length > 0) m = r.Relearn; return m; } /// /// Sanity checks the provided value, and returns a sane value. /// /// /// Most-legal value public static int GetSaneGender(this PKM pk) { int gt = pk.PersonalInfo.Gender; switch (gt) { case 255: return 2; // Genderless case 254: return 1; // Female-Only case 0: return 0; // Male-Only } if (!pk.IsGenderValid()) return PKX.GetGenderFromPIDAndRatio(pk.PID, gt); return pk.Gender; } /// /// Copies details to the . /// /// Pokémon to modify. /// details to copy from. public static void ApplySetDetails(this PKM pk, ShowdownSet 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; pk.EVs = Set.EVs; pk.SetSuggestedHyperTrainingData(Set.IVs); if (ShowdownSetIVMarkings) pk.SetMarkings(); pk.SetNickname(Set.Nickname); pk.SetAltForm(Set.FormIndex); pk.SetGender(Set.Gender); pk.SetMaximumPPUps(Set.Moves); 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++) pk.SetEV(i, 0); b.ResetCalculatedValues(); } } if (pk is IGigantamax c) c.CanGigantamax = Set.CanGigantamax; pk.ClearRecordFlags(); pk.SetRecordFlags(Set.Moves); 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.GetFormatHeldItemID(item, format, pk.Format); pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item; } /// /// Sets all Memory related data to the default value (zero). /// /// Pokémon to modify. public static void ClearMemories(this PKM pk) { pk.OT_Memory = pk.OT_Affection = pk.OT_Feeling = pk.OT_Intensity = pk.OT_TextVar = pk.HT_Memory = pk.HT_Affection = pk.HT_Feeling = pk.HT_Intensity = pk.HT_TextVar = 0; } /// /// Sets the to indicate flawless (or near-flawless) . /// /// Pokémon to modify. /// to use (if already known). Will fetch the current if not provided. public static void SetMarkings(this PKM pk, int[] ivs) { if (pk.Format <= 3) return; // no markings (gen3 only has 4; can't mark stats intelligently var markings = ivs.Select(MarkingMethod(pk)).ToArray(); pk.Markings = PKX.ReorderSpeedLast(markings); } /// /// Sets the to indicate flawless (or near-flawless) . /// /// Pokémon to modify. public static void SetMarkings(this PKM pk) { if (pk.Format <= 3) return; // no markings (gen3 only has 4; can't mark stats intelligently pk.SetMarkings(pk.IVs); } private static Func FlagHighLow(PKM pk) { if (pk.Format < 7) return GetSimpleMarking; return GetComplexMarking; static int GetSimpleMarking(int val, int _) => val == 31 ? 1 : 0; static int GetComplexMarking(int val, int _) { if (val == 31 || val == 1) return 1; if (val == 30 || val == 0) return 2; return 0; } } /// /// Sets one of the based on its index within the array. /// /// Pokémon to modify. /// Index to set to /// Value to set public static void SetEV(this PKM pk, int index, int value) { switch (index) { case 0: pk.EV_HP = value; break; case 1: pk.EV_ATK = value; break; case 2: pk.EV_DEF = value; break; case 3: pk.EV_SPE = value; break; case 4: pk.EV_SPA = value; break; case 5: pk.EV_SPD = value; break; default: 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 void SetIV(this PKM pk, int index, int value) { switch (index) { case 0: pk.IV_HP = value; break; case 1: pk.IV_ATK = value; break; case 2: pk.IV_DEF = value; break; case 3: pk.IV_SPE = value; break; case 4: pk.IV_SPA = value; break; case 5: pk.IV_SPD = value; break; default: throw new ArgumentOutOfRangeException(nameof(index)); } } /// /// Updates the for a Generation 1/2 format . /// /// Pokémon to modify. /// Desired . public static void SetATKIVGender(this PKM pk, int gender) { while (pk.Gender != gender) pk.IV_ATK = Util.Rand.Next(pk.MaxIV + 1); } /// /// 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 EVs = pk.EVs; EVs[index] = 0; var sum = EVs.Sum(); 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 maxmimum. /// 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; } /// /// Sets the to match a provided . /// /// Pokémon to modify. /// Desired Hidden Power typing. public static void SetHiddenPower(this PKM pk, int hptype) { var IVs = pk.IVs; HiddenPower.SetIVsForType(hptype, pk.IVs, pk.Format); pk.IVs = IVs; } /// /// Sets the to match a provided . /// /// Pokémon to modify. /// Desired Hidden Power typing. public static void SetHiddenPower(this PKM pk, MoveType hptype) => pk.SetHiddenPower((int) hptype); /// /// Sets the Technical Record flags for the . /// /// Pokémon to modify. /// Value to set for the record. /// Max record to set. public static void SetRecordFlags(this PKM pk, bool value, int max = 100) { if (!(pk is PK8 pk8)) return; for (int i = 0; i < max; i++) pk8.SetMoveRecordFlag(i, value); } /// /// Clears the Technical Record flags for the . /// /// Pokémon to modify. public static void ClearRecordFlags(this PKM pk) => pk.SetRecordFlags(false, 112); /// /// Sets the Technical Record flags for the based on the current moves. /// /// Pokémon to modify. /// Moves to set flags for. If a move is not a Technical Record, it is skipped. public static void SetRecordFlags(this PKM pk, IEnumerable moves) { if (!(pk is PK8 pk8)) return; var permit = pk8.PersonalInfo.TMHM; foreach (var m in moves) { var index = Array.IndexOf(Legal.TMHM_SWSH, m, 100); if (index < 100) continue; if (permit[index]) pk8.SetMoveRecordFlag(index - 100, true); } } /// /// Sets all the Technical Record flags for the if they are permitted to be learned in-game. /// /// Pokémon to modify. public static void SetRecordFlags(this PKM pk) { if (!(pk is PK8 pk8)) return; var permit = pk8.PersonalInfo.TMHM; for (int i = 100; i < permit.Length; i++) { if (permit[i]) pk8.SetMoveRecordFlag(i - 100, true); } } /// /// Refreshes the Move PP for the desired move. /// /// Pokémon to modify. /// Move PP to refresh. public static void SetSuggestedMovePP(this PKM pk, int index) { switch (index) { case 0: pk.Move1_PP = pk.GetMovePP(pk.Move1, pk.Move1_PPUps); return; case 1: pk.Move2_PP = pk.GetMovePP(pk.Move2, pk.Move2_PPUps); return; case 2: pk.Move3_PP = pk.GetMovePP(pk.Move3, pk.Move3_PPUps); return; case 3: pk.Move4_PP = pk.GetMovePP(pk.Move4, pk.Move4_PPUps); return; default: throw new ArgumentException(nameof(index)); } } /// /// 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, 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(); } /// /// Gets a moveset for the provided data. /// /// PKM to generate for /// Full movepool & shuffling /// 4 moves public static int[] GetMoveSet(this PKM pk, bool random = false) { var la = new LegalityAnalysis(pk); var moves = pk.GetMoveSet(la, random); if (random) return moves; var clone = pk.Clone(); clone.SetMoves(moves); var newLa = new LegalityAnalysis(pk); // ReSharper disable once TailRecursiveCall return newLa.Valid ? moves : GetMoveSet(pk, true); } /// /// Gets a moveset for the provided data. /// /// PKM to generate for /// Precomputed optional /// Full movepool & shuffling /// 4 moves public static int[] GetMoveSet(this PKM pk, LegalityAnalysis la, bool random = false) { int[] m = la.GetSuggestedMoves(tm: random, tutor: random, reminder: random); if (m == null) return pk.Moves; if (!m.All(z => la.AllSuggestedMovesAndRelearn().Contains(z))) m = m.Intersect(la.AllSuggestedMovesAndRelearn()).ToArray(); if (random) Util.Shuffle(m); const int count = 4; if (m.Length > count) return m.SliceEnd(m.Length - count); Array.Resize(ref m, count); return m; } /// /// Toggles the marking at a given index. /// /// Pokémon to modify. /// Marking index to toggle /// Current marking values (optional) /// Current marking values public static int[] ToggleMarking(this PKM pk, int index, int[] markings) { switch (pk.Format) { case 3: case 4: case 5: case 6: // on/off markings[index] ^= 1; // toggle pk.Markings = markings; break; case 7: // 0 (none) | 1 (blue) | 2 (pink) case 8: markings[index] = (markings[index] + 1) % 3; // cycle 0->1->2->0... pk.Markings = markings; break; } return markings; } /// /// Toggles the marking at a given index. /// /// Pokémon to modify. /// Marking index to toggle /// Current marking values public static int[] ToggleMarking(this PKM pk, int index) => pk.ToggleMarking(index, pk.Markings); /// /// Sets the Memory details to a Hatched Egg's memories. /// /// Pokémon to modify. public static void SetHatchMemory6(this PKM pk) { pk.OT_Memory = 2; pk.OT_Affection = 0; pk.OT_Feeling = Memories.GetRandomFeeling(pk.OT_Memory); pk.OT_Intensity = 1; pk.OT_TextVar = pk.XY ? 43 : 27; // riverside road : battling spot } /// /// Sets a random memory specific to locality. /// /// Pokémon to modify. public static void SetRandomMemory6(this PKM pk) { // for lack of better randomization :) pk.OT_Memory = 63; pk.OT_Intensity = 6; pk.OT_Feeling = Memories.GetRandomFeeling(pk.OT_Memory); } /// /// 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 t && t.HasNickname) 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 locval = eggmet ? pk.Egg_Location : pk.Met_Location; return GameInfo.GetLocationName(eggmet, locval, pk.Format, pk.GenNumber, (GameVersion)pk.Version); } } }