using System; 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; } extension(PKM pk) { /// /// Sets the to the provided value. /// /// to set. If no nickname is provided, the is set to the default value for its current language and format. public void SetNickname(string nick) { if (nick.Length == 0) { pk.ClearNickname(); return; } pk.PrepareNickname(); pk.Nickname = nick; pk.IsNicknamed = true; } /// /// Sets the to the default value of the current species and language. /// /// Default nickname for the current species and language. public string ClearNickname() { pk.IsNicknamed = false; string nick = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format); pk.SetString(pk.NicknameTrash, nick, nick.Length, StringConverterOption.None); if (pk is GBPKM pk12) pk12.SetNotNicknamed(); return nick; } /// /// Sets the value by sanity checking the provided against the possible pool of abilities. /// /// Desired value to set. public void SetAbility(int abilityID) { if (abilityID < 0) return; var index = pk.PersonalInfo.GetIndexOfAbility(abilityID); if (index < 0) return; // leave original value pk.SetAbilityIndex(index); } /// /// Sets the value based on the provided ability index (0-2) /// /// Desired (shifted by 1) to set. public void SetAbilityIndex(int index) { if (pk is PK5 pk5 && index == 2) pk5.HiddenAbility = true; else if (pk.Format <= 5) pk.PID = EntityPID.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. public void SetRandomEC() { var gen = pk.Generation; if (gen is 3 or 4 or 5) { pk.EncryptionConstant = pk.PID; return; } pk.EncryptionConstant = GetComplicatedEC(pk); } /// /// Sets the derived value. /// /// Desired state to set. public bool SetIsShiny(bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny(); /// /// Makes a shiny. /// /// Shiny type to force. Only use Always* or Random /// Returns true if the data was modified. public bool SetShiny(Shiny type = Shiny.Random) { if (pk.IsShiny && type.IsValid(pk)) return false; if (type == Shiny.Random || pk.FatefulEncounter || pk.Version == GameVersion.GO || pk.Format <= 2) { pk.SetShiny(); return true; } do { pk.SetShiny(); } while (!type.IsValid(pk)); return true; } /// /// Makes a not-shiny. /// /// Returns true if the data was modified. public bool SetUnshiny() { if (!pk.IsShiny) return false; pk.SetPIDGender(pk.Gender); return true; } /// /// Sets the value, with special consideration for the values which derive the value. /// /// Desired value to set. public void SetNature(Nature nature) { if (!nature.IsFixed) nature = 0; // default valid var format = pk.Format; if (format >= 8) pk.StatNature = nature; else if (format is 3 or 4) pk.SetPIDNature(nature); else pk.Nature = nature; } /// /// Copies details to the . /// /// details to copy from. public void ApplySetDetails(IBattleTemplate set) { pk.Species = Math.Min(pk.MaxSpeciesID, set.Species); pk.Form = set.Form; ReadOnlySpan moves = set.Moves; if (moves[0] != 0) pk.SetMoves(moves, true); if (Legal.IsPPUpAvailable(pk)) pk.SetMaximumPPUps(moves); pk.ApplyHeldItem(set.HeldItem, set.Context); pk.CurrentLevel = set.Level; pk.CurrentFriendship = set.Friendship; ReadOnlySpan ivs = set.IVs; ReadOnlySpan evs = set.EVs; pk.SetIVs(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 (moves.Contains((ushort)Move.HiddenPower) && gb.HPType != set.HiddenPowerType) { if (ivs.ContainsAny(30, 31)) gb.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 (!evs.ContainsAnyExcept(0)) gb.MaxEVs(); else if (evs.ContainsAnyExceptInRange(0, 252)) // Any specified above 252 gb.SetEVs(evs); else gb.SetSqrtEVs(evs); } else { pk.SetEVs(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 Pokémon met in gen 8 if (pk.Generation < 8) pk.SetSuggestedHyperTrainingData(ivs); if (ShowdownSetIVMarkings) pk.SetMarkings(); pk.SetNickname(set.Nickname); pk.SetSaneGender(set.Gender); 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 IGanbaru g) g.SetSuggestedGanbaruValues(pk); if (pk is IGigantamax c) c.CanGigantamax = set.CanGigantamax; if (pk is IDynamaxLevel d) d.DynamaxLevel = d.GetSuggestedDynamaxLevel(pk, requested: set.DynamaxLevel); if (pk is ITeraType tera) { var type = set.TeraType == MoveType.Any ? (MoveType)pk.PersonalInfo.Type1 : set.TeraType; tera.SetTeraType(type); } if (pk is IMoveShop8Mastery s) s.SetMoveShopFlags(set.Moves, pk); if (ShowdownSetBehaviorNature && pk.Format >= 8) pk.Nature = pk.StatNature; var legal = new LegalityAnalysis(pk); if (pk is ITechRecord t) { t.ClearRecordFlags(); t.SetRecordFlags(set.Moves, legal.Info.EvoChainsAllGens.Get(pk.Context)); } if (pk is IPlusRecord plus && pk.PersonalInfo is IPermitPlus permit) { plus.ClearPlusFlags(permit.PlusCountTotal); plus.SetPlusFlags(permit, legal, true, true); } if (legal.Parsed && !MoveResult.AllValid(legal.Info.Relearn)) pk.SetRelearnMoves(legal); pk.ResetPartyStats(); pk.RefreshChecksum(); } /// /// Sets the value depending on the current format and the provided item index & format. /// /// Held Item to apply /// Format required for importing public void ApplyHeldItem(int item, EntityContext context) { item = ItemConverter.GetItemForFormat(item, context, pk.Context); pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item; } /// /// Sets one of the based on its index within the array. /// /// Index to set to /// Value to set public int SetEV(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. /// /// Index to set to /// Value to set public int SetIV(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. /// /// Index to fetch for /// Highest value the value can be. public int GetMaximumEV(int index) { if (pk.Format < 3) return EffortValues.Max12; var sum = pk.EVTotal - pk.GetEV(index); int remaining = EffortValues.Max510 - sum; return Math.Clamp(remaining, 0, EffortValues.Max252); } /// /// Fetches the highest value the provided . /// /// 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 int GetMaximumIV(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. /// /// Trainer to force hatch with if Version is not currently set. /// Re-hatch already hatched inputs public void ForceHatchPKM(ITrainerInfo? tr = null, bool reHatch = false) { if (!pk.IsEgg && !reHatch) return; pk.IsEgg = false; pk.ClearNickname(); pk.OriginalTrainerFriendship = Math.Min(pk.OriginalTrainerFriendship, EggStateLegality.GetEggHatchFriendship(pk.Context)); if (pk.IsTradedEgg) pk.EggLocation = pk.MetLocation; if (pk.Version == 0) pk.Version = EggStateLegality.GetEggHatchVersion(pk, tr?.Version ?? RecentTrainerCache.Version); var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pk); if (loc != EncounterSuggestion.LocationNone) pk.MetLocation = loc; if (pk.Format >= 4) pk.MetDate = EncounterDate.GetDate(pk.Context.Console); if (pk.Gen6) pk.SetHatchMemory6(); } /// /// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game. /// /// Game the egg originated from /// Game the egg is currently present on public void SetEggMetData(GameVersion origin, GameVersion dest) { if (pk.Format < 4) return; var console = pk.Context.Console; var date = EncounterDate.GetDate(console); var today = pk.MetDate = date; bool traded = origin != dest; pk.EggLocation = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(pk.Generation, origin, traded); pk.EggMetDate = today; } /// /// Maximizes the . If the , the hatch counter is set to 1. /// public void MaximizeFriendship() { if (pk.IsEgg) pk.OriginalTrainerFriendship = 1; else pk.CurrentFriendship = byte.MaxValue; if (pk is ICombatPower pb) pb.ResetCP(); } /// /// Maximizes the . If the , the is ignored. /// public void MaximizeLevel() { if (pk.IsEgg) return; pk.CurrentLevel = Experience.MaxLevel; if (pk is ICombatPower pb) pb.ResetCP(); } /// /// Sets the to its default value. /// /// Precomputed optional public void SetDefaultNickname(LegalityAnalysis la) { if (la is { Parsed: true, EncounterOriginal: IFixedNickname {IsFixedNickname: true} t }) pk.SetNickname(t.GetNickname(pk.Language)); else pk.ClearNickname(); } /// /// Sets the to its default value. /// public void SetDefaultNickname() => pk.SetDefaultNickname(new LegalityAnalysis(pk)); /// /// Gets the Location Name for the /// /// Location requested is the egg obtained location, not met location. /// Location string public string GetLocationString(bool eggmet) { if (pk.Format < 2) return string.Empty; ushort location = eggmet ? pk.EggLocation : pk.MetLocation; return GameInfo.GetLocationName(eggmet, location, pk.Format, pk.Generation, pk.Version); } } // Extensions public const char OptionNone = '\0'; /// /// Gets a to match the requested option. /// public static uint GetComplicatedEC(ISpeciesForm pk, char option = OptionNone) { var species = pk.Species; var form = pk.Form; return GetComplicatedEC(species, form, option); } /// public static uint GetComplicatedEC(ushort species, byte form, char option = OptionNone) { var rng = Util.Rand; uint rand = rng.Rand32(); uint mod, noise; if (species is >= (int)Species.Wurmple and <= (int)Species.Dustox) { mod = 10; bool lower = option is '0' or 'B' or 'S' || WurmpleUtil.GetWurmpleEvoGroup(species) == 0; noise = (lower ? 0u : 5u) + (uint)rng.Next(0, 5); } else if (species is (int)Species.Dunsparce or (int)Species.Dudunsparce or (int)Species.Tandemaus or (int)Species.Maushold) { mod = 100; noise = species switch { // Retain requisite correlation to allow for evolving into this species too. (int)Species.Dudunsparce => form == 1 ? 0 : (uint)rng.Next(1, 100), // 3 Segment (int)Species.Maushold => form == 0 ? 0 : (uint)rng.Next(1, 100), // Family of 3 // Otherwise, check if one is preferred, and if not, just make it the more common outcome. _ => option switch { '0' or '3' => 0u, _ => (uint)rng.Next(1, 100), }, }; } else if (option is >= '0' and <= '5') { mod = 6; noise = (uint)(option - '0'); } else { return rand; } return unchecked(rand - (rand % mod) + noise); } }