using System; using System.Linq; namespace PKHeX.Core { /// /// Contains extension logic for modifying data. /// public static class CommonEdits { public static bool ShowdownSetIVMarkings { get; set; } = true; /// /// 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 = null) { if (nick != null) { pk.IsNicknamed = true; pk.Nickname = nick; } else { pk.IsNicknamed = false; pk.Nickname = PKX.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format); if (pk is _K12 pk12) pk12.SetNotNicknamed(); } } /// /// 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; int wIndex = Array.IndexOf(Legal.WurmpleEvolutions, pk.Species); if (gen < 6 && gen > 2) pk.EncryptionConstant = pk.PID; else pk.EncryptionConstant = wIndex < 0 ? Util.Rand32() : PKX.GetWurmpleEC(wIndex / 2); } /// /// 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. /// Returns true if the data was modified. public static bool SetShiny(PKM pk) { if (pk.IsShiny) return false; pk.SetShiny(); 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) { if (pk.Format <= 4) pk.SetPIDNature(Math.Max(0, nature)); else pk.Nature = Math.Max(0, nature); } /// /// 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 = null) { if (Moves == null) Moves = pk.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); int GetPPUpCount(int moveID) => moveID > 0 ? 3 : 0; } /// /// 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 = null) { if (Moves == null) Moves = pk.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); } /// /// 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 = gender == null ? 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. /// which contains parsed information pertaining to legality. /// best suited for the current data. public static int[] GetSuggestedRelearnMoves(this PKM pk, LegalityAnalysis legal) { int[] m = legal.GetSuggestedRelearn(); if (!m.All(z => z == 0)) return m; if (pk.WasEgg || pk.WasEvent || pk.WasEventEgg || pk.WasLink) return m; var encounter = legal.GetSuggestedMetInfo(); if (encounter?.Relearn.Length > 0) m = encounter.Relearn; return m; } /// /// Sanity checks the provided value, and returns a sane value. /// /// /// Most-legal value public static int GetSaneGender(this PKM pkm) { int gt = pkm.PersonalInfo.Gender; switch (gt) { case 255: return 2; // Genderless case 254: return 1; // Female-Only case 0: return 0; // Male-Only } if (!pkm.IsGenderValid()) return PKX.GetGenderFromPIDAndRatio(pkm.PID, gt); return pkm.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); var legal = new LegalityAnalysis(pk); if (legal.Parsed && legal.Info.Relearn.Any(z => !z.Valid)) pk.RelearnMoves = pk.GetSuggestedRelearnMoves(legal); 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) { if (item <= 0) { pk.HeldItem = 0; return; } if (format <= 3 && pk.Format != format) { if (pk.Format > 3) // try remapping { item = format == 2 ? ItemConverter.GetG4Item((byte) item) : ItemConverter.GetG4Item((ushort) item); pk.ApplyHeldItem(item, pk.Format); return; } if (pk.Format > format) // can't set past gen items { pk.HeldItem = 0; return; } // ShowdownSet checks gen3 then gen2. For gen2 collisions (if any?) remap 3->4->2. item = ItemConverter.GetG4Item((ushort) item); item = ItemConverter.GetG2Item((ushort) item); if (item == 0 || item < 0) { pk.HeldItem = 0; return; } } switch (pk.Format) { case 3: pk.HeldItem = ItemConverter.GetG3Item((ushort)item); break; case 2: pk.HeldItem = (byte)item; break; case 1: pk.HeldItem = 0; break; default: pk.HeldItem = item; break; } if (pk.HeldItem > pk.MaxItemID) pk.HeldItem = 0; } /// /// 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 = null) { if (pk.Format <= 3) return; // no markings (gen3 only has 4; can't mark stats intelligently if (IVs == null) IVs = pk.IVs; var markings = IVs.Select(MarkingMethod(pk)).ToArray(); pk.Markings = PKX.ReorderSpeedLast(markings); } public static Func> MarkingMethod { get; set; } = FlagHighLow; private static Func FlagHighLow(PKM pk) { if (pk.Format < 7) return GetSimpleMarking; return GetComplexMarking; int GetSimpleMarking(int val, int _) => val == 31 ? 1 : 0; 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)); } } /// /// Gets one of the based on its index within the array. /// /// Pokémon to check. /// Index to get public static int GetEV(this PKM pk, int index) { switch (index) { case 0: return pk.EV_HP ; case 1: return pk.EV_ATK; case 2: return pk.EV_DEF; case 3: return pk.EV_SPE; case 4: return pk.EV_SPA; case 5: return pk.EV_SPD; default: throw new ArgumentOutOfRangeException(nameof(index)); } } /// /// Gets one of the based on its index within the array. /// /// Pokémon to check. /// Index to get public static int GetIV(this PKM pk, int index) { switch (index) { case 0: return pk.IV_HP ; case 1: return pk.IV_ATK; case 2: return pk.IV_DEF; case 3: return pk.IV_SPE; case 4: return pk.IV_SPA; case 5: return pk.IV_SPD; 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.IVs[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); /// /// 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 pkm, bool reHatch = false) { if (!pkm.IsEgg && !reHatch) return; pkm.IsEgg = false; pkm.SetNickname(); pkm.CurrentFriendship = pkm.PersonalInfo.BaseFriendship; if (pkm.IsTradedEgg) pkm.Egg_Location = pkm.Met_Location; var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pkm); if (loc >= 0) pkm.Met_Location = loc; pkm.MetDate = DateTime.Today; if (pkm.Gen6) pkm.SetHatchMemory6(); } /// /// Maximizes the . If the , the hatch counter is set to 1. /// /// PKM to apply hatch details to public static void MaximizeFriendship(this PKM pkm) { if (pkm.IsEgg) pkm.OT_Friendship = 1; else pkm.CurrentFriendship = byte.MaxValue; } /// /// Maximizes the . If the , the is ignored. /// /// PKM to apply hatch details to public static void MaximizeLevel(this PKM pkm) { if (pkm.IsEgg) return; pkm.CurrentLevel = 100; } /// /// Gets a moveset for the provided data. /// /// PKM to generate for /// Full movepool & shuffling /// Precomputed optional /// 4 moves public static int[] GetMoveSet(this PKM pkm, bool random = false, LegalityAnalysis la = null) { if (la == null) la = new LegalityAnalysis(pkm); int[] m = la.GetSuggestedMoves(tm: random, tutor: random, reminder: random); if (m == null) return pkm.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.Skip(m.Length - count).ToArray(); 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 = null) { if (markings == null) markings = pk.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) markings[index] = (markings[index] + 1) % 3; // cycle pk.Markings = markings; break; } return 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 = null) { if (la == null) la = new LegalityAnalysis(pk); if (la.Parsed && la.EncounterOriginal is EncounterTrade t && t.HasNickname) pk.SetNickname(t.GetNickname(pk.Language)); else pk.SetNickname(); } 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]; } } }