diff --git a/PKHeX.Core/Editing/CommonEdits.cs b/PKHeX.Core/Editing/CommonEdits.cs index 5943c304a..6db31b384 100644 --- a/PKHeX.Core/Editing/CommonEdits.cs +++ b/PKHeX.Core/Editing/CommonEdits.cs @@ -469,46 +469,6 @@ namespace PKHeX.Core } } - /// - /// 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 . /// diff --git a/PKHeX.Core/Legality/Core.cs b/PKHeX.Core/Legality/Core.cs index aeb8308ea..bfda4b91f 100644 --- a/PKHeX.Core/Legality/Core.cs +++ b/PKHeX.Core/Legality/Core.cs @@ -914,5 +914,27 @@ namespace PKHeX.Core default: return gen >= 6 ? 12 : 10; } } + + public static bool GetIsFixedIVSequenceValid(IReadOnlyList IVs, PKM pkm, int max = 31) + { + for (int i = 0; i < 6; i++) + { + if ((uint) IVs[i] > max) // random + continue; + if (IVs[i] != pkm.GetIV(i)) + return false; + } + return true; + } + + public static bool GetIsFixedIVSequenceValidNoRand(IReadOnlyList IVs, PKM pkm) + { + for (int i = 0; i < 6; i++) + { + if (IVs[i] != pkm.GetIV(i)) + return false; + } + return true; + } } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade.cs index 2a7fde988..d002cb624 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade.cs @@ -328,10 +328,12 @@ namespace PKHeX.Core return false; if (TID != pkm.TID) return false; - if (Gender >= 0 && Gender != pkm.Gender && pkm.Format <= 2) - return false; - if (IVs?.SequenceEqual(pkm.IVs) == false && pkm.Format <= 2) - return false; + if (pkm.Format <= 2) + { + if (Gender >= 0 && Gender != pkm.Gender) + return false; + if (IVs != null && !Legal.GetIsFixedIVSequenceValidNoRand(IVs, pkm)) return false; + } if (pkm.Met_Location != 0 && pkm.Format == 2 && pkm.Met_Location != 126) return false; diff --git a/PKHeX.Core/Legality/RNG/MethodFinder.cs b/PKHeX.Core/Legality/RNG/MethodFinder.cs index c21f11ef6..9268c3cff 100644 --- a/PKHeX.Core/Legality/RNG/MethodFinder.cs +++ b/PKHeX.Core/Legality/RNG/MethodFinder.cs @@ -24,10 +24,9 @@ namespace PKHeX.Core var top = pid >> 16; var bot = pid & 0xFFFF; - var iIVs = pk.IVs; var IVs = new uint[6]; for (int i = 0; i < 6; i++) - IVs[i] = (uint)iIVs[i]; + IVs[i] = (uint)pk.GetIV(i); if (GetLCRNGMatch(top, bot, IVs, out PIDIV pidiv)) return pidiv; @@ -739,8 +738,9 @@ namespace PKHeX.Core return true; if (PIDType.Method_1 != val) return false; - var IVs = pkm.IVs; - return !(IVs[2] != 0 || IVs[3] != 0 || IVs[4] != 0 || IVs[5] != 0 || IVs[1] > 7); + + // only 8 bits are stored instead of 32 -- 5 bits HP, 3 bits for ATK. + return !(pkm.IV_DEF != 0 || pkm.IV_SPE != 0 || pkm.IV_SPA != 0 || pkm.IV_SPD != 0 || pkm.IV_ATK > 7); } public static bool IsCompatible4(this PIDType val, IEncounterable encounter, PKM pkm) diff --git a/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs index 6676e4310..fd3b28689 100644 --- a/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs @@ -19,29 +19,37 @@ namespace PKHeX.Core return; } var EncounterMatch = data.EncounterMatch; - var evs = pkm.EVs; int sum = pkm.EVTotal; if (sum > 0 && pkm.IsEgg) data.AddLine(GetInvalid(LEffortEgg)); - if (pkm.Format >= 3 && sum > 510) - data.AddLine(GetInvalid(LEffortAbove510)); - if (pkm.Format >= 6 && evs.Any(ev => ev > 252)) - data.AddLine(GetInvalid(LEffortAbove252)); - if (pkm.Format == 4 && pkm.Gen4 && EncounterMatch.LevelMin == 100) - { - // Cannot EV train at level 100 -- Certain events are distributed at level 100. - if (evs.Any(ev => ev > 100)) // EVs can only be increased by vitamins to a max of 100. - data.AddLine(GetInvalid(LEffortCap100)); - } - else if (pkm.Format < 5) - { - // In Generations I and II, when a Pokémon is taken out of the Day Care, its experience will lower to the minimum value for its current level. - if (pkm.Format < 3) // can abuse daycare for EV training without EXP gain - return; - const int maxEV = 100; // Vitamin Max - if (Experience.GetEXP(EncounterMatch.LevelMin, pkm.Species, pkm.AltForm) == pkm.EXP && evs.Any(ev => ev > maxEV)) - data.AddLine(GetInvalid(string.Format(LEffortUntrainedCap, maxEV))); + // In Generations I and II, when a Pokémon is taken out of the Day Care, its experience will lower to the minimum value for its current level. + int format = pkm.Format; + if (format < 3) // can abuse daycare for EV training without EXP gain + return; + + if (sum > 510) // format >= 3 + data.AddLine(GetInvalid(LEffortAbove510)); + var evs = pkm.EVs; + if (format >= 6 && evs.Any(ev => ev > 252)) + data.AddLine(GetInvalid(LEffortAbove252)); + + const int vitaMax = 100; // Vitamin Max + if (format < 5) // 3/4 + { + if (EncounterMatch.LevelMin == 100) // only true for Gen4 and Format=4 + { + // Cannot EV train at level 100 -- Certain events are distributed at level 100. + if (evs.Any(ev => ev > vitaMax)) // EVs can only be increased by vitamins to a max of 100. + data.AddLine(GetInvalid(LEffortCap100)); + } + else // check for gained EVs without gaining EXP -- don't check gen5+ which have wings to boost above 100. + { + var growth = PersonalTable.HGSS[EncounterMatch.Species].EXPGrowth; + var baseEXP = Experience.GetEXP(EncounterMatch.LevelMin, growth); + if (baseEXP == pkm.EXP && evs.Any(ev => ev > vitaMax)) + data.AddLine(GetInvalid(string.Format(LEffortUntrainedCap, vitaMax))); + } } // Only one of the following can be true: 0, 508, and x%6!=0 diff --git a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs index 8fa370200..3ac02e8b9 100644 --- a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core @@ -56,7 +55,7 @@ namespace PKHeX.Core var ivflag = Array.Find(IVs, iv => (byte)(iv - 0xFC) < 3); if (ivflag == 0) // Random IVs { - bool valid = GetIsFixedIVSequenceValid(IVs, data.pkm.IVs); + bool valid = Legal.GetIsFixedIVSequenceValid(IVs, data.pkm); if (!valid) data.AddLine(GetInvalid(LEncGiftIVMismatch)); } @@ -67,16 +66,6 @@ namespace PKHeX.Core } } - private static bool GetIsFixedIVSequenceValid(IReadOnlyList IVs, IReadOnlyList pkIVs) - { - for (int i = 0; i < 6; i++) - { - if (IVs[i] <= 31 && IVs[i] != pkIVs[i]) - return false; - } - return true; - } - private void VerifyIVsSlot(LegalityAnalysis data, EncounterSlot w) { switch (w.Generation) @@ -111,7 +100,7 @@ namespace PKHeX.Core private void VerifyIVsFlawless(LegalityAnalysis data, int count) { - if (data.pkm.IVs.Count(iv => iv == 31) < count) + if (data.pkm.FlawlessIVCount < count) data.AddLine(GetInvalid(string.Format(LIVF_COUNT0_31, count))); } diff --git a/PKHeX.Core/PKM/PK5.cs b/PKHeX.Core/PKM/PK5.cs index 3283e52e0..3dee74ded 100644 --- a/PKHeX.Core/PKM/PK5.cs +++ b/PKHeX.Core/PKM/PK5.cs @@ -271,16 +271,14 @@ namespace PKHeX.Core { get { - // Characteristic with PID%6 - int pm6 = (int)(PID % 6); // PID MOD 6 - int maxIV = IVs.Max(); + int pm6 = (int)(PID % 6); // PID + int maxIV = MaximumIV; int pm6stat = 0; - for (int i = 0; i < 6; i++) { pm6stat = (pm6 + i) % 6; - if (IVs[pm6stat] == maxIV) - break; // P%6 is this stat + if (GetIV(pm6stat) == maxIV) + break; } return (pm6stat * 5) + (maxIV % 5); } @@ -436,10 +434,8 @@ namespace PKHeX.Core } // Battle Ribbon Counter - // Winning Ribbon - if ((Data[0x3E] & 0x20) >> 5 == 1) battleribbons++; - // Victory Ribbon - if ((Data[0x3E] & 0x40) >> 6 == 1) battleribbons++; + if (RibbonWinning) battleribbons++; + if (RibbonVictory) battleribbons++; for (int i = 1; i < 7; i++) // Sinnoh Battle Ribbons if (((Data[0x24] >> i) & 1) == 1) battleribbons++; @@ -448,57 +444,42 @@ namespace PKHeX.Core pk6.RibbonCountMemoryBattle = battleribbons; // Copy Ribbons to their new locations. - int bx30 = 0; - // bx30 |= 0; // Kalos Champ - New Kalos Ribbon - bx30 |= ((Data[0x3E] & 0x10) >> 4) << 1; // Hoenn Champion - bx30 |= ((Data[0x24] & 0x01) >> 0) << 2; // Sinnoh Champ - // bx30 |= 0; // Best Friend - New Kalos Ribbon - // bx30 |= 0; // Training - New Kalos Ribbon - // bx30 |= 0; // Skillful - New Kalos Ribbon - // bx30 |= 0; // Expert - New Kalos Ribbon - bx30 |= ((Data[0x3F] & 0x01) >> 0) << 7; // Effort Ribbon - pk6.Data[0x30] = (byte)bx30; + pk6.RibbonChampionG3Hoenn = RibbonChampionG3Hoenn; + pk6.RibbonChampionSinnoh = RibbonChampionSinnoh; + pk6.RibbonEffort = RibbonEffort; - int bx31 = 0; - bx31 |= ((Data[0x24] & 0x80) >> 7) << 0; // Alert - bx31 |= ((Data[0x25] & 0x01) >> 0) << 1; // Shock - bx31 |= ((Data[0x25] & 0x02) >> 1) << 2; // Downcast - bx31 |= ((Data[0x25] & 0x04) >> 2) << 3; // Careless - bx31 |= ((Data[0x25] & 0x08) >> 3) << 4; // Relax - bx31 |= ((Data[0x25] & 0x10) >> 4) << 5; // Snooze - bx31 |= ((Data[0x25] & 0x20) >> 5) << 6; // Smile - bx31 |= ((Data[0x25] & 0x40) >> 6) << 7; // Gorgeous - pk6.Data[0x31] = (byte)bx31; + pk6.RibbonAlert = RibbonAlert; + pk6.RibbonShock = RibbonShock; + pk6.RibbonDowncast = RibbonDowncast; + pk6.RibbonCareless = RibbonCareless; + pk6.RibbonRelax = RibbonRelax; + pk6.RibbonSnooze = RibbonSnooze; + pk6.RibbonSmile = RibbonSmile; + pk6.RibbonGorgeous = RibbonGorgeous; - int bx32 = 0; - bx32 |= ((Data[0x25] & 0x80) >> 7) << 0; // Royal - bx32 |= ((Data[0x26] & 0x01) >> 0) << 1; // Gorgeous Royal - bx32 |= ((Data[0x3E] & 0x80) >> 7) << 2; // Artist - bx32 |= ((Data[0x26] & 0x02) >> 1) << 3; // Footprint - bx32 |= ((Data[0x26] & 0x04) >> 2) << 4; // Record - bx32 |= ((Data[0x26] & 0x10) >> 4) << 5; // Legend - bx32 |= ((Data[0x3F] & 0x10) >> 4) << 6; // Country - bx32 |= ((Data[0x3F] & 0x20) >> 5) << 7; // National - pk6.Data[0x32] = (byte)bx32; + pk6.RibbonRoyal = RibbonRoyal; + pk6.RibbonGorgeousRoyal = RibbonGorgeousRoyal; + pk6.RibbonArtist = RibbonArtist; + pk6.RibbonFootprint = RibbonFootprint; + pk6.RibbonRecord = RibbonRecord; + pk6.RibbonLegend = RibbonLegend; + pk6.RibbonCountry = RibbonCountry; + pk6.RibbonNational = RibbonNational; - int bx33 = 0; - bx33 |= ((Data[0x3F] & 0x40) >> 6) << 0; // Earth - bx33 |= ((Data[0x3F] & 0x80) >> 7) << 1; // World - bx33 |= ((Data[0x27] & 0x04) >> 2) << 2; // Classic - bx33 |= ((Data[0x27] & 0x08) >> 3) << 3; // Premier - bx33 |= ((Data[0x26] & 0x08) >> 3) << 4; // Event - bx33 |= ((Data[0x26] & 0x40) >> 6) << 5; // Birthday - bx33 |= ((Data[0x26] & 0x80) >> 7) << 6; // Special - bx33 |= ((Data[0x27] & 0x01) >> 0) << 7; // Souvenir - pk6.Data[0x33] = (byte)bx33; + pk6.RibbonEarth = RibbonEarth; + pk6.RibbonWorld = RibbonWorld; + pk6.RibbonClassic = RibbonClassic; + pk6.RibbonPremier = RibbonPremier; + pk6.RibbonEvent = RibbonEvent; + pk6.RibbonBirthday = RibbonBirthday; + pk6.RibbonSpecial = RibbonSpecial; + pk6.RibbonSouvenir = RibbonSouvenir; - int bx34 = 0; - bx34 |= ((Data[0x27] & 0x02) >> 1) << 0; // Wishing Ribbon - bx34 |= ((Data[0x3F] & 0x02) >> 1) << 1; // Battle Champion - bx34 |= ((Data[0x3F] & 0x04) >> 2) << 2; // Regional Champion - bx34 |= ((Data[0x3F] & 0x08) >> 3) << 3; // National Champion - bx34 |= ((Data[0x26] & 0x20) >> 5) << 4; // World Champion - pk6.Data[0x34] = (byte)bx34; + pk6.RibbonWishing = RibbonWishing; + pk6.RibbonChampionBattle = RibbonChampionBattle; + pk6.RibbonChampionRegional = RibbonChampionRegional; + pk6.RibbonChampionNational = RibbonChampionNational; + pk6.RibbonChampionWorld = RibbonChampionWorld; // Write Transfer Location - location is dependent on 3DS system that transfers. pk6.Country = PKMConverter.Country; diff --git a/PKHeX.Core/PKM/PKM.cs b/PKHeX.Core/PKM/PKM.cs index 1a0f21993..ddc0e57c1 100644 --- a/PKHeX.Core/PKM/PKM.cs +++ b/PKHeX.Core/PKM/PKM.cs @@ -342,6 +342,23 @@ namespace PKHeX.Core public int MarkDiamond { get => Markings[5]; set { var marks = Markings; marks[5] = value; Markings = marks; } } public int IVTotal => IV_HP + IV_ATK + IV_DEF + IV_SPA + IV_SPD + IV_SPE; public int EVTotal => EV_HP + EV_ATK + EV_DEF + EV_SPA + EV_SPD + EV_SPE; + public int MaximumIV => Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(IV_HP, IV_ATK), IV_DEF), IV_SPA), IV_SPD), IV_SPE); + + public int FlawlessIVCount + { + get + { + int max = MaxIV; + int ctr = 0; + if (IV_HP == max) ++ctr; + if (IV_ATK == max) ++ctr; + if (IV_DEF == max) ++ctr; + if (IV_SPA == max) ++ctr; + if (IV_SPD == max) ++ctr; + if (IV_SPE == max) ++ctr; + return ctr; + } + } public string FileName => $"{FileNameWithoutExtension}.{Extension}"; @@ -1002,5 +1019,43 @@ namespace PKHeX.Core Moves = moves; FixMoves(); } + + /// + /// Gets one of the based on its index within the array. + /// + /// Index to get + public int GetEV(int index) + { + switch (index) + { + case 0: return EV_HP; + case 1: return EV_ATK; + case 2: return EV_DEF; + case 3: return EV_SPE; + case 4: return EV_SPA; + case 5: return EV_SPD; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + /// + /// Gets one of the based on its index within the array. + /// + /// Index to get + public int GetIV(int index) + { + switch (index) + { + case 0: return IV_HP; + case 1: return IV_ATK; + case 2: return IV_DEF; + case 3: return IV_SPE; + case 4: return IV_SPA; + case 5: return IV_SPD; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } } } diff --git a/PKHeX.Core/PKM/Shared/_K4.cs b/PKHeX.Core/PKM/Shared/_K4.cs index d68c5b16a..218c0a589 100644 --- a/PKHeX.Core/PKM/Shared/_K4.cs +++ b/PKHeX.Core/PKM/Shared/_K4.cs @@ -1,6 +1,4 @@ -using System.Linq; - -namespace PKHeX.Core +namespace PKHeX.Core { public abstract class _K4 : PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetUnique3, IRibbonSetUnique4, IRibbonSetCommon3, IRibbonSetCommon4, IContestStats { @@ -23,16 +21,14 @@ namespace PKHeX.Core { get { - // Characteristic with PID%6 - int pm6 = (int)(PID % 6); // PID MOD 6 - int maxIV = IVs.Max(); + int pm6 = (int)(EncryptionConstant % 6); // PID + int maxIV = MaximumIV; int pm6stat = 0; - for (int i = 0; i < 6; i++) { pm6stat = (pm6 + i) % 6; - if (IVs[pm6stat] == maxIV) - break; // P%6 is this stat + if (GetIV(pm6stat) == maxIV) + break; } return (pm6stat * 5) + (maxIV % 5); } diff --git a/PKHeX.Core/PKM/Shared/_K6.cs b/PKHeX.Core/PKM/Shared/_K6.cs index e867611ec..aa0a4fba1 100644 --- a/PKHeX.Core/PKM/Shared/_K6.cs +++ b/PKHeX.Core/PKM/Shared/_K6.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace PKHeX.Core { @@ -67,14 +66,13 @@ namespace PKHeX.Core { get { - // Characteristic with EC%6 - int pm6 = (int)(EncryptionConstant % 6); // EC MOD 6 - int maxIV = IVs.Max(); + int pm6 = (int)(EncryptionConstant % 6); + int maxIV = MaximumIV; int pm6stat = 0; - for (int i = 0; i < 6; i++) { - if (IVs[pm6stat = pm6++ % 6] == maxIV) + pm6stat = (pm6 + i) % 6; + if (GetIV(pm6stat) == maxIV) break; } return (pm6stat * 5) + (maxIV % 5); diff --git a/PKHeX.Core/PKM/Util/Experience.cs b/PKHeX.Core/PKM/Util/Experience.cs index d91cb246a..f77a58543 100644 --- a/PKHeX.Core/PKM/Util/Experience.cs +++ b/PKHeX.Core/PKM/Util/Experience.cs @@ -30,13 +30,25 @@ /// National Dex number of the Pokémon. /// AltForm ID (starters in Let's Go) /// Experience points needed to have specified level. - public static uint GetEXP(int level, int species, int forme = 0) + public static uint GetEXP(int level, int species, int forme) + { + var growth = PKX.Personal.GetFormeEntry(species, forme).EXPGrowth; + return GetEXP(level, growth); + } + + /// + /// Gets the minimum Experience points for the specified level. + /// + /// Current level + /// Growth Rate type + /// Experience points needed to have specified level. + public static uint GetEXP(int level, int growth) { if (level <= 1) return 0; if (level > 100) level = 100; - return ExpTable[level - 1, PKX.Personal.GetFormeEntry(species, forme).EXPGrowth]; + return ExpTable[level - 1, growth]; } ///