From f632aedd15104040a780d5138aca9830e7a45fb1 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 12 Aug 2023 16:01:16 -0700 Subject: [PATCH] Encounter Templates: Searching and Creating (#3955) We implement simple state machine iterators to iterate through every split type encounter array, and more finely control the path we iterate through. And, by using generics, we can have the compiler generate optimized code to avoid virtual calls. In addition to this, we shift away from the big-5 encounter types and not inherit from an abstract class. This allows for creating a PK* of a specific type and directly writing properties (no virtual calls). Plus we can now fine-tune each encounter type to call specific code, and not have to worry about future game encounter types bothering the generation routines. --- .../Applicators/CatchRateApplicator.cs | 4 +- .../Editing/Applicators/MoveApplicator.cs | 2 - .../Editing/Applicators/MoveSetApplicator.cs | 1 - PKHeX.Core/Editing/Bulk/BatchEditing.cs | 4 +- .../Bulk/Suggestion/BatchModifications.cs | 1 - PKHeX.Core/Editing/CommonEdits.cs | 2 +- .../Editing/Database/TrainerDatabase.cs | 4 +- .../Editing/PKM/EntitySuggestionUtil.cs | 20 +- .../Editors/EventUnlock/EventUnlocker8b.cs | 1 - PKHeX.Core/Items/ItemStorage2.cs | 22 +- PKHeX.Core/Legality/Areas/EncounterArea.cs | 22 - PKHeX.Core/Legality/Areas/EncounterArea1.cs | 72 ---- PKHeX.Core/Legality/Areas/EncounterArea2.cs | 128 ------ PKHeX.Core/Legality/Areas/EncounterArea3XD.cs | 74 ---- PKHeX.Core/Legality/Areas/EncounterArea4.cs | 167 -------- PKHeX.Core/Legality/Areas/EncounterArea6AO.cs | 99 ----- PKHeX.Core/Legality/Areas/EncounterArea7g.cs | 112 ----- PKHeX.Core/Legality/Areas/EncounterArea8g.cs | 131 ------ PKHeX.Core/Legality/Areas/EncounterArea9.cs | 89 ---- PKHeX.Core/Legality/Bulk/CombinedReference.cs | 3 + .../Encounters/Data/EncounterEvent.cs | 184 ++++---- .../Legality/Encounters/Data/EncounterUtil.cs | 75 +--- .../Encounters/Data/Gen1/Encounters1.cs | 132 ++++++ .../Encounters/Data/Gen1/Encounters1GBEra.cs | 38 ++ .../Encounters/Data/Gen1/Encounters1VC.cs | 13 + .../Encounters/Data/Gen12/Encounters1.cs | 162 ------- .../Encounters/Data/Gen12/Encounters2.cs | 382 ----------------- .../Encounters/Data/Gen2/Encounters2.cs | 125 ++++++ .../Encounters/Data/Gen2/Encounters2GBEra.cs | 253 +++++++++++ .../Encounters/Data/Gen3/Encounters3Colo.cs | 184 ++++---- .../Encounters/Data/Gen3/Encounters3FRLG.cs | 112 ++--- .../Encounters/Data/Gen3/Encounters3RSE.cs | 119 ++++-- .../Encounters/Data/Gen3/Encounters3XD.cs | 209 ++++----- .../Encounters/Data/Gen3/EncountersWC3.cs | 57 +-- .../Encounters/Data/Gen4/Encounters4DPPt.cs | 148 +++++++ .../Encounters/Data/Gen4/Encounters4HGSS.cs | 147 +++++++ .../Encounters/Data/Gen45/Encounters4DPPt.cs | 139 ------ .../Encounters/Data/Gen45/Encounters4HGSS.cs | 144 ------- .../Data/{Gen45 => Gen5}/Encounters5B2W2.cs | 167 ++++---- .../Data/{Gen45 => Gen5}/Encounters5BW.cs | 87 ++-- .../Data/{Gen45 => Gen5}/Encounters5DR.cs | 4 +- .../Data/{Gen67 => Gen6}/Encounters6AO.cs | 121 +++--- .../Encounters/Data/Gen6/Encounters6XY.cs | 106 +++++ .../Encounters/Data/Gen67/Encounters6XY.cs | 102 ----- .../Data/{Gen67 => Gen7}/Encounters7GG.cs | 70 +-- .../Data/{Gen67 => Gen7}/Encounters7SM.cs | 110 ++--- .../Data/{Gen67 => Gen7}/Encounters7USUM.cs | 185 ++++---- .../Encounters/Data/Gen8/Encounters8.cs | 259 ++++++----- .../Encounters/Data/Gen8/Encounters8Nest.cs | 26 +- .../Encounters/Data/Gen8/Encounters8a.cs | 26 +- .../Encounters/Data/Gen8/Encounters8b.cs | 81 ++-- .../Encounters/Data/{ => Gen9}/Encounters9.cs | 83 ++-- .../Encounters/EncounterMatchRating.cs | 22 - .../Encounters/EncounterSlot/EncounterSlot.cs | 247 ----------- .../EncounterSlot/EncounterSlot1.cs | 39 -- .../EncounterSlot/EncounterSlot2.cs | 93 ---- .../EncounterSlot/EncounterSlot3.cs | 40 -- .../EncounterSlot/EncounterSlot3PokeSpot.cs | 33 -- .../EncounterSlot/EncounterSlot3Swarm.cs | 22 - .../EncounterSlot/EncounterSlot4.cs | 71 --- .../EncounterSlot/EncounterSlot5.cs | 19 - .../EncounterSlot/EncounterSlot6AO.cs | 65 --- .../EncounterSlot/EncounterSlot6XY.cs | 35 -- .../EncounterSlot/EncounterSlot7.cs | 36 -- .../EncounterSlot/EncounterSlot7b.cs | 25 -- .../EncounterSlot/EncounterSlot8.cs | 138 ------ .../EncounterSlot/EncounterSlot8b.cs | 94 ---- .../EncounterSlot/EncounterSlot9.cs | 98 ----- .../EncounterSlot/GO/EncounterSlot7GO.cs | 58 --- .../EncounterSlot/GO/EncounterSlotGO.cs | 151 ------- .../Encounters/EncounterSlot/GO/IPogoSlot.cs | 23 - .../Encounters/EncounterSlot/INumberedSlot.cs | 15 - .../EncounterStatic/DreamWorldEntry.cs | 64 --- .../EncounterStatic/EncounterFixed9.cs | 123 ------ .../EncounterStatic/EncounterStatic.cs | 328 -------------- .../EncounterStatic/EncounterStatic1.cs | 93 ---- .../EncounterStatic/EncounterStatic1E.cs | 120 ------ .../EncounterStatic/EncounterStatic2.cs | 144 ------- .../EncounterStatic/EncounterStatic2E.cs | 101 ----- .../EncounterStatic/EncounterStatic3.cs | 69 --- .../EncounterStatic/EncounterStatic4.cs | 150 ------- .../EncounterStatic4Pokewalker.cs | 115 ----- .../EncounterStatic/EncounterStatic5.cs | 59 --- .../EncounterStatic/EncounterStatic5DR.cs | 29 -- .../EncounterStatic/EncounterStatic5N.cs | 54 --- .../EncounterStatic/EncounterStatic6.cs | 57 --- .../EncounterStatic/EncounterStatic7.cs | 101 ----- .../EncounterStatic/EncounterStatic7b.cs | 21 - .../EncounterStatic/EncounterStatic8.cs | 136 ------ .../EncounterStatic/EncounterStatic8Nest.cs | 120 ------ .../EncounterStatic/EncounterStatic8a.cs | 244 ----------- .../EncounterStatic/EncounterStatic8b.cs | 145 ------- .../EncounterStatic/EncounterStatic9.cs | 115 ----- .../EncounterStatic/EncounterStaticShadow.cs | 117 ----- .../EncounterTrade/EncounterTrade.cs | 277 ------------ .../EncounterTrade/EncounterTrade1.cs | 138 ------ .../EncounterTrade/EncounterTrade2.cs | 99 ----- .../EncounterTrade/EncounterTrade3.cs | 94 ---- .../EncounterTrade/EncounterTrade4.cs | 169 -------- .../EncounterTrade/EncounterTrade5.cs | 64 --- .../EncounterTrade/EncounterTrade6.cs | 24 -- .../EncounterTrade/EncounterTrade7.cs | 26 -- .../EncounterTrade/EncounterTrade7b.cs | 25 -- .../EncounterTrade/EncounterTrade8.cs | 56 --- .../EncounterTrade/EncounterTrade8b.cs | 95 ---- .../EncounterTrade/EncounterTrade9.cs | 72 ---- .../EncounterTrade/EncounterTradeGB.cs | 13 - .../ByGeneration/EncounterGenerator1.cs | 192 +-------- .../ByGeneration/EncounterGenerator2.cs | 293 ++----------- .../ByGeneration/EncounterGenerator3.cs | 395 ++++------------- .../ByGeneration/EncounterGenerator3GC.cs | 157 +------ .../ByGeneration/EncounterGenerator4.cs | 394 ++++------------- .../ByGeneration/EncounterGenerator5.cs | 337 +++------------ .../ByGeneration/EncounterGenerator6.cs | 403 +++-------------- .../ByGeneration/EncounterGenerator7.cs | 312 ++------------ .../ByGeneration/EncounterGenerator7GG.cs | 208 +-------- .../ByGeneration/EncounterGenerator7GO.cs | 74 +--- .../ByGeneration/EncounterGenerator8.cs | 320 +++----------- .../ByGeneration/EncounterGenerator8GO.cs | 77 +--- .../ByGeneration/EncounterGenerator8a.cs | 169 +------- .../ByGeneration/EncounterGenerator8b.cs | 404 ++---------------- .../ByGeneration/EncounterGenerator9.cs | 399 ++--------------- .../Encounters/Generator/EncounterCriteria.cs | 71 ++- .../Encounters/Generator/EncounterFinder.cs | 4 +- .../Generator/Possible/EncounterPossible1.cs | 216 ++++++++++ .../Generator/Possible/EncounterPossible2.cs | 226 ++++++++++ .../Generator/Possible/EncounterPossible3.cs | 279 ++++++++++++ .../Possible/EncounterPossible3GC.cs | 144 +++++++ .../Generator/Possible/EncounterPossible4.cs | 271 ++++++++++++ .../Generator/Possible/EncounterPossible5.cs | 290 +++++++++++++ .../Generator/Possible/EncounterPossible6.cs | 249 +++++++++++ .../Generator/Possible/EncounterPossible7.cs | 248 +++++++++++ .../Possible/EncounterPossible7GG.cs | 192 +++++++++ .../Possible/EncounterPossible7GO.cs | 86 ++++ .../Generator/Possible/EncounterPossible8.cs | 235 ++++++++++ .../Possible/EncounterPossible8GO.cs | 86 ++++ .../Generator/Possible/EncounterPossible8a.cs | 125 ++++++ .../Generator/Possible/EncounterPossible8b.cs | 180 ++++++++ .../Generator/Possible/EncounterPossible9.cs | 193 +++++++++ .../Dirtied/EncounterEnumerator8bSWSH.cs | 245 +++++++++++ .../Dirtied/EncounterEnumerator9SWSH.cs | 243 +++++++++++ .../Generator/Search/EncounterEnumerator1.cs | 214 ++++++++++ .../Generator/Search/EncounterEnumerator2.cs | 291 +++++++++++++ .../Generator/Search/EncounterEnumerator3.cs | 314 ++++++++++++++ .../Search/EncounterEnumerator3GC.cs | 180 ++++++++ .../Generator/Search/EncounterEnumerator4.cs | 321 ++++++++++++++ .../Generator/Search/EncounterEnumerator5.cs | 334 +++++++++++++++ .../Generator/Search/EncounterEnumerator6.cs | 290 +++++++++++++ .../Generator/Search/EncounterEnumerator7.cs | 289 +++++++++++++ .../Search/EncounterEnumerator7GG.cs | 222 ++++++++++ .../Search/EncounterEnumerator7GO.cs | 116 +++++ .../Generator/Search/EncounterEnumerator8.cs | 291 +++++++++++++ .../Search/EncounterEnumerator8GO.cs | 117 +++++ .../Generator/Search/EncounterEnumerator8a.cs | 170 ++++++++ .../Generator/Search/EncounterEnumerator8b.cs | 242 +++++++++++ .../Generator/Search/EncounterEnumerator9.cs | 240 +++++++++++ .../Encounters/Information/EncounterDate.cs | 76 ++++ .../Encounters/Information/EncounterLearn.cs | 6 - .../Information/EncounterSuggestionData.cs | 4 +- .../Enums}/AbilityPermission.cs | 16 + .../Templates/Enums/EncounterMatchRating.cs | 80 ++++ .../Enums/HiddenAbilityPermission.cs | 8 + .../Templates/Enums}/Shiny.cs | 9 +- .../Templates}/Enums/SlotType.cs | 2 +- .../Templates/GO/EncounterArea7g.cs | 65 +++ .../Templates/GO/EncounterArea8g.cs | 65 +++ .../Templates/GO/EncounterSlot7GO.cs | 142 ++++++ .../GO/EncounterSlot8GO.cs | 161 +++++-- .../Encounters/Templates/GO/IPogoDateRange.cs | 57 +++ .../Encounters/Templates/GO/IPogoSlot.cs | 57 +++ .../GO/PogoType.cs | 0 .../Templates/Gen1/EncounterArea1.cs | 48 +++ .../Templates/Gen1/EncounterGBLanguage.cs | 16 + .../Templates/Gen1/EncounterGift1.cs | 215 ++++++++++ .../Templates/Gen1/EncounterSlot1.cs | 77 ++++ .../Templates/Gen1/EncounterStatic1.cs | 133 ++++++ .../Templates/Gen1/EncounterTrade1.cs | 197 +++++++++ .../Templates/Gen1/EncounterUtil1.cs | 49 +++ .../Templates/Gen1/IFixedGBLanguage.cs | 12 + .../Templates/Gen2/EncounterArea2.cs | 75 ++++ .../Templates/Gen2/EncounterGift2.cs | 254 +++++++++++ .../Templates/Gen2/EncounterSlot2.cs | 156 +++++++ .../Templates/Gen2/EncounterStatic2.cs | 214 ++++++++++ .../Templates/Gen2}/EncounterTime.cs | 0 .../Templates/Gen2/EncounterTrade2.cs | 203 +++++++++ .../Templates/Gen3/Colo/EncounterGift3Colo.cs | 175 ++++++++ .../Gen3/Colo/EncounterShadow3Colo.cs | 215 ++++++++++ .../Gen3/Colo/EncounterStatic3Colo.cs | 149 +++++++ .../Templates/Gen3/Colo/IShadow3.cs | 9 + .../Templates/Gen3}/EncounterArea3.cs | 73 +--- .../Templates/Gen3/EncounterSlot3.cs | 126 ++++++ .../Templates/Gen3/EncounterSlot3Swarm.cs | 17 + .../Templates/Gen3/EncounterStatic3.cs | 223 ++++++++++ .../Templates/Gen3/EncounterTrade3.cs | 177 ++++++++ .../Templates/Gen3/XD/EncounterArea3XD.cs | 25 ++ .../Templates/Gen3/XD/EncounterShadow3XD.cs | 168 ++++++++ .../Templates/Gen3/XD/EncounterSlot3XD.cs | 78 ++++ .../Templates/Gen3/XD/EncounterStatic3XD.cs | 160 +++++++ .../Templates/Gen3/XD/EncounterTrade3XD.cs | 186 ++++++++ .../Templates/Gen4/EncounterArea4.cs | 112 +++++ .../Templates/Gen4/EncounterSlot4.cs | 166 +++++++ .../Templates/Gen4/EncounterStatic4.cs | 261 +++++++++++ .../Gen4/EncounterStatic4Pokewalker.cs | 183 ++++++++ .../Templates/Gen4/EncounterTrade4PID.cs | 296 +++++++++++++ .../Gen4/EncounterTrade4RanchGift.cs | 166 +++++++ .../Templates/Gen4}/GroundTileAllowed.cs | 0 .../Gen4}/IGroundTypeTile.cs | 0 .../Templates/Gen4/PokewalkerCourse4.cs | 33 ++ .../Templates/Gen5/DreamWorldEntry.cs | 45 ++ .../Templates/Gen5}/EncounterArea5.cs | 38 +- .../Templates/Gen5/EncounterSlot5.cs | 116 +++++ .../Templates/Gen5/EncounterStatic5.cs | 199 +++++++++ .../Templates/Gen5/EncounterStatic5Entree.cs | 108 +++++ .../Templates/Gen5/EncounterStatic5N.cs | 133 ++++++ .../Templates/Gen5/EncounterStatic5Radar.cs | 106 +++++ .../Templates/Gen5/EncounterTrade5B2W2.cs | 160 +++++++ .../Templates/Gen5/EncounterTrade5BW.cs | 153 +++++++ .../Templates/Gen6/EncounterArea6AO.cs | 60 +++ .../Templates/Gen6}/EncounterArea6XY.cs | 84 +--- .../Templates/Gen6/EncounterSlot6AO.cs | 149 +++++++ .../Templates/Gen6/EncounterSlot6XY.cs | 138 ++++++ .../Templates/Gen6/EncounterStatic6.cs | 199 +++++++++ .../Templates/Gen6/EncounterTrade6.cs | 172 ++++++++ .../Templates/Gen7}/EncounterArea7.cs | 41 +- .../Templates/Gen7/EncounterSlot7.cs | 140 ++++++ .../Templates/Gen7/EncounterStatic7.cs | 227 ++++++++++ .../Templates/Gen7/EncounterTrade7.cs | 168 ++++++++ .../Templates/Gen7/EncounterTransfer7.cs | 63 +++ .../Templates/Gen7b}/EncounterArea7b.cs | 39 +- .../Templates/Gen7b/EncounterSlot7b.cs | 85 ++++ .../Templates/Gen7b/EncounterStatic7b.cs | 104 +++++ .../Templates/Gen7b/EncounterTrade7b.cs | 132 ++++++ .../Templates/Gen8/AreaSlotType8.cs | 38 ++ .../Encounters/Templates/Gen8/AreaWeather8.cs | 55 +++ .../Encounters/Templates/Gen8/Crossover8.cs | 8 + .../Templates/Gen8}/EncounterArea8.cs | 189 +------- .../Templates/Gen8/EncounterSlot8.cs | 206 +++++++++ .../Templates/Gen8/EncounterStatic8.cs | 203 +++++++++ .../Gen8}/EncounterStatic8N.cs | 13 +- .../Gen8}/EncounterStatic8NC.cs | 9 +- .../Gen8}/EncounterStatic8ND.cs | 21 +- .../Templates/Gen8/EncounterStatic8Nest.cs | 232 ++++++++++ .../Gen8}/EncounterStatic8U.cs | 30 +- .../Templates/Gen8/EncounterTrade8.cs | 210 +++++++++ .../Templates/Gen8/IOverworldCorrelation8.cs | 7 + .../Gen8/OverworldCorrelation8Requirement.cs | 8 + .../Templates/Gen8a}/EncounterArea8a.cs | 45 +- .../Gen8a}/EncounterSlot8a.cs | 172 ++++---- .../Templates/Gen8a/EncounterStatic8a.cs | 272 ++++++++++++ .../Gen8a/EncounterStatic8aCorrelation.cs | 7 + .../Gen8a}/IMasteryInitialMoveShop8.cs | 0 .../Templates/Gen8b}/EncounterArea8b.cs | 65 +-- .../Templates/Gen8b/EncounterSlot8b.cs | 178 ++++++++ .../Templates/Gen8b/EncounterStatic8b.cs | 205 +++++++++ .../Templates/Gen8b/EncounterTrade8b.cs | 239 +++++++++++ .../Templates/Gen8b/IStaticCorrelation8b.cs | 7 + .../Gen8b/StaticCorrelation8bRequirement.cs | 8 + .../Gen9}/AreaWeather9.cs | 0 .../Templates/Gen9/EncounterArea9.cs | 55 +++ .../Gen9}/EncounterDist9.cs | 196 +++++++-- .../Templates/Gen9/EncounterFixed9.cs | 243 +++++++++++ .../Gen9}/EncounterMight9.cs | 173 ++++++-- .../Templates/Gen9/EncounterSlot9.cs | 190 ++++++++ .../Templates/Gen9/EncounterStatic9.cs | 234 ++++++++++ .../Gen9}/EncounterTera9.cs | 177 +++++--- .../Templates/Gen9/EncounterTrade9.cs | 210 +++++++++ .../Gen9}/IGemType.cs | 0 .../Gen9}/ITeraRaid9.cs | 0 .../Gen9}/SizeType9.cs | 0 .../Templates/Interfaces/IEncounterArea.cs | 21 + .../Interfaces}/IEncounterConvertible.cs | 17 +- .../Interfaces}/IEncounterInfo.cs | 0 .../Interfaces}/IEncounterMatch.cs | 0 .../Interfaces}/IEncounterTemplate.cs | 26 +- .../Interfaces}/IEncounterable.cs | 0 .../Templates/Interfaces}/ILocation.cs | 0 .../Properties}/IEncounterFormRandom.cs | 3 + .../Properties}/IFixedAbilityNumber.cs | 0 .../Interfaces/Properties}/IFixedBall.cs | 0 .../Interfaces/Properties/IFixedGender.cs | 17 + .../Interfaces/Properties/IFixedNickname.cs | 30 ++ .../Interfaces/Properties/IFixedTrainer.cs | 23 + .../Interfaces/Properties/IFlawlessIVCount.cs | 12 + .../Properties/IFlawlessIVCountConditional.cs | 13 + .../Interfaces/Properties/IHatchCycle.cs | 15 + .../Interfaces/Properties/ILevelRange.cs | 61 +++ .../Interfaces/Properties}/IMoveset.cs | 0 .../Interfaces/Properties}/IRelearn.cs | 0 .../Interfaces/Properties/IRestrictVersion.cs | 12 + .../Interfaces/Properties}/IShinyPotential.cs | 0 .../Interfaces/Properties}/IVersion.cs | 0 .../Interfaces/RNG}/IMagnetStatic.cs | 2 +- .../Templates/Interfaces/RNG/INumberedSlot.cs | 15 + .../Templates/Interfaces/RNG/ISlotRNGType.cs | 12 + .../Shared}/EncounterEgg.cs | 0 .../Shared}/EncounterInvalid.cs | 0 .../Templates/Shared}/IndividualValueSet.cs | 0 .../Templates/Shared}/Moveset.cs | 0 .../Encounters/Verifiers/EncounterVerifier.cs | 143 +++---- .../EvolutionGroup/EvolutionGroup1.cs | 4 +- .../EvolutionGroup/EvolutionGroup2.cs | 4 +- .../EvolutionGroup/EvolutionGroup3.cs | 4 +- .../EvolutionGroup/EvolutionGroup4.cs | 4 +- .../EvolutionGroup/EvolutionGroup5.cs | 4 +- .../EvolutionGroup/EvolutionGroup6.cs | 4 +- .../EvolutionGroup/EvolutionGroup7.cs | 4 +- .../EvolutionGroup/EvolutionGroup7b.cs | 4 +- .../EvolutionGroup/EvolutionGroupHOME.cs | 16 +- .../EvolutionGroup/IEvolutionGroup.cs | 4 +- .../Forward/EvolutionForwardPersonal.cs | 2 +- .../Forward/EvolutionForwardSpecies.cs | 2 +- .../Evolutions/Forward/IEvolutionForward.cs | 2 +- .../Evolutions/Methods/EvoCriteria.cs | 2 +- .../Reversal/EvolutionReversePersonal.cs | 2 +- .../Reversal/EvolutionReverseSpecies.cs | 2 +- .../Evolutions/Reversal/IEvolutionReverse.cs | 2 +- .../LearnSource/Group/LearnGroupHOME.cs | 1 - .../Legality/LearnSource/LearnMethod.cs | 2 +- .../LearnSource/Sources/LearnSource1RB.cs | 2 +- .../LearnSource/Sources/LearnSource1YW.cs | 2 +- .../LearnSource/Sources/LearnSource2GS.cs | 26 ++ PKHeX.Core/Legality/LegalityAnalysis.cs | 3 +- PKHeX.Core/Legality/LegalityAnalyzers.cs | 1 - PKHeX.Core/Legality/Moves/GameData.cs | 18 + PKHeX.Core/Legality/RNG/Frame/Frame.cs | 33 +- PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs | 40 +- .../Legality/RNG/Frame/FrameGenerator.cs | 4 +- PKHeX.Core/Legality/RNG/Frame/FrameType.cs | 4 +- PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs | 4 +- PKHeX.Core/Legality/RNG/Frame/SlotRange.cs | 4 +- PKHeX.Core/Legality/RNG/Locks/LockFinder.cs | 10 +- PKHeX.Core/Legality/RNG/Locks/NPCLock.cs | 2 +- PKHeX.Core/Legality/RNG/MethodFinder.cs | 108 +---- PKHeX.Core/Legality/RNG/PIDGenerator.cs | 26 +- .../Legality/Restrictions/GBRestrictions.cs | 6 +- .../Restrictions/Memories/MemoryContext6.cs | 20 + .../Memories/MemoryContext6Data.cs | 40 ++ .../Restrictions/Memories/MemoryContext8.cs | 19 +- .../Memories/MemoryContext8Data.cs | 24 ++ .../Memories/MemoryPermissions.cs | 39 +- .../{Enums => Structures}/CheckIdentifier.cs | 0 PKHeX.Core/Legality/Structures/LegalInfo.cs | 2 +- .../{Enums => Structures}/Severity.cs | 0 .../Verifiers/Ability/AbilityVerifier.cs | 4 +- .../Legality/Verifiers/Ball/BallVerifier.cs | 52 +-- PKHeX.Core/Legality/Verifiers/CXDVerifier.cs | 46 +- .../Legality/Verifiers/EffortValueVerifier.cs | 43 +- .../Verifiers/Egg/EggStateLegality.cs | 11 +- .../Legality/Verifiers/GenderVerifier.cs | 4 +- .../Legality/Verifiers/GroundTileVerifier.cs | 19 +- .../Legality/Verifiers/HistoryVerifier.cs | 2 +- .../Verifiers/IndividualValueVerifier.cs | 63 +-- .../Legality/Verifiers/LanguageVerifier.cs | 2 +- .../Verifiers/LegendsArceusVerifier.cs | 11 +- .../Legality/Verifiers/LevelVerifier.cs | 4 +- PKHeX.Core/Legality/Verifiers/MiscVerifier.cs | 30 +- .../Legality/Verifiers/NHarmoniaVerifier.cs | 54 --- .../Legality/Verifiers/NicknameVerifier.cs | 255 ++--------- PKHeX.Core/Legality/Verifiers/PIDVerifier.cs | 52 +-- .../Legality/Verifiers/ParseSettings.cs | 10 +- .../Legality/Verifiers/Ribbons/MarkRules.cs | 4 +- .../Legality/Verifiers/Ribbons/RibbonRules.cs | 4 +- .../Legality/Verifiers/TrainerNameVerifier.cs | 8 +- .../Legality/Verifiers/TransferVerifier.cs | 4 +- PKHeX.Core/MysteryGifts/MysteryGift.cs | 2 +- PKHeX.Core/MysteryGifts/PCD.cs | 6 +- PKHeX.Core/MysteryGifts/PGF.cs | 10 +- PKHeX.Core/MysteryGifts/PGT.cs | 48 ++- PKHeX.Core/MysteryGifts/PL6.cs | 23 +- PKHeX.Core/MysteryGifts/WA8.cs | 6 +- PKHeX.Core/MysteryGifts/WB7.cs | 6 +- PKHeX.Core/MysteryGifts/WB8.cs | 6 +- PKHeX.Core/MysteryGifts/WC3.cs | 21 +- PKHeX.Core/MysteryGifts/WC6.cs | 13 +- PKHeX.Core/MysteryGifts/WC7.cs | 15 +- PKHeX.Core/MysteryGifts/WC8.cs | 10 +- PKHeX.Core/MysteryGifts/WC9.cs | 17 +- PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs | 17 +- PKHeX.Core/PKM/PK1.cs | 2 +- PKHeX.Core/PKM/PK2.cs | 2 +- PKHeX.Core/PKM/PK3.cs | 2 +- PKHeX.Core/PKM/PK4.cs | 4 +- PKHeX.Core/PKM/PKM.cs | 51 +-- PKHeX.Core/PKM/Strings/StringConverter.cs | 10 + .../legality/wild/Gen6/encounter_x.pkl | Bin 17452 -> 17476 bytes .../legality/wild/Gen6/encounter_y.pkl | Bin 17452 -> 17476 bytes .../Abstractions}/ITrainerInfo.cs | 4 +- .../Abstractions}/SimpleTrainerInfo.cs | 0 PKHeX.Core/Saves/SAV1.cs | 1 + PKHeX.Core/Saves/SAV2.cs | 1 + .../Saves/Substructures/Gen7/LGPE/GP1.cs | 11 +- .../Saves/Substructures/PokeDex/Zukan9.cs | 1 - PKHeX.Core/Saves/Util/SaveUtil.cs | 2 +- PKHeX.Core/Util/ArrayUtil.cs | 2 +- PKHeX.Core/Util/FileUtil.cs | 2 +- PKHeX.Core/Util/ValueTypeTypeConverter.cs | 15 +- PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs | 5 +- PKHeX.Drawing/ImageUtil.cs | 2 +- .../Controls/PKM Editor/PKMEditor.cs | 20 +- .../Controls/PKM Editor/StatEditor.cs | 2 +- PKHeX.WinForms/Subforms/SAV_Encounters.cs | 18 +- PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs | 14 +- .../Legality/LegalityTests.cs | 3 - .../Simulator/GeneratorTests.cs | 8 +- .../Simulator/ShowdownSetTests.cs | 23 +- 405 files changed, 22165 insertions(+), 13346 deletions(-) delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea.cs delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea1.cs delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea2.cs delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea3XD.cs delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea4.cs delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea6AO.cs delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea7g.cs delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea8g.cs delete mode 100644 PKHeX.Core/Legality/Areas/EncounterArea9.cs create mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1GBEra.cs create mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1VC.cs delete mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen12/Encounters1.cs delete mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen12/Encounters2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2GBEra.cs create mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen4/Encounters4DPPt.cs create mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen4/Encounters4HGSS.cs delete mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters4DPPt.cs delete mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters4HGSS.cs rename PKHeX.Core/Legality/Encounters/Data/{Gen45 => Gen5}/Encounters5B2W2.cs (52%) rename PKHeX.Core/Legality/Encounters/Data/{Gen45 => Gen5}/Encounters5BW.cs (66%) rename PKHeX.Core/Legality/Encounters/Data/{Gen45 => Gen5}/Encounters5DR.cs (97%) rename PKHeX.Core/Legality/Encounters/Data/{Gen67 => Gen6}/Encounters6AO.cs (62%) create mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen6/Encounters6XY.cs delete mode 100644 PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters6XY.cs rename PKHeX.Core/Legality/Encounters/Data/{Gen67 => Gen7}/Encounters7GG.cs (55%) rename PKHeX.Core/Legality/Encounters/Data/{Gen67 => Gen7}/Encounters7SM.cs (58%) rename PKHeX.Core/Legality/Encounters/Data/{Gen67 => Gen7}/Encounters7USUM.cs (69%) rename PKHeX.Core/Legality/Encounters/Data/{ => Gen9}/Encounters9.cs (52%) delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterMatchRating.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3PokeSpot.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6XY.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7b.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot9.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/GO/IPogoSlot.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterSlot/INumberedSlot.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/DreamWorldEntry.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterFixed9.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1E.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2E.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4Pokewalker.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5DR.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7b.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic9.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStaticShadow.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade1.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade2.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade3.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade4.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade5.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade6.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7b.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade9.cs delete mode 100644 PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTradeGB.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible3.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible3GC.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible4.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible5.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible6.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7GG.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7GO.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8GO.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8a.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible9.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/Dirtied/EncounterEnumerator8bSWSH.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/Dirtied/EncounterEnumerator9SWSH.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator3.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator3GC.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator4.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator5.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator6.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7GG.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7GO.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8GO.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8a.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator9.cs create mode 100644 PKHeX.Core/Legality/Encounters/Information/EncounterDate.cs rename PKHeX.Core/Legality/Encounters/{Generator => Templates/Enums}/AbilityPermission.cs (60%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Enums/EncounterMatchRating.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Enums/HiddenAbilityPermission.cs rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Enums}/Shiny.cs (87%) rename PKHeX.Core/Legality/{ => Encounters/Templates}/Enums/SlotType.cs (97%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea7g.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea8g.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot7GO.cs rename PKHeX.Core/Legality/Encounters/{EncounterSlot => Templates}/GO/EncounterSlot8GO.cs (60%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/GO/IPogoDateRange.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/GO/IPogoSlot.cs rename PKHeX.Core/Legality/Encounters/{EncounterSlot => Templates}/GO/PogoType.cs (100%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterArea1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterGBLanguage.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterGift1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterSlot1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterStatic1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterTrade1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterUtil1.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen1/IFixedGBLanguage.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterArea2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterGift2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterSlot2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterStatic2.cs rename PKHeX.Core/Legality/{Enums => Encounters/Templates/Gen2}/EncounterTime.cs (100%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTrade2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterGift3Colo.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterShadow3Colo.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterStatic3Colo.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/IShadow3.cs rename PKHeX.Core/Legality/{Areas => Encounters/Templates/Gen3}/EncounterArea3.cs (63%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3Swarm.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterStatic3.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterTrade3.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterArea3XD.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterShadow3XD.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterSlot3XD.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterStatic3XD.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterTrade3XD.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterArea4.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterSlot4.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4PID.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4RanchGift.cs rename PKHeX.Core/Legality/{Enums => Encounters/Templates/Gen4}/GroundTileAllowed.cs (100%) rename PKHeX.Core/Legality/Encounters/{EncounterSlot => Templates/Gen4}/IGroundTypeTile.cs (100%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen4/PokewalkerCourse4.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen5/DreamWorldEntry.cs rename PKHeX.Core/Legality/{Areas => Encounters/Templates/Gen5}/EncounterArea5.cs (59%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterSlot5.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5Entree.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5N.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5Radar.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterTrade5B2W2.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterTrade5BW.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterArea6AO.cs rename PKHeX.Core/Legality/{Areas => Encounters/Templates/Gen6}/EncounterArea6XY.cs (62%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6AO.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6XY.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterStatic6.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterTrade6.cs rename PKHeX.Core/Legality/{Areas => Encounters/Templates/Gen7}/EncounterArea7.cs (59%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterSlot7.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterStatic7.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterTrade7.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterTransfer7.cs rename PKHeX.Core/Legality/{Areas => Encounters/Templates/Gen7b}/EncounterArea7b.cs (60%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterSlot7b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterStatic7b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterTrade7b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/AreaSlotType8.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/AreaWeather8.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/Crossover8.cs rename PKHeX.Core/Legality/{Areas => Encounters/Templates/Gen8}/EncounterArea8.cs (65%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterSlot8.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8.cs rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen8}/EncounterStatic8N.cs (89%) rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen8}/EncounterStatic8NC.cs (76%) rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen8}/EncounterStatic8ND.cs (78%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen8}/EncounterStatic8U.cs (53%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterTrade8.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/IOverworldCorrelation8.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8/OverworldCorrelation8Requirement.cs rename PKHeX.Core/Legality/{Areas => Encounters/Templates/Gen8a}/EncounterArea8a.cs (58%) rename PKHeX.Core/Legality/Encounters/{EncounterSlot => Templates/Gen8a}/EncounterSlot8a.cs (57%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8a.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8aCorrelation.cs rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Gen8a}/IMasteryInitialMoveShop8.cs (100%) rename PKHeX.Core/Legality/{Areas => Encounters/Templates/Gen8b}/EncounterArea8b.cs (64%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterSlot8b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterStatic8b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterTrade8b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8b/IStaticCorrelation8b.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen8b/StaticCorrelation8bRequirement.cs rename PKHeX.Core/Legality/Encounters/{EncounterSlot => Templates/Gen9}/AreaWeather9.cs (100%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterArea9.cs rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen9}/EncounterDist9.cs (63%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterFixed9.cs rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen9}/EncounterMight9.cs (72%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterSlot9.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterStatic9.cs rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen9}/EncounterTera9.cs (51%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterTrade9.cs rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen9}/IGemType.cs (100%) rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen9}/ITeraRaid9.cs (100%) rename PKHeX.Core/Legality/Encounters/{EncounterStatic => Templates/Gen9}/SizeType9.cs (100%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterArea.cs rename PKHeX.Core/Legality/Encounters/{ => Templates/Interfaces}/IEncounterConvertible.cs (52%) rename PKHeX.Core/Legality/Encounters/{ => Templates/Interfaces}/IEncounterInfo.cs (100%) rename PKHeX.Core/Legality/Encounters/{ => Templates/Interfaces}/IEncounterMatch.cs (100%) rename PKHeX.Core/Legality/Encounters/{ => Templates/Interfaces}/IEncounterTemplate.cs (51%) rename PKHeX.Core/Legality/Encounters/{ => Templates/Interfaces}/IEncounterable.cs (100%) rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Interfaces}/ILocation.cs (100%) rename PKHeX.Core/Legality/Encounters/{ => Templates/Interfaces/Properties}/IEncounterFormRandom.cs (70%) rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Interfaces/Properties}/IFixedAbilityNumber.cs (100%) rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Interfaces/Properties}/IFixedBall.cs (100%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedGender.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedNickname.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedTrainer.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFlawlessIVCount.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFlawlessIVCountConditional.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IHatchCycle.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/ILevelRange.cs rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Interfaces/Properties}/IMoveset.cs (100%) rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Interfaces/Properties}/IRelearn.cs (100%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IRestrictVersion.cs rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Interfaces/Properties}/IShinyPotential.cs (100%) rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Interfaces/Properties}/IVersion.cs (100%) rename PKHeX.Core/Legality/Encounters/{EncounterSlot => Templates/Interfaces/RNG}/IMagnetStatic.cs (93%) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/INumberedSlot.cs create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/ISlotRNGType.cs rename PKHeX.Core/Legality/Encounters/{EncounterMisc => Templates/Shared}/EncounterEgg.cs (100%) rename PKHeX.Core/Legality/Encounters/{EncounterMisc => Templates/Shared}/EncounterInvalid.cs (100%) rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Shared}/IndividualValueSet.cs (100%) rename PKHeX.Core/Legality/{Structures => Encounters/Templates/Shared}/Moveset.cs (100%) rename PKHeX.Core/Legality/{Enums => Structures}/CheckIdentifier.cs (100%) rename PKHeX.Core/Legality/{Enums => Structures}/Severity.cs (100%) delete mode 100644 PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs rename PKHeX.Core/{Legality/Structures => Saves/Abstractions}/ITrainerInfo.cs (97%) rename PKHeX.Core/{Legality/Structures => Saves/Abstractions}/SimpleTrainerInfo.cs (100%) diff --git a/PKHeX.Core/Editing/Applicators/CatchRateApplicator.cs b/PKHeX.Core/Editing/Applicators/CatchRateApplicator.cs index c6c22bd0e..0e54bccf4 100644 --- a/PKHeX.Core/Editing/Applicators/CatchRateApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/CatchRateApplicator.cs @@ -31,9 +31,7 @@ public static class CatchRateApplicator var enc = la.EncounterMatch; switch (enc) { - case EncounterTrade1 c: - return c.GetInitialCatchRate(); - case EncounterStatic1E { Version: GameVersion.Stadium, Species: (int)Species.Psyduck}: + case EncounterGift1 { Version: GameVersion.Stadium, Species: (int)Species.Psyduck }: return pk.Japanese ? (byte)167 : (byte)168; // Amnesia Psyduck has different catch rates depending on language default: var pt = GetPersonalTable(sav, enc); diff --git a/PKHeX.Core/Editing/Applicators/MoveApplicator.cs b/PKHeX.Core/Editing/Applicators/MoveApplicator.cs index 3db8713c3..f5b122450 100644 --- a/PKHeX.Core/Editing/Applicators/MoveApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/MoveApplicator.cs @@ -64,8 +64,6 @@ public static class MoveApplicator pk.SetMoves(moves); if (maxPP && Legal.IsPPUpAvailable(pk)) pk.SetMaximumPPUps(moves); - else - pk.SetMaximumPPCurrent(moves); pk.FixMoves(); } diff --git a/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs b/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs index 328899b80..dcf83b6f2 100644 --- a/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs @@ -48,7 +48,6 @@ public static class MoveSetApplicator var clone = pk.Clone(); clone.SetMoves(moves); - clone.SetMaximumPPCurrent(moves); var newLa = new LegalityAnalysis(clone); if (newLa.Valid) diff --git a/PKHeX.Core/Editing/Bulk/BatchEditing.cs b/PKHeX.Core/Editing/Bulk/BatchEditing.cs index cd210765d..973535e2f 100644 --- a/PKHeX.Core/Editing/Bulk/BatchEditing.cs +++ b/PKHeX.Core/Editing/Bulk/BatchEditing.cs @@ -538,7 +538,9 @@ public static class BatchEditing { if (cmd.PropertyName == nameof(PKM.IVs)) { - pk.SetRandomIVs(); + var la = new LegalityAnalysis(pk); + var flawless = la.EncounterMatch is IFlawlessIVCount fc ? fc.FlawlessIVCount : 0; + pk.SetRandomIVs(flawless); return; } diff --git a/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs b/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs index 10035c9c6..fbc8f5c42 100644 --- a/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs +++ b/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs @@ -120,7 +120,6 @@ internal static class BatchModifications public static ModifyResult SetMoves(PKM pk, ReadOnlySpan moves) { pk.SetMoves(moves); - pk.HealPP(); return ModifyResult.Modified; } diff --git a/PKHeX.Core/Editing/CommonEdits.cs b/PKHeX.Core/Editing/CommonEdits.cs index b6eacf2fa..7df4d26a8 100644 --- a/PKHeX.Core/Editing/CommonEdits.cs +++ b/PKHeX.Core/Editing/CommonEdits.cs @@ -403,7 +403,7 @@ public static class CommonEdits /// Precomputed optional public static void SetDefaultNickname(this PKM pk, LegalityAnalysis la) { - if (la is { Parsed: true, EncounterOriginal: EncounterTrade {HasNickname: true} t }) + if (la is { Parsed: true, EncounterOriginal: IFixedNickname {IsFixedNickname: true} t }) pk.SetNickname(t.GetNickname(pk.Language)); else pk.ClearNickname(); diff --git a/PKHeX.Core/Editing/Database/TrainerDatabase.cs b/PKHeX.Core/Editing/Database/TrainerDatabase.cs index 50d3cdefc..7f227eec8 100644 --- a/PKHeX.Core/Editing/Database/TrainerDatabase.cs +++ b/PKHeX.Core/Editing/Database/TrainerDatabase.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace PKHeX.Core; @@ -139,7 +139,7 @@ public sealed class TrainerDatabase if (pk is IRegionOrigin r) r.CopyRegionOrigin(result); else - result.SetDefaultRegionOrigins(); + result.SetDefaultRegionOrigins(result.Language); return result; } diff --git a/PKHeX.Core/Editing/PKM/EntitySuggestionUtil.cs b/PKHeX.Core/Editing/PKM/EntitySuggestionUtil.cs index 40334089a..a399fb7d6 100644 --- a/PKHeX.Core/Editing/PKM/EntitySuggestionUtil.cs +++ b/PKHeX.Core/Editing/PKM/EntitySuggestionUtil.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using static PKHeX.Core.MessageStrings; @@ -9,7 +9,7 @@ namespace PKHeX.Core; /// public static class EntitySuggestionUtil { - public static List GetMetLocationSuggestionMessage(PKM pk, int level, int location, int minimumLevel) + public static List GetMetLocationSuggestionMessage(PKM pk, int level, int location, int minimumLevel, IEncounterable? enc) { var suggestion = new List { MsgPKMSuggestionStart }; if (pk.Format >= 3) @@ -19,6 +19,22 @@ public static class EntitySuggestionUtil suggestion.Add($"{MsgPKMSuggestionMetLocation} {locationName}"); suggestion.Add($"{MsgPKMSuggestionMetLevel} {level}"); } + else if (pk is ICaughtData2) + { + var metList = GameInfo.GetLocationList(GameVersion.C, pk.Context); + string locationName; + if (enc?.Version.Contains(GameVersion.C) == true) + { + locationName = metList.First(loc => loc.Value == location).Text; + } + else + { + locationName = metList[0].Text; + level = 0; + } + suggestion.Add($"{MsgPKMSuggestionMetLocation} {locationName}"); + suggestion.Add($"{MsgPKMSuggestionMetLevel} {level}"); + } if (pk.CurrentLevel < minimumLevel) suggestion.Add($"{MsgPKMSuggestionLevel} {minimumLevel}"); return suggestion; diff --git a/PKHeX.Core/Editing/Saves/Editors/EventUnlock/EventUnlocker8b.cs b/PKHeX.Core/Editing/Saves/Editors/EventUnlock/EventUnlocker8b.cs index 449fc052f..33ee8a63c 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventUnlock/EventUnlocker8b.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventUnlock/EventUnlocker8b.cs @@ -105,5 +105,4 @@ public sealed class EventUnlocker8b : EventUnlocker for (int i = FASHION_START; i <= FASHION_END; i++) SAV.FlagWork.SetFlag(i, true); } - } diff --git a/PKHeX.Core/Items/ItemStorage2.cs b/PKHeX.Core/Items/ItemStorage2.cs index 735e73c95..b845048d4 100644 --- a/PKHeX.Core/Items/ItemStorage2.cs +++ b/PKHeX.Core/Items/ItemStorage2.cs @@ -12,23 +12,23 @@ public sealed class ItemStorage2 : IItemStorage private static ReadOnlySpan Pouch_Items_GSC => new ushort[] { - 003, 008, 009, + 003, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, 020, 021, 022, 023, 024, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, 040, 041, 042, 043, 044, 046, 047, 048, 049, - 051, 052, 053, 057, + 051, 052, 053, 057, 060, 062, 063, 064, 065, - 072, 073, 074, 075, 076, 077, 078, 079, + 072, 073, 074, 075, 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, - 091, 092, 093, 094, 095, 096, 097, 098, 099, - 101, 102, 103, 104, 105, 106, 107, 108, 109, + 091, 092, 093, 094, 095, 096, 097, 098, 099, + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 117, 118, 119, - 121, 122, 123, 124, 125, 126, - 131, 132, 138, 139, + 121, 122, 123, 124, 125, 126, + 131, 132, 138, 139, 140, 143, 144, 146, 150, 151, 152, 156, 158, - 163, 167, 168, 169, + 163, 167, 168, 169, 170, 172, 173, 174, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, }; @@ -40,7 +40,7 @@ public sealed class ItemStorage2 : IItemStorage private static ReadOnlySpan Pouch_Key_GS => new ushort[] { - 7, 54, 55, 58, 59, 61, 66, 67, 68, 69, 71, 127, 128, 130, 133, 134, 175, 178, + 007, 054, 055, 058, 059, 061, 066, 067, 068, 069, 071, 127, 128, 130, 133, 134, 175, 178, }; private const int ExtraKeyCrystal = 4; @@ -53,10 +53,10 @@ public sealed class ItemStorage2 : IItemStorage private static ReadOnlySpan Pouch_TMHM_GSC => new ushort[] { - 191, 192, 193, 194, 196, 197, 198, 199, + 191, 192, 193, 194, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 221, 222, 223, 224, 225, 226, 227, 228, 229, + 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, }; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea.cs b/PKHeX.Core/Legality/Areas/EncounterArea.cs deleted file mode 100644 index a041396be..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Represents an Area where can be encountered, which contains a Location ID and data. -/// -public abstract record EncounterArea(GameVersion Version) : IVersion -{ - public int Location { get; protected init; } - public SlotType Type { get; protected init; } - - /// - /// Checks if the provided met location ID matches the parameters for the area. - /// - /// Met Location ID - /// True if possibly originated from this area, false otherwise. - public virtual bool IsMatchLocation(int location) => Location == location; -} - -internal interface IMemorySpeciesArea -{ - bool HasSpecies(ushort species); -} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea1.cs b/PKHeX.Core/Legality/Areas/EncounterArea1.cs deleted file mode 100644 index 5021c8997..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea1.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PKHeX.Core; - -/// -/// -/// encounter area -/// -public sealed record EncounterArea1 : EncounterArea -{ - public readonly int Rate; - public readonly EncounterSlot1[] Slots; - - public static EncounterArea1[] GetAreas(BinLinkerAccessor input, GameVersion game) - { - var result = new EncounterArea1[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea1(input[i], game); - return result; - } - - private EncounterArea1(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = data[0]; - // 1 byte unused - Type = (SlotType)data[2]; - Rate = data[3]; - - var next = data[4..]; - int count = next.Length / 4; - var slots = new EncounterSlot1[count]; - for (int i = 0; i < slots.Length; i++) - { - const int size = 4; - var entry = next.Slice(i * size, size); - byte max = entry[3]; - byte min = entry[2]; - byte slotNum = entry[1]; - byte species = entry[0]; - slots[i] = new EncounterSlot1(this, species, min, max, slotNum); - } - Slots = slots; - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - (bool useCatchRate, byte rate) = pk is PK1 pk1 ? (true, pk1.Catch_Rate) : (false, (byte)0); - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.LevelMin > evo.LevelMax) - break; - if (slot.Form != evo.Form) - break; - - if (useCatchRate) - { - var expect = (slot.Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB)[slot.Species].CatchRate; - if (expect != rate && !(ParseSettings.AllowGen1Tradeback && GBRestrictions.IsTradebackCatchRate(rate))) - break; - } - yield return slot; - break; - } - } - } -} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea2.cs b/PKHeX.Core/Legality/Areas/EncounterArea2.cs deleted file mode 100644 index 589c4bf45..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea2.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PKHeX.Core; - -/// -/// -/// encounter area -/// -public sealed record EncounterArea2 : EncounterArea -{ - private static ReadOnlySpan BCC_SlotRates => new byte[] { 20, 20, 10, 10, 05, 05, 10, 10, 05, 05 }; - private static ReadOnlySpan RatesGrass => new byte[] { 30, 30, 20, 10, 5, 4, 1 }; - private static ReadOnlySpan RatesSurf => new byte[] { 60, 30, 10 }; - - internal readonly EncounterTime Time; - public readonly byte Rate; - public readonly byte[]? Rates; - public readonly EncounterSlot2[] Slots; - - public static EncounterArea2[] GetAreas(BinLinkerAccessor input, GameVersion game) - { - var result = new EncounterArea2[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea2(input[i], game); - return result; - } - - private EncounterArea2(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = data[0]; - Time = (EncounterTime)data[1]; - var type = (Type = (SlotType)data[2]) & (SlotType)0xF; - Rate = data[3]; - - var next = data[4..]; - if (type is > SlotType.Surf and not SlotType.BugContest) // Not Grass/Surf - { - const int size = 5; - int count = next.Length / size; - Rates = next[..count].ToArray(); - Slots = ReadSlots(next[count..], count); - } - else - { - const int size = 4; - int count = next.Length / size; - Rates = null; // fetch as needed. - Slots = ReadSlots(next, count); - } - } - - private EncounterSlot2[] ReadSlots(ReadOnlySpan data, int count) - { - const int size = 4; - var slots = new EncounterSlot2[count]; - for (int i = 0; i < slots.Length; i++) - { - var entry = data.Slice(i * size, size); - byte max = entry[3]; - byte min = entry[2]; - byte slotNum = entry[1]; - byte species = entry[0]; - slots[i] = new EncounterSlot2(this, species, min, max, slotNum); - } - return slots; - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - if (pk is not ICaughtData2 {CaughtData: not 0} pk2) - return GetSlotsFuzzy(chain); - - if (pk2.Met_Location != Location) - return Array.Empty(); - return GetSlotsSpecificLevelTime(chain, pk2.Met_TimeOfDay, pk2.Met_Level); - } - - private IEnumerable GetSlotsSpecificLevelTime(EvoCriteria[] chain, int time, int lvl) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - { - if (slot.Species != (int)Species.Unown || evo.Form >= 26) // Don't yield !? forms - break; - } - - if (!slot.IsLevelWithinRange(lvl)) - break; - - if (!Time.Contains(time)) - break; - - yield return slot; - break; - } - } - } - - private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - { - if (slot.Species != (int) Species.Unown || evo.Form >= 26) // Don't yield !? forms - break; - } - if (slot.LevelMin > evo.LevelMax) - break; - - yield return slot; - break; - } - } - } -} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs b/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs deleted file mode 100644 index 882fcdb69..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PKHeX.Core; - -/// -/// -/// encounter area -/// -public sealed record EncounterArea3XD : EncounterArea -{ - public readonly EncounterSlot3PokeSpot[] Slots; - - public EncounterArea3XD(int loc, ushort s0, byte l0, ushort s1, byte l1, ushort s2, byte l2) : base(GameVersion.XD) - { - Location = loc; - Type = SlotType.Grass; - Slots = new[] - { - new EncounterSlot3PokeSpot(this, s0, 10, l0, 0), - new EncounterSlot3PokeSpot(this, s1, 10, l1, 1), - new EncounterSlot3PokeSpot(this, s2, 10, l2, 2), - }; - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - if (pk.Format != 3) // Met Location and Met Level are changed on PK3->PK4 - return GetSlotsFuzzy(chain); - if (pk.Met_Location != Location) - return Array.Empty(); - return GetSlotsMatching(chain, pk.Met_Level); - } - - private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - break; - if (!slot.IsLevelWithinRange(lvl)) - break; - - yield return slot; - break; - } - } - } - - private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - break; - if (slot.LevelMin > evo.LevelMax) - break; - - yield return slot; - break; - } - } - } -} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea4.cs b/PKHeX.Core/Legality/Areas/EncounterArea4.cs deleted file mode 100644 index 758ca38d6..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea4.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using static System.Buffers.Binary.BinaryPrimitives; - -namespace PKHeX.Core; - -/// -/// -/// encounter area -/// -public sealed record EncounterArea4 : EncounterArea -{ - public readonly int Rate; - public readonly GroundTileAllowed GroundTile; - public readonly EncounterSlot4[] Slots; - - public static EncounterArea4[] GetAreas(BinLinkerAccessor input, GameVersion game) - { - var result = new EncounterArea4[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea4(input[i], game); - return result; - } - - private EncounterArea4(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = ReadUInt16LittleEndian(data); - Type = (SlotType)data[2]; - Rate = data[3]; - // although GroundTilePermission flags are 32bit, none have values > 16bit. - GroundTile = (GroundTileAllowed)ReadUInt16LittleEndian(data[4..]); - - Slots = ReadRegularSlots(data); - } - - private EncounterSlot4[] ReadRegularSlots(ReadOnlySpan data) - { - const int size = 10; - int count = (data.Length - 6) / size; - var slots = new EncounterSlot4[count]; - for (int i = 0; i < slots.Length; i++) - { - int offset = 6 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadRegularSlot(entry); - } - - return slots; - } - - private EncounterSlot4 ReadRegularSlot(ReadOnlySpan entry) - { - ushort species = ReadUInt16LittleEndian(entry); - byte form = entry[2]; - byte slotNum = entry[3]; - byte min = entry[4]; - byte max = entry[5]; - byte mpi = entry[6]; - byte mpc = entry[7]; - byte sti = entry[8]; - byte stc = entry[9]; - return new EncounterSlot4(this, species, form, min, max, slotNum, mpi, mpc, sti, stc); - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - if (pk.Format != 4) // Met Location and Met Level are changed on PK4->PK5 - return GetSlotsFuzzy(chain); - if (pk.Met_Location != Location) - return Array.Empty(); - return GetSlotsMatching(chain, pk.Met_Level, pk); - } - - private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl, PKM pk) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) - { - // Unown forms are random, not specific form IDs - if (!slot.IsRandomUnspecificForm) - break; - } - if (!slot.IsLevelWithinRange(lvl)) - break; - - if (Type is SlotType.HoneyTree && IsInaccessibleHoneySlotLocation(slot, pk)) - break; - - yield return slot; - break; - } - } - } - - private static bool IsInaccessibleHoneySlotLocation(EncounterSlot4 slot, PKM pk) - { - // A/B/C tables, only Munchlax is a 'C' encounter, and A/B are accessible from any tree. - // C table encounters are only available from 4 trees, which are determined by TID16/SID16 of the save file. - if (slot.Species is not (int)Species.Munchlax) - return false; - - // We didn't encode the honey tree index to the encounter slot resource. - // Check if any of the slot's location doesn't match any of the groupC trees' area location ID. - var location = pk.Met_Location; - var trees = SAV4Sinnoh.CalculateMunchlaxTrees(pk.TID16, pk.SID16); - return LocationID_HoneyTree[trees.Tree1] != location - && LocationID_HoneyTree[trees.Tree2] != location - && LocationID_HoneyTree[trees.Tree3] != location - && LocationID_HoneyTree[trees.Tree4] != location; - } - - private static ReadOnlySpan LocationID_HoneyTree => new byte[] - { - 20, // 00 Route 205 Floaroma - 20, // 01 Route 205 Eterna - 21, // 02 Route 206 - 22, // 03 Route 207 - 23, // 04 Route 208 - 24, // 05 Route 209 - 25, // 06 Route 210 Solaceon - 25, // 07 Route 210 Celestic - 26, // 08 Route 211 - 27, // 09 Route 212 Hearthome - 27, // 10 Route 212 Pastoria - 28, // 11 Route 213 - 29, // 12 Route 214 - 30, // 13 Route 215 - 33, // 14 Route 218 - 36, // 15 Route 221 - 37, // 16 Route 222 - 47, // 17 Valley Windworks - 48, // 18 Eterna Forest - 49, // 19 Fuego Ironworks - 58, // 20 Floaroma Meadow - }; - - // original met level cannot be inferred - private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) - { - // Unown forms are random, not specific form IDs - if (!slot.IsRandomUnspecificForm) - break; - } - if (slot.LevelMin > evo.LevelMax) - break; - - yield return slot; - break; - } - } - } -} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs b/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs deleted file mode 100644 index d7eec4204..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using static System.Buffers.Binary.BinaryPrimitives; - -namespace PKHeX.Core; - -/// -/// -/// encounter area -/// -public sealed record EncounterArea6AO : EncounterArea, IMemorySpeciesArea -{ - public readonly EncounterSlot6AO[] Slots; - - public static EncounterArea6AO[] GetAreas(BinLinkerAccessor input, GameVersion game) - { - var result = new EncounterArea6AO[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea6AO(input[i], game); - return result; - } - - private EncounterArea6AO(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = ReadInt16LittleEndian(data); - Type = (SlotType)data[2]; - - Slots = ReadSlots(data); - } - - private EncounterSlot6AO[] ReadSlots(ReadOnlySpan data) - { - const int size = 4; - int count = (data.Length - 4) / size; - var slots = new EncounterSlot6AO[count]; - for (int i = 0; i < slots.Length; i++) - { - int offset = 4 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadSlot(entry); - } - - return slots; - } - - private EncounterSlot6AO ReadSlot(ReadOnlySpan entry) - { - ushort species = ReadUInt16LittleEndian(entry); - byte form = (byte)(species >> 11); - species &= 0x3FF; - byte min = entry[2]; - byte max = entry[3]; - return new EncounterSlot6AO(this, species, form, min, max); - } - - private const int FluteBoostMin = 4; // White Flute decreases levels. - private const int FluteBoostMax = 4; // Black Flute increases levels. - private const int DexNavBoost = 30; // Maximum DexNav chain - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - var boostMax = Type != SlotType.Rock_Smash ? DexNavBoost : FluteBoostMax; - const int boostMin = FluteBoostMin; - if (!slot.IsLevelWithinRange(pk.Met_Level, boostMin, boostMax)) - break; - - if (slot.Form != evo.Form && !slot.IsRandomUnspecificForm) - break; - - // Track some metadata about how this slot was matched. - var clone = slot with - { - WhiteFlute = evo.LevelMin < slot.LevelMin, - BlackFlute = evo.LevelMin > slot.LevelMax && evo.LevelMin <= slot.LevelMax + FluteBoostMax, - DexNav = slot.CanDexNav && (evo.LevelMin != slot.LevelMax || pk.RelearnMove1 != 0 || pk.AbilityNumber == 4), - }; - yield return clone; - break; - } - } - } - - public bool HasSpecies(ushort species) - { - foreach (var slot in Slots) - { - if (slot.Species == species) - return true; - } - return false; - } -} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7g.cs b/PKHeX.Core/Legality/Areas/EncounterArea7g.cs deleted file mode 100644 index d7e317800..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea7g.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using static System.Buffers.Binary.BinaryPrimitives; - -namespace PKHeX.Core; - -/// -/// -/// encounter area for -/// -public sealed record EncounterArea7g : EncounterArea, ISpeciesForm -{ - /// Species for the area - /// Due to how the encounter data is packaged by PKHeX, each species-form is grouped together. - public ushort Species { get; } - /// Form of the Species - public byte Form { get; } - public readonly EncounterSlot7GO[] Slots; - - private EncounterArea7g(ushort species, byte form, EncounterSlot7GO[] slots) : base(GameVersion.GO) - { - Species = species; - Form = form; - Location = Locations.GO7; - Slots = slots; - } - - internal static EncounterArea7g[] GetArea(BinLinkerAccessor data) - { - var areas = new EncounterArea7g[data.Length]; - for (int i = 0; i < areas.Length; i++) - areas[i] = GetArea(data[i]); - return areas; - } - - private const int meta = 4; - private const int entrySize = (2 * sizeof(int)) + 2; - - private static EncounterArea7g GetArea(ReadOnlySpan data) - { - var species = ReadUInt16LittleEndian(data); - var form = data[2]; - //var import = (EntityFormatDetected)data[3]; - - data = data[meta..]; - var result = new EncounterSlot7GO[data.Length / entrySize]; - var area = new EncounterArea7g(species, form, result); - for (int i = 0; i < result.Length; i++) - { - var offset = i * entrySize; - var entry = data.Slice(offset, entrySize); - result[i] = ReadSlot(entry, area, species, form); - } - - return area; - } - - private static EncounterSlot7GO ReadSlot(ReadOnlySpan entry, EncounterArea7g area, ushort species, byte form) - { - int start = ReadInt32LittleEndian(entry); - int end = ReadInt32LittleEndian(entry[4..]); - var sg = entry[8]; - var shiny = (Shiny)(sg & 0x3F); - var gender = (Gender)(sg >> 6); - var type = (PogoType)entry[9]; - return new EncounterSlot7GO(area, species, form, start, end, shiny, gender, type); - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - var sf = Array.Find(chain, z => z.Species == Species && z.Form == Form); - if (sf == default) - return Array.Empty(); - - return GetMatchingSlots(pk, sf); - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria evo) - { - // Find the first chain that has slots defined. - // Since it is possible to evolve before transferring, we only need the highest evolution species possible. - // PoGoEncTool has already extrapolated the evolutions to separate encounters! - var stamp = EncounterSlotGO.GetTimeStamp(pk.Met_Year + 2000, pk.Met_Month, pk.Met_Day); - var met = Math.Max(evo.LevelMin, pk.Met_Level); - EncounterSlot7GO? deferredIV = null; - - foreach (var slot in Slots) - { - if (!slot.IsLevelWithinRange(met)) - continue; - //if (!slot.IsBallValid(ball)) -- can have any of the in-game balls due to re-capture - // continue; - if (!slot.Shiny.IsValid(pk)) - continue; - //if (slot.Gender != Gender.Random && (int) slot.Gender != pk.Gender) - // continue; - if (!slot.IsWithinStartEnd(stamp)) - continue; - - if (!slot.GetIVsValid(pk)) - { - deferredIV ??= slot; - continue; - } - - yield return slot; - } - - if (deferredIV != null) - yield return deferredIV; - } -} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8g.cs b/PKHeX.Core/Legality/Areas/EncounterArea8g.cs deleted file mode 100644 index 11c94bf97..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea8g.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using static System.Buffers.Binary.BinaryPrimitives; - -namespace PKHeX.Core; - -/// -/// -/// encounter area for direct-to-HOME transfers. -/// -public sealed record EncounterArea8g : EncounterArea, ISpeciesForm -{ - /// Species for the area - /// Due to how the encounter data is packaged by PKHeX, each species-form is grouped together. - public ushort Species { get; } - /// Form of the Species - public byte Form { get; } - public readonly EncounterSlot8GO[] Slots; - - private EncounterArea8g(ushort species, byte form, EncounterSlot8GO[] slots) : base(GameVersion.GO) - { - Species = species; - Form = form; - Location = Locations.GO8; - Slots = slots; - } - - internal static EncounterArea8g[] GetArea(BinLinkerAccessor data) - { - var areas = new EncounterArea8g[data.Length]; - for (int i = 0; i < areas.Length; i++) - areas[i] = GetArea(data[i]); - return areas; - } - - private const int meta = 4; - private const int entrySize = (2 * sizeof(int)) + 2; - - private static EncounterArea8g GetArea(ReadOnlySpan data) - { - var species = ReadUInt16LittleEndian(data); - var form = data[2]; - var import = (PogoImportFormat)data[3]; - - data = data[meta..]; - var result = new EncounterSlot8GO[data.Length / entrySize]; - var area = new EncounterArea8g(species, form, result); - for (int i = 0; i < result.Length; i++) - { - var offset = i * entrySize; - var entry = data.Slice(offset, entrySize); - result[i] = ReadSlot(entry, area, species, form, import); - } - - return area; - } - - private static EncounterSlot8GO ReadSlot(ReadOnlySpan entry, EncounterArea8g area, ushort species, byte form, PogoImportFormat format) - { - int start = ReadInt32LittleEndian(entry); - int end = ReadInt32LittleEndian(entry[4..]); - var sg = entry[8]; - var shiny = (Shiny)(sg & 0x3F); - var gender = (Gender)(sg >> 6); - var type = (PogoType)entry[9]; - return new EncounterSlot8GO(area, species, form, start, end, shiny, gender, type, format); - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - // Find the first chain that has slots defined. - // Since it is possible to evolve before transferring, we only need the highest evolution species possible. - // PoGoEncTool has already extrapolated the evolutions to separate encounters! - var sf = FindCriteriaToIterate(pk, chain); - if (sf == default) - return Array.Empty(); - - return GetMatchingSlots(pk, sf); - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria evo) - { - var species = pk.Species; - var ball = (Ball)pk.Ball; - var met = Math.Max(evo.LevelMin, pk.Met_Level); - EncounterSlot8GO? deferredIV = null; - - foreach (var slot in Slots) - { - if (!slot.IsLevelWithinRange(met)) - continue; - if (!slot.IsBallValid(ball, species, pk)) - continue; - if (!slot.Shiny.IsValid(pk)) - continue; - if (slot.Gender != Gender.Random && (int)slot.Gender != pk.Gender) - continue; - - if (!slot.GetIVsValid(pk)) - { - deferredIV ??= slot; - continue; - } - - yield return slot; - } - - if (deferredIV != null) - yield return deferredIV; - } - - private EvoCriteria FindCriteriaToIterate(PKM pk, EvoCriteria[] chain) - { - foreach (var evo in chain) - { - if (evo.Species != Species) - continue; - - if (evo.Form == Form) - return evo; - - // Check for form mismatches - if (FormInfo.IsFormChangeable(Species, Form, evo.Form, EntityContext.Gen8, pk.Context)) - return evo; - if (Species == (int)Core.Species.Burmy) - return evo; - break; - } - return default; - } -} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea9.cs b/PKHeX.Core/Legality/Areas/EncounterArea9.cs deleted file mode 100644 index 7e6cf60a7..000000000 --- a/PKHeX.Core/Legality/Areas/EncounterArea9.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Collections.Generic; -using static System.Buffers.Binary.BinaryPrimitives; - -namespace PKHeX.Core; - -/// -/// -/// encounter area -/// -public sealed record EncounterArea9 : EncounterArea -{ - public readonly EncounterSlot9[] Slots; - - public ushort CrossFrom { get; } - - public static EncounterArea9[] GetAreas(BinLinkerAccessor input, GameVersion game) - { - var result = new EncounterArea9[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea9(input[i], game); - return result; - } - - private EncounterArea9(ReadOnlySpan areaData, GameVersion game) : base(game) - { - Location = areaData[0]; - CrossFrom = areaData[2]; - Slots = ReadSlots(areaData[4..]); - } - - private EncounterSlot9[] ReadSlots(ReadOnlySpan areaData) - { - const int size = 8; - var result = new EncounterSlot9[areaData.Length / size]; - for (int i = 0; i < result.Length; i++) - { - var slot = areaData[(i * size)..]; - var species = ReadUInt16LittleEndian(slot); - var form = slot[2]; - var gender = (sbyte)slot[3]; - - var min = slot[4]; - var max = slot[5]; - var time = slot[6]; - - result[i] = new EncounterSlot9(this, species, form, min, max, gender, time); - } - return result; - } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - var lvl = pk.Met_Level; - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - if (slot.Form != evo.Form && !slot.IsRandomUnspecificForm && !IsFormOkayWild(slot.Species, evo.Form)) - break; - if (slot.Gender != -1 && pk.Gender != slot.Gender) - break; - if (!slot.IsLevelWithinRange(lvl)) - break; - - if (pk is ITeraType t) - { - var orig = (byte)t.TeraTypeOriginal; - var pi = PersonalTable.SV[slot.Species, slot.Form]; - if (pi.Type1 != orig && pi.Type2 != orig) - break; - } - - yield return slot; - break; - } - } - } - - private static bool IsFormOkayWild(ushort species, byte form) => species switch - { - (int)Species.Rotom => form <= 5, - (int)Species.Deerling or (int)Species.Sawsbuck => form < 4, - (int)Species.Oricorio => form < 4, - _ => false, - }; -} diff --git a/PKHeX.Core/Legality/Bulk/CombinedReference.cs b/PKHeX.Core/Legality/Bulk/CombinedReference.cs index fb6daa30b..f4a7f6f16 100644 --- a/PKHeX.Core/Legality/Bulk/CombinedReference.cs +++ b/PKHeX.Core/Legality/Bulk/CombinedReference.cs @@ -1,3 +1,6 @@ namespace PKHeX.Core.Bulk; +/// +/// Tuple wrapper to store a legality analysis result and the slot it was generated from. +/// public sealed record CombinedReference(SlotCache Slot, LegalityAnalysis Analysis); diff --git a/PKHeX.Core/Legality/Encounters/Data/EncounterEvent.cs b/PKHeX.Core/Legality/Encounters/Data/EncounterEvent.cs index 7fc3834a9..cee58c976 100644 --- a/PKHeX.Core/Legality/Encounters/Data/EncounterEvent.cs +++ b/PKHeX.Core/Legality/Encounters/Data/EncounterEvent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using static PKHeX.Core.EncountersWC3; @@ -11,38 +12,69 @@ namespace PKHeX.Core; /// public static class EncounterEvent { + #region Pickle Data /// Event Database for Generation 3 - public static IReadOnlyList MGDB_G3 { get; private set; } = Array.Empty(); + public static WC3[] MGDB_G3 => Encounter_WC3; /// Event Database for Generation 4 - public static IReadOnlyList MGDB_G4 { get; private set; } = Array.Empty(); + public static readonly PCD[] MGDB_G4 = GetPCDDB(Util.GetBinaryResource("wc4.pkl")); /// Event Database for Generation 5 - public static IReadOnlyList MGDB_G5 { get; private set; } = Array.Empty(); + public static readonly PGF[] MGDB_G5 = GetPGFDB(Util.GetBinaryResource("pgf.pkl")); /// Event Database for Generation 6 - public static IReadOnlyList MGDB_G6 { get; private set; } = Array.Empty(); + public static readonly WC6[] MGDB_G6 = GetWC6DB(Util.GetBinaryResource("wc6.pkl"), Util.GetBinaryResource("wc6full.pkl")); /// Event Database for Generation 7 - public static IReadOnlyList MGDB_G7 { get; private set; } = Array.Empty(); + public static readonly WC7[] MGDB_G7 = GetWC7DB(Util.GetBinaryResource("wc7.pkl"), Util.GetBinaryResource("wc7full.pkl")); /// Event Database for Generation 7 - public static IReadOnlyList MGDB_G7GG { get; private set; } = Array.Empty(); + public static readonly WB7[] MGDB_G7GG = GetWB7DB(Util.GetBinaryResource("wb7full.pkl")); /// Event Database for Generation 8 - public static IReadOnlyList MGDB_G8 { get; private set; } = Array.Empty(); + public static readonly WC8[] MGDB_G8 = GetWC8DB(Util.GetBinaryResource("wc8.pkl")); /// Event Database for Generation 8 - public static IReadOnlyList MGDB_G8A { get; private set; } = Array.Empty(); + public static readonly WA8[] MGDB_G8A = GetWA8DB(Util.GetBinaryResource("wa8.pkl")); /// Event Database for Generation 8 - public static IReadOnlyList MGDB_G8B { get; private set; } = Array.Empty(); + public static readonly WB8[] MGDB_G8B = GetWB8DB(Util.GetBinaryResource("wb8.pkl")); /// Event Database for Generation 9 - public static IReadOnlyList MGDB_G9 { get; private set; } = Array.Empty(); + public static readonly WC9[] MGDB_G9 = GetWC9DB(Util.GetBinaryResource("wc9.pkl")); + #endregion + + #region Locally Loaded Data + /// Event Database for Generation 4 + public static PCD[] EGDB_G4 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 5 + public static PGF[] EGDB_G5 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 6 + public static WC6[] EGDB_G6 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 7 + public static WC7[] EGDB_G7 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 7 + public static WB7[] EGDB_G7GG { get; private set; } = Array.Empty(); + + /// Event Database for Generation 8 + public static WC8[] EGDB_G8 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 8 + public static WA8[] EGDB_G8A { get; private set; } = Array.Empty(); + + /// Event Database for Generation 8 + public static WB8[] EGDB_G8B { get; private set; } = Array.Empty(); + + /// Event Database for Generation 9 + public static WC9[] EGDB_G9 { get; private set; } = Array.Empty(); + #endregion /// Indicates if the databases are initialized. - public static bool Initialized => MGDB_G3.Count != 0; + public static bool Initialized => MGDB_G3.Length != 0; private static PCD[] GetPCDDB(ReadOnlySpan bin) => Get(bin, PCD.Size, static d => new PCD(d)); private static PGF[] GetPGFDB(ReadOnlySpan bin) => Get(bin, PGF.Size, static d => new PGF(d)); @@ -61,7 +93,7 @@ public static class EncounterEvent // bin is a multiple of size // bin.Length % size == 0 var result = new T[bin.Length / size]; - System.Diagnostics.Debug.Assert(result.Length * size == bin.Length); + Debug.Assert(result.Length * size == bin.Length); for (int i = 0; i < result.Length; i++) { var offset = i * size; @@ -72,79 +104,69 @@ public static class EncounterEvent } /// - /// Reloads the stored event templates. + /// Reloads the locally stored event templates. /// /// External folder(s) to source individual mystery gift template files from. public static void RefreshMGDB(params string[] paths) { - ICollection g4 = GetPCDDB(Util.GetBinaryResource("wc4.pkl")); - ICollection g5 = GetPGFDB(Util.GetBinaryResource("pgf.pkl")); - ICollection g6 = GetWC6DB(Util.GetBinaryResource("wc6.pkl"), Util.GetBinaryResource("wc6full.pkl")); - ICollection g7 = GetWC7DB(Util.GetBinaryResource("wc7.pkl"), Util.GetBinaryResource("wc7full.pkl")); - ICollection b7 = GetWB7DB(Util.GetBinaryResource("wb7full.pkl")); - ICollection g8 = GetWC8DB(Util.GetBinaryResource("wc8.pkl")); - ICollection b8 = GetWB8DB(Util.GetBinaryResource("wb8.pkl")); - ICollection a8 = GetWA8DB(Util.GetBinaryResource("wa8.pkl")); - ICollection g9 = GetWC9DB(Util.GetBinaryResource("wc9.pkl")); + HashSet? g4 = null; List? lg4 = null; + HashSet? g5 = null; List? lg5 = null; + HashSet? g6 = null; List? lg6 = null; + HashSet? g7 = null; List? lg7 = null; + HashSet? b7 = null; List? lb7 = null; + HashSet? g8 = null; List? lg8 = null; + HashSet? b8 = null; List? lb8 = null; + HashSet? a8 = null; List? la8 = null; + HashSet? g9 = null; List? lg9 = null; // Load external files // For each file, load the gift object into the appropriate list. - var gifts = GetGifts(paths); - foreach (var gift in gifts) - { - static void AddOrExpand(ref ICollection arr, T obj) - { - if (arr is HashSet h) - h.Add(obj); - else - arr = new HashSet(arr) {obj}; - } - switch (gift) - { - case PCD pcd: AddOrExpand(ref g4, pcd); continue; - case PGF pgf: AddOrExpand(ref g5, pgf); continue; - case WC6 wc6: AddOrExpand(ref g6, wc6); continue; - case WC7 wc7: AddOrExpand(ref g7, wc7); continue; - case WB7 wb7: AddOrExpand(ref b7, wb7); continue; - case WC8 wc8: AddOrExpand(ref g8, wc8); continue; - case WB8 wb8: AddOrExpand(ref b8, wb8); continue; - case WA8 wa8: AddOrExpand(ref a8, wa8); continue; - case WC9 wc9: AddOrExpand(ref g9, wc9); continue; - } - } - - static T[] SetArray(ICollection arr) - { - if (arr is T[] x) - return x; - - // rather than use Linq to build an array, just do it the quick way directly. - var result = new T[arr.Count]; - arr.CopyTo(result, 0); - return result; - } - - MGDB_G3 = Encounter_WC3; // hardcoded - MGDB_G4 = SetArray(g4); - MGDB_G5 = SetArray(g5); - MGDB_G6 = SetArray(g6); - MGDB_G7 = SetArray(g7); - MGDB_G7GG = SetArray(b7); - MGDB_G8 = SetArray(g8); - MGDB_G8A = SetArray(a8); - MGDB_G8B = SetArray(b8); - MGDB_G9 = SetArray(g9); - } - - private static IEnumerable GetGifts(IEnumerable paths) - { foreach (var path in paths) { if (!Directory.Exists(path)) continue; var gifts = MysteryUtil.GetGiftsFromFolder(path); foreach (var gift in gifts) - yield return gift; + { + var added = gift switch + { + PCD pcd => AddOrExpand(ref g4, ref lg4, pcd, MGDB_G4), + PGF pgf => AddOrExpand(ref g5, ref lg5, pgf, MGDB_G5), + WC6 wc6 => AddOrExpand(ref g6, ref lg6, wc6, MGDB_G6), + WC7 wc7 => AddOrExpand(ref g7, ref lg7, wc7, MGDB_G7), + WB7 wb7 => AddOrExpand(ref b7, ref lb7, wb7, MGDB_G7GG), + WC8 wc8 => AddOrExpand(ref g8, ref lg8, wc8, MGDB_G8), + WB8 wb8 => AddOrExpand(ref b8, ref lb8, wb8, MGDB_G8B), + WA8 wa8 => AddOrExpand(ref a8, ref la8, wa8, MGDB_G8A), + WC9 wc9 => AddOrExpand(ref g9, ref lg9, wc9, MGDB_G9), + _ => false, + }; + if (!added) + Trace.WriteLine($"Failed to add gift in {Path.GetDirectoryName(path)}: {gift.FileName}"); + + static bool AddOrExpand(ref HashSet? arr, ref List? extra, T obj, T[] master) + { + arr ??= new(master); + if (arr.Add(obj)) + (extra ??= new()).Add(obj); + return true; + } + } + EGDB_G4 = SetArray(lg4); + EGDB_G5 = SetArray(lg5); + EGDB_G6 = SetArray(lg6); + EGDB_G7 = SetArray(lg7); + EGDB_G7GG = SetArray(lb7); + EGDB_G8 = SetArray(lg8); + EGDB_G8A = SetArray(la8); + EGDB_G8B = SetArray(lb8); + EGDB_G9 = SetArray(lg9); + static T[] SetArray(List? arr) + { + if (arr is null) + return Array.Empty(); + return arr.ToArray(); + } } } @@ -156,15 +178,15 @@ public static class EncounterEvent { var regular = new IReadOnlyList[] { - MGDB_G4, - MGDB_G5, - MGDB_G6, - MGDB_G7, - MGDB_G7GG, - MGDB_G8, - MGDB_G8A, - MGDB_G8B, - MGDB_G9, + MGDB_G4, EGDB_G4, + MGDB_G5, EGDB_G5, + MGDB_G6, EGDB_G6, + MGDB_G7, EGDB_G7, + MGDB_G7GG, EGDB_G7GG, + MGDB_G8, EGDB_G8, + MGDB_G8A, EGDB_G8A, + MGDB_G8B, EGDB_G8B, + MGDB_G9, EGDB_G9, }.SelectMany(z => z); regular = regular.Where(mg => mg is { IsItem: false, IsEntity: true, Species: > 0 }); var result = MGDB_G3.Concat(regular); diff --git a/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs b/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs index 8c7a6b997..baff9767e 100644 --- a/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs +++ b/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs @@ -11,43 +11,6 @@ internal static class EncounterUtil internal static ReadOnlySpan Get(string resource) => Util.GetBinaryResource($"encounter_{resource}.pkl"); internal static BinLinkerAccessor Get(string resource, string ident) => BinLinkerAccessor.Get(Get(resource), ident); - /// - /// Gets the relevant objects that appear in the relevant game. - /// - /// Table of valid encounters that appear for the game pairing - /// Game to filter for - /// Array of encounter objects that can be encountered in the input game - internal static T[] GetEncounters(T[] source, GameVersion game) where T : IVersion - { - return Array.FindAll(source, s => s.Version.Contains(game)); - } - - /// - /// Gets the relevant objects that appear in the relevant game. - /// - /// Table of valid encounters that appear for the game pairing - /// Game to filter out - /// Array of encounter objects that can be encountered in the input game - internal static T[] GetEncounters(T[][] source, GameVersion exclude) where T : EncounterStatic - { - var count = 0; - foreach (T[] arr in source) - count += arr.Length; - - var temp = new T[count]; - count = 0; - foreach (var arr in source) - { - foreach (var enc in arr) - { - if (enc.Version != exclude) - temp[count++] = enc; - } - } - Array.Resize(ref temp, count); - return temp; - } - internal static T? GetMinByLevel(ReadOnlySpan chain, IEnumerable possible) where T : class, IEncounterTemplate { // MinBy grading: prefer species-form match, select lowest min level encounter. @@ -75,49 +38,13 @@ internal static class EncounterUtil return result; } - /// - /// Loads the language string lists into the objects. - /// - /// Encounter template type - /// Trade templates - /// Localization strings, grouped by language. - /// - /// The first half of strings in the language resource array are - /// The second half of strings in the language resource strings are - /// - internal static void MarkEncounterTradeStrings(T[] table, ReadOnlySpan strings) where T : EncounterTrade - { - uint languageCount = (uint)strings[1].Length / 2; - for (uint i = 0; i < languageCount; i++) - { - var t = table[i]; - t.Nicknames = GetNamesForLanguage(strings, i); - t.TrainerNames = GetNamesForLanguage(strings, languageCount + i); - } - } - - /// - /// Loads the language string lists into the objects. - /// - /// Encounter template type - /// Trade templates - /// Localization strings, grouped by language. - internal static void MarkEncounterTradeNicknames(T[] table, ReadOnlySpan strings) where T : EncounterTrade - { - for (uint i = 0; i < table.Length; i++) - { - var t = table[i]; - t.Nicknames = GetNamesForLanguage(strings, i); - } - } - /// /// Grabs the localized names for individual templates for all languages from the specified of the list. /// /// Arrays of strings grouped by language /// Index to grab from the language arrays /// Row of localized strings for the template. - private static string[] GetNamesForLanguage(ReadOnlySpan names, uint index) + public static string[] GetNamesForLanguage(ReadOnlySpan names, uint index) { var result = new string[names.Length]; for (int i = 0; i < result.Length; i++) diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1.cs b/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1.cs new file mode 100644 index 000000000..394e95d66 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1.cs @@ -0,0 +1,132 @@ +using static PKHeX.Core.GameVersion; +using static PKHeX.Core.EncounterUtil; + +namespace PKHeX.Core; + +/// +/// Generation 1 Encounters +/// +internal static class Encounters1 +{ + internal static readonly EncounterArea1[] SlotsRD = EncounterArea1.GetAreas(Get("red", "g1"), RD); + internal static readonly EncounterArea1[] SlotsGN = EncounterArea1.GetAreas(Get("blue", "g1"), GN); + internal static readonly EncounterArea1[] SlotsYW = EncounterArea1.GetAreas(Get("yellow", "g1"), YW); + internal static readonly EncounterArea1[] SlotsBU = EncounterArea1.GetAreas(Get("blue_jp", "g1"), BU); + + private const string tradeRBY = "traderby"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings7(tradeRBY); + + internal static readonly EncounterStatic1[] StaticRBY = + { + // GameVersion is RBY for Pokemon with the same catch rate and initial moves in all games + // If there are any differences in moves or catch rate, they will be defined as different encounters (GameVersion) + new(001, 05, RBY), // Bulbasaur + new(004, 05, RBY), // Charmander + new(007, 05, RBY), // Squirtle + + // Game Corner + new(035, 08, RBY), // Clefairy (Red Game Corner) + new(037, 18, RBY), // Vulpix (Yellow Game Corner) + new(040, 22, RBY), // Wigglytuff (Yellow Game Corner) + new(123, 25, RBY), // Scyther (Red Game Corner) + new(147, 18, RBY), // Dratini (Red Game Corner) + + // Lower level less ideal matches; best match is from above. + // new(035, 12), // Clefairy (Blue[EN] / Green[JP] Game Corner) + // new(063, 09), // Abra (Red Game Corner) + // new(063, 08), // Abra (Blue[JP] Game Corner) + // new(063, 15), // Abra (Yellow Game Corner) + // new(123, 30), // Scyther (Yellow Game Corner) + // new(137, 22), // Porygon (Blue[JP] Game Corner) + // new(137, 26), // Porygon (Red Game Corner) + // new(137, 26), // Porygon (Yellow Game Corner) + // new(147, 24), // Dratini (Blue[EN] / Green[JP] Game Corner) + + new(129, 05, RBY), // Magikarp + new(143, 30, RBY), // Snorlax + new(106, 30, RBY), // Hitmonlee + new(107, 30, RBY), // Hitmonchan + + new(131, 15, RBY), // Lapras + new(138, 30, RBY), // Omanyte + new(140, 30, RBY), // Kabuto + new(142, 30, RBY), // Aerodactyl + + new(144, 50, RBY), // Articuno + new(145, 50, RBY), // Zapdos + new(146, 50, RBY), // Moltres + + new(150, 70, RBY), // Mewtwo + + new(100, 40, RBY), // Voltorb (Power Plant) + new(101, 43, RBY), // Electrode (Power Plant) + + // Yellow Only -- duplicate encounters with a higher level + // new(001, 10, YW), // Bulbasaur (Cerulean City) + // new(004, 10, YW), // Charmander (Route 24) + // new(007, 10, YW), // Squirtle (Vermillion City) + }; + + internal static readonly EncounterStatic1[] StaticRB = + { + new(030, 17, RB), // Nidorina (Red Game Corner) + new(033, 17, RB), // Nidorino (Blue[EN] / Green[JP] Game Corner) + new(063, 06, RB), // Abra (Blue[EN] / Green[JP] Game Corner) + new(133, 25, RB), // Eevee + new(127, 20, RB), // Pinsir (Blue[EN] / Green[JP] Game Corner) + new(137, 18, RB), // Porygon (Blue[EN] / Green[JP] Game Corner) + }; + + internal static readonly EncounterStatic1[] StaticYW = + { + new(025, 05, YW), // Pikachu + new(127, 30, YW), // Pinsir (Yellow Game Corner) (Different initial moves) + new(133, 25, YW), // Eevee (Different initial moves) + }; + + internal static readonly EncounterStatic1[] StaticBU = + { + new(116, 18, BU), // Horsea (Blue[JP] Game Corner) + new(036, 24, BU), // Clefable (Blue[JP] Game Corner) + new(148, 30, BU), // Dragonair (Blue[JP] Game Corner) + new(025, 12, BU), // Pikachu (Blue[JP] Game Corner) (Different catch rate) + }; + + internal static readonly EncounterTrade1[] TradeGift_RB = + { + new(TradeNames, 00, 122, RB, 06, 05), // Mr. Mime - Abra + new(TradeNames, 01, 032, RB, 02 ), // Nidoran♂ - Nidoran♀ + new(TradeNames, 02, 030, RB, 16 ), // Nidorina - Nidorino + new(TradeNames, 03, 108, RB, 15 ), // Lickitung - Slowbro + new(TradeNames, 04, 124, RB, 15, 10), // Jynx - Poliwhirl + new(TradeNames, 05, 083, RB, 02 ), // Farfetch’d - Spearow + new(TradeNames, 06, 101, RB, 03 ), // Electrode - Raichu + new(TradeNames, 07, 114, RB, 13, 05), // Tangela - Venonat + new(TradeNames, 08, 086, RB, 28, 05), // Seel - Ponyta + }; + + public static readonly EncounterTrade1[] TradeGift_YW = + { + new(TradeNames, 09, 122, YW, 08, 06), // Mr. Mime - Clefairy + new(TradeNames, 10, 067, YW, 16, 05) { EvolveOnTrade = true }, // Machoke - Cubone + new(TradeNames, 11, 051, YW, 15, 05), // Dugtrio - Lickitung + new(TradeNames, 12, 047, YW, 13, 05), // Parasect - Tangel + new(TradeNames, 13, 112, YW, 15, 10), // Rhydon - Golduck + new(TradeNames, 14, 087, YW, 15, 05), // Dewgong - Growlithe + new(TradeNames, 15, 089, YW, 25, 05), // Muk - Kangaskhan + }; + + public static readonly EncounterTrade1[] TradeGift_BU = + { + new(TradeNames, 16, 122, BU, 03 ), // Mr. Mime - Jigglypuff + new(TradeNames, 17, 029, BU, 02 ), // Nidoran♀ - Nidoran♂ + new(TradeNames, 18, 060, BU, 02 ), // Poliwag - Rattata + new(TradeNames, 19, 115, BU, 15, 10), // Kangaskhan - Rhydon + new(TradeNames, 20, 128, BU, 28, 18), // Tauros - Persian + new(TradeNames, 21, 093, BU, 28, 14) { EvolveOnTrade = true }, // Haunter - Machop->Machoke + new(TradeNames, 22, 083, BU, 02 ), // Farfetch’d - Wild Pidgey + new(TradeNames, 23, 075, BU, 16, 15) { EvolveOnTrade = true }, // Graveler - Abra->Kadabra + new(TradeNames, 24, 079, BU, 22, 05), // Slowpoke - Seel + new(TradeNames, 25, 098, BU, 15, 05), // Krabby - Growlithe + }; +} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1GBEra.cs b/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1GBEra.cs new file mode 100644 index 000000000..dabcae787 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1GBEra.cs @@ -0,0 +1,38 @@ +namespace PKHeX.Core; + +internal static class Encounters1GBEra +{ + private static readonly IndividualValueSet Yoshira = new(5, 10, 1, 12, 5, 5); + private static readonly string[] YoshiOT = { "YOSHIRA", "YOSHIRB", "YOSHIBA", "YOSHIBB" }; + private static readonly string[] TourOT = { "LINKE", "LINKW", "LUIGE", "LUIGW", "LUIGIC", "YOSHIC" }; + private static readonly string[] StadiumOT_Int = { "STADIUM", "STADE", "STADIO", "ESTADIO" }; + private const string StadiumOT_JPN = "スタジアム"; + + internal static readonly EncounterGift1[] Gifts = + { + // Stadium 1 (International) + new(001, 05, GameVersion.Stadium) {Moves = new(033, 045), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Bulbasaur + new(004, 05, GameVersion.Stadium) {Moves = new(010, 043), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Charmander + new(007, 05, GameVersion.Stadium) {Moves = new(033, 045), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Squirtle + new(106, 20, GameVersion.Stadium) {Moves = new(024, 096), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Hitmonlee + new(107, 20, GameVersion.Stadium) {Moves = new(004, 097), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Hitmonchan + new(133, 25, GameVersion.Stadium) {Moves = new(033, 039), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Eevee + new(138, 20, GameVersion.Stadium) {Moves = new(055, 110), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Omanyte + new(140, 20, GameVersion.Stadium) {Moves = new(010, 106), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Kabuto + new(054, 15, GameVersion.Stadium) {Moves = new(133, 010), TID16 = 2000, OT_Names = StadiumOT_Int, Language = EncounterGBLanguage.International}, // Psyduck (Amnesia) + + // Stadium 2 (Japan) + new(001, 05, GameVersion.Stadium) {Moves = new(033, 045), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Bulbasaur + new(004, 05, GameVersion.Stadium) {Moves = new(010, 043), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Charmander + new(007, 05, GameVersion.Stadium) {Moves = new(033, 045), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Squirtle + new(106, 20, GameVersion.Stadium) {Moves = new(024, 096), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Hitmonlee + new(107, 20, GameVersion.Stadium) {Moves = new(004, 097), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Hitmonchan + new(133, 25, GameVersion.Stadium) {Moves = new(033, 039), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Eevee + new(138, 20, GameVersion.Stadium) {Moves = new(055, 110), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Omanyte + new(140, 20, GameVersion.Stadium) {Moves = new(010, 106), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Kabuto + new(054, 15, GameVersion.Stadium) {Moves = new(133, 010), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Psyduck (Amnesia) + + new(151, 5, GameVersion.RB) {IVs = Yoshira, OT_Names = YoshiOT, Language = EncounterGBLanguage.International }, // Yoshira Mew Events + new(151, 5, GameVersion.RB) {IVs = Yoshira, OT_Names = TourOT, Language = EncounterGBLanguage.International }, // Pokémon 2000 Stadium Tour Mew + }; +} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1VC.cs b/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1VC.cs new file mode 100644 index 000000000..5cf613bbb --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Data/Gen1/Encounters1VC.cs @@ -0,0 +1,13 @@ +namespace PKHeX.Core; + +internal static class Encounters1VC +{ + private static readonly IndividualValueSet Flawless15 = new(15, 15, 15, 15, 15, 15); + + internal static readonly EncounterGift1[] Gifts = + { + // Event Mew + new(151, 5, GameVersion.RBY) { IVs = Flawless15, TID16 = 22796, OT_Name = "GF", Language = EncounterGBLanguage.International }, + new(151, 5, GameVersion.RBY) { IVs = Flawless15, TID16 = 22796, OT_Name = "ゲーフリ" }, + }; +} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen12/Encounters1.cs b/PKHeX.Core/Legality/Encounters/Data/Gen12/Encounters1.cs deleted file mode 100644 index e0d798592..000000000 --- a/PKHeX.Core/Legality/Encounters/Data/Gen12/Encounters1.cs +++ /dev/null @@ -1,162 +0,0 @@ -using static PKHeX.Core.GameVersion; -using static PKHeX.Core.EncounterGBLanguage; -using static PKHeX.Core.EncounterUtil; - -namespace PKHeX.Core; - -/// -/// Generation 1 Encounters -/// -internal static class Encounters1 -{ - internal static readonly EncounterArea1[] SlotsRD = EncounterArea1.GetAreas(Get("red", "g1"), RD); - internal static readonly EncounterArea1[] SlotsGN = EncounterArea1.GetAreas(Get("blue", "g1"), GN); - internal static readonly EncounterArea1[] SlotsYW = EncounterArea1.GetAreas(Get("yellow", "g1"), YW); - internal static readonly EncounterArea1[] SlotsBU = EncounterArea1.GetAreas(Get("blue_jp", "g1"), BU); - internal static readonly EncounterArea1[] SlotsRBY = ArrayUtil.ConcatAll(SlotsRD, SlotsGN, SlotsYW); - internal static readonly EncounterArea1[] SlotsRGBY = ArrayUtil.ConcatAll(SlotsRBY, SlotsBU); - - static Encounters1() => MarkEncounterTradeNicknames(TradeGift_RBY, TradeGift_RBY_OTs); - - internal static readonly EncounterStatic1[] StaticRBY = - { - // GameVersion is RBY for Pokemon with the same catch rate and initial moves in all games - // If there are any differences in moves or catch rate, they will be defined as different encounters (GameVersion) - new(001, 05, RBY), // Bulbasaur - new(004, 05, RBY), // Charmander - new(007, 05, RBY), // Squirtle - new(025, 05, YW), // Pikachu - - // Game Corner - new(030, 17, RB), // Nidorina (Red Game Corner) - new(033, 17, RB), // Nidorino (Blue[EN] / Green[JP] Game Corner) - new(035, 08, RBY), // Clefairy (Red Game Corner) - new(036, 24, BU), // Clefable (Blue[JP] Game Corner) - new(037, 18, RBY), // Vulpix (Yellow Game Corner) - new(040, 22, RBY), // Wigglytuff (Yellow Game Corner) - new(063, 06, RB), // Abra (Blue[EN] / Green[JP] Game Corner) - new(116, 18, BU), // Horsea (Blue[JP] Game Corner) - new(123, 25, RBY), // Scyther (Red Game Corner) - new(127, 20, RB), // Pinsir (Blue[EN] / Green[JP] Game Corner) - new(127, 30, YW), // Pinsir (Yellow Game Corner) (Different initial moves) - new(137, 18, RB), // Porygon (Blue[EN] / Green[JP] Game Corner) - new(147, 18, RBY), // Dratini (Red Game Corner) - new(148, 30, BU), // Dragonair (Blue[JP] Game Corner) - new(025, 12, BU), // Pikachu (Blue[JP] Game Corner) (Different catch rate) - - // Lower level less ideal matches; best match is from above. - // new(035, 12), // Clefairy (Blue[EN] / Green[JP] Game Corner) - // new(063, 09), // Abra (Red Game Corner) - // new(063, 08), // Abra (Blue[JP] Game Corner) - // new(063, 15), // Abra (Yellow Game Corner) - // new(123, 30), // Scyther (Yellow Game Corner) - // new(137, 22), // Porygon (Blue[JP] Game Corner) - // new(137, 26), // Porygon (Red Game Corner) - // new(137, 26), // Porygon (Yellow Game Corner) - // new(147, 24), // Dratini (Blue[EN] / Green[JP] Game Corner) - - new(129, 05, RBY), // Magikarp - new(143, 30, RBY), // Snorlax - new(106, 30, RBY), // Hitmonlee - new(107, 30, RBY), // Hitmonchan - - new(131, 15, RBY), // Lapras - new(138, 30, RBY), // Omanyte - new(140, 30, RBY), // Kabuto - new(142, 30, RBY), // Aerodactyl - - new(144, 50, RBY), // Articuno - new(145, 50, RBY), // Zapdos - new(146, 50, RBY), // Moltres - - new(150, 70, RBY), // Mewtwo - - new(133, 25, RB) {Moves = new((int)Move.Tackle, (int)Move.SandAttack)}, // Eevee - new(133, 25, YW) {Moves = new((int)Move.TailWhip, (int)Move.SandAttack, (int)Move.Growl, (int)Move.QuickAttack)}, // Eevee (Different initial moves) - - new(100, 40, RBY), // Voltorb (Power Plant) - new(101, 43, RBY), // Electrode (Power Plant) - - // Yellow Only -- duplicate encounters with a higher level - // new(001, 10, YW), // Bulbasaur (Cerulean City) - // new(004, 10, YW), // Charmander (Route 24) - // new(007, 10, YW), // Squirtle (Vermillion City) - }; - - internal static readonly EncounterTrade1[] TradeGift_RBY = - { - new(122, RB, 06, 05), // Mr. Mime - Abra - new(032, RB, 02 ), // Nidoran♂ - Nidoran♀ - new(030, RB, 16 ), // Nidorina - Nidorino - new(108, RB, 15 ), // Lickitung - Slowbro - new(124, RB, 15, 10), // Jynx - Poliwhirl - new(083, RB, 02 ), // Farfetch’d - Spearow - new(101, RB, 03 ), // Electrode - Raichu - new(114, RB, 13, 05), // Tangela - Venonat - new(086, RB, 28, 05), // Seel - Ponyta - - new(122, YW, 08, 06), // Mr. Mime - Clefairy - new(067, YW, 16, 05) { EvolveOnTrade = true }, // Machoke - Cubone - new(051, YW, 15, 05), // Dugtrio - Lickitung - new(047, YW, 13, 05), // Parasect - Tangel - new(112, YW, 15, 10), // Rhydon - Golduck - new(087, YW, 15, 05), // Dewgong - Growlithe - new(089, YW, 25, 05), // Muk - Kangaskhan - - new(122, BU, 03 ), // Mr. Mime - Jigglypuff - new(029, BU, 02 ), // Nidoran♀ - Nidoran♂ - new(060, BU, 02 ), // Poliwag - Rattata - new(115, BU, 15, 10), // Kangaskhan - Rhydon - new(128, BU, 28, 18), // Tauros - Persian - new(093, BU, 28, 14) { EvolveOnTrade = true }, // Haunter - Machop->Machoke - new(083, BU, 02 ), // Farfetch’d - Wild Pidgey - new(075, BU, 16, 15) { EvolveOnTrade = true }, // Graveler - Abra->Kadabra - new(079, BU, 22, 05), // Slowpoke - Seel - new(098, BU, 15, 05), // Krabby - Growlithe - }; - - private const string tradeRBY = "traderby"; - private static readonly string[][] TradeGift_RBY_OTs = Util.GetLanguageStrings7(tradeRBY); - - private static readonly IndividualValueSet Flawless15 = new(15, 15, 15, 15, 15, 15); - private static readonly IndividualValueSet Yoshira = new(5, 10, 1, 12, 5, 5); - private static readonly string[] YoshiOT = { "YOSHIRA", "YOSHIRB", "YOSHIBA", "YOSHIBB" }; - private static readonly string[] TourOT = { "LINKE", "LINKW", "LUIGE", "LUIGW", "LUIGIC", "YOSHIC" }; - private static readonly string[] StadiumOT_Int = { "STADIUM", "STADE", "STADIO", "ESTADIO" }; - private const string StadiumOT_JPN = "スタジアム"; - - internal static readonly EncounterStatic1E[] StaticEventsVC = - { - // Event Mew - new(151, 5, RBY) { IVs = Flawless15, TID16 = 22796, OT_Name = "GF", Language = International }, - new(151, 5, RBY) { IVs = Flawless15, TID16 = 22796, OT_Name = "ゲーフリ" }, - }; - - internal static readonly EncounterStatic1E[] StaticEventsGB = - { - // Stadium 1 (International) - new(001, 05, Stadium) {Moves = new(033, 045), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Bulbasaur - new(004, 05, Stadium) {Moves = new(010, 043), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Charmander - new(007, 05, Stadium) {Moves = new(033, 045), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Squirtle - new(106, 20, Stadium) {Moves = new(024, 096), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Hitmonlee - new(107, 20, Stadium) {Moves = new(004, 097), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Hitmonchan - new(133, 25, Stadium) {Moves = new(033, 039), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Eevee - new(138, 20, Stadium) {Moves = new(055, 110), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Omanyte - new(140, 20, Stadium) {Moves = new(010, 106), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Kabuto - new(054, 15, Stadium) {Moves = new(133, 010), TID16 = 2000, OT_Names = StadiumOT_Int, Language = International}, // Psyduck (Amnesia) - - // Stadium 2 (Japan) - new(001, 05, Stadium) {Moves = new(033, 045), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Bulbasaur - new(004, 05, Stadium) {Moves = new(010, 043), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Charmander - new(007, 05, Stadium) {Moves = new(033, 045), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Squirtle - new(106, 20, Stadium) {Moves = new(024, 096), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Hitmonlee - new(107, 20, Stadium) {Moves = new(004, 097), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Hitmonchan - new(133, 25, Stadium) {Moves = new(033, 039), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Eevee - new(138, 20, Stadium) {Moves = new(055, 110), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Omanyte - new(140, 20, Stadium) {Moves = new(010, 106), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Kabuto - new(054, 15, Stadium) {Moves = new(133, 010), TID16 = 1999, OT_Name = StadiumOT_JPN}, // Psyduck (Amnesia) - - new(151, 5, RB) {IVs = Yoshira, OT_Names = YoshiOT, Language = International }, // Yoshira Mew Events - new(151, 5, RB) {IVs = Yoshira, OT_Names = TourOT, Language = International }, // Pokémon 2000 Stadium Tour Mew - }; -} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen12/Encounters2.cs b/PKHeX.Core/Legality/Encounters/Data/Gen12/Encounters2.cs deleted file mode 100644 index 55a98fd66..000000000 --- a/PKHeX.Core/Legality/Encounters/Data/Gen12/Encounters2.cs +++ /dev/null @@ -1,382 +0,0 @@ -using System; -using static PKHeX.Core.EncounterUtil; -using static PKHeX.Core.GameVersion; -using static PKHeX.Core.EncounterGBLanguage; - -namespace PKHeX.Core; - -/// -/// Generation 2 Encounters -/// -internal static class Encounters2 -{ - internal static readonly EncounterArea2[] SlotsGD = EncounterArea2.GetAreas(Get("gold", "g2"), GD); - internal static readonly EncounterArea2[] SlotsSV = EncounterArea2.GetAreas(Get("silver", "g2"), SI); - internal static readonly EncounterArea2[] SlotsC = EncounterArea2.GetAreas(Get("crystal", "g2"), C); - - internal static readonly EncounterArea2[] SlotsGS = ArrayUtil.ConcatAll(SlotsGD, SlotsSV); - internal static readonly EncounterArea2[] SlotsGSC = ArrayUtil.ConcatAll(SlotsGS, SlotsC); - - static Encounters2() => MarkEncounterTradeStrings(TradeGift_GSC, TradeGift_GSC_OTs); - - private static readonly EncounterStatic2[] Encounter_GSC_Common = - { - new(152, 05, GSC) { Location = 001 }, // Chikorita @ New Bark Town - new(155, 05, GSC) { Location = 001 }, // Cyndaquil @ New Bark Town - new(158, 05, GSC) { Location = 001 }, // Totodile @ New Bark Town - - new(175, 05, GSC) { EggLocation = 256 }, // Togepi - new(131, 20, GSC) { Location = 010 }, // Lapras @ Union Cave - new(133, 20, GSC) { Location = 016 }, // Eevee @ Goldenrod City - - new(185, 20, GSC) { Location = 020 }, // Sudowoodo @ Route 36 - new(236, 10, GSC) { Location = 035 }, // Tyrogue @ Mt. Mortar - - new(130, 30, GSC) { Location = 038, Shiny = Shiny.Always, Gender = 0, IVs = new(0, 14, 10, 10, 10, 10) }, // Gyarados @ Lake of Rage (forcing shiny IVs result in always Male) - new(074, 21, GSC) { Location = 036 }, // Geodude @ Rocket Hideout (Mahogany Town) - new(109, 21, GSC) { Location = 036 }, // Koffing @ Rocket Hideout (Mahogany Town) - new(100, 23, GSC) { Location = 036 }, // Voltorb @ Rocket Hideout (Mahogany Town) - new(101, 23, GSC) { Location = 036 }, // Electrode @ Rocket Hideout (Mahogany Town) - new(143, 50, GSC) { Location = 061 }, // Snorlax @ Vermillion City - - new(211, 05, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Old Rod) - new(211, 20, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Good Rod) - new(211, 40, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Super Rod) - }; - - private static readonly EncounterStatic2[] Encounter_GS_Exclusive = - { - new(245, 40, GS), // Suicune - - new(249, 70, GD), // Lugia @ Whirl Islands - new(249, 40, SI), // Lugia @ Whirl Islands - - new(250, 40, GD), // Ho-Oh @ Tin Tower - new(250, 70, SI), // Ho-Oh @ Tin Tower - - new(137, 15, GS), // Porygon @ Celadon Game Corner - new(133, 15, GS), // Eevee @ Celadon Game Corner - new(122, 15, GS), // Mr. Mime @ Celadon Game Corner - - new(063, 10, GS), // Abra @ Goldenrod City (Game Corner) - new(147, 10, GS), // Dratini @ Goldenrod City (Game Corner) - new(023, 10, GS), // Ekans @ Goldenrod City (Game Corner) (Gold) - new(027, 10, GS), // Sandshrew @ Goldenrod City (Game Corner) (Silver) - - new(223, 05, GS), // Remoraid Swarm @ Route 44 (Old Rod) - new(223, 20, GS), // Remoraid Swarm @ Route 44 (Good Rod) - new(223, 40, GS), // Remoraid Swarm @ Route 44 (Super Rod) - }; - - private static readonly EncounterStatic2[] Encounter_C_Exclusive = - { - new(245, 40, C) { Location = 023 }, // Suicune @ Tin Tower - - new EncounterStatic2Odd(172) {Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Pichu - new EncounterStatic2Odd(173) {Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Cleffa - new EncounterStatic2Odd(174) {Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Igglybuff - new EncounterStatic2Odd(236) {Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Tyrogue - new EncounterStatic2Odd(238) {Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Smoochum - new EncounterStatic2Odd(239) {Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Elekid - new EncounterStatic2Odd(240) {Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Magby - - new(147, 15, C) { Location = 042, Moves = new((int)Move.ExtremeSpeed, (int)Move.Wrap, (int)Move.ThunderWave, (int)Move.Twister) }, // Dratini ExtremeSpeed - - new(249, 60, C) { Location = 031 }, // Lugia @ Whirl Islands - new(250, 60, C) { Location = 023 }, // Ho-Oh @ Tin Tower - - new(137, 15, C) { Location = 071 }, // Porygon @ Celadon Game Corner - new(025, 25, C) { Location = 071 }, // Pikachu @ Celadon Game Corner - new(246, 40, C) { Location = 071 }, // Larvitar @ Celadon Game Corner - - new(063, 05, C) { Location = 016 }, // Abra @ Goldenrod City (Game Corner) - new(104, 15, C) { Location = 016 }, // Cubone @ Goldenrod City (Game Corner) - new(202, 15, C) { Location = 016 }, // Wobbuffet @ Goldenrod City (Game Corner) - }; - - private static readonly EncounterStatic2[] Encounter_GSC_Roam = - { - new EncounterStatic2Roam(243, 40, GSC), // Raikou - new EncounterStatic2Roam(244, 40, GSC), // Entei - new EncounterStatic2Roam(245, 40, GS), // Suicune - }; - - private static readonly EncounterStatic2[] Encounter_GS = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_GS_Exclusive, Encounter_GSC_Roam); - private static readonly EncounterStatic2[] Encounter_C = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_C_Exclusive, Encounter_GSC_Roam.AsSpan(0, 2)); - private static readonly EncounterStatic2[] Encounter_GSC = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_GS_Exclusive, Encounter_C_Exclusive, Encounter_GSC_Roam); - - internal static readonly EncounterTrade2[] TradeGift_GSC = - { - new(095, 03, 48926) { Gender = 0, IVs = new(08, 09, 06, 06, 06, 06) }, // Onix @ Violet City for Bellsprout [wild] - new(066, 05, 37460) { Gender = 1, IVs = new(12, 03, 07, 06, 06, 06) }, // Machop @ Goldenrod City for Drowzee [wild 9, hatched egg 5] - new(100, 05, 29189) { Gender = 2, IVs = new(08, 09, 08, 08, 08, 08) }, // Voltorb @ Olivine City for Krabby [egg] - new(112, 10, 00283) { Gender = 1, IVs = new(12, 07, 07, 06, 06, 06) }, // Rhydon @ Blackthorn City for Dragonair [wild] - new(142, 05, 26491) { Gender = 0, IVs = new(08, 09, 06, 06, 06, 06), OTGender = 1}, // Aerodactyl @ Route 14 for Chansey [egg] - new(078, 14, 15616) { Gender = 0, IVs = new(08, 09, 06, 06, 06, 06) }, // Rapidash @ Pewter City for Gloom [wild] - - new(085, 10, 00283) { Gender = 1, IVs = new(12, 07, 07, 06, 06, 06), OTGender = 1}, // Dodrio @ Blackthorn City for Dragonair [wild] - new(178, 15, 15616) { Gender = 0, IVs = new(08, 09, 06, 08, 06, 06) }, // Xatu @ Pewter City for Haunter [wild] - new(082, 05, 50082) { Gender = 2, IVs = new(08, 09, 06, 06, 06, 06) }, // Magneton @ Power Plant for Dugtrio [traded for Lickitung] - - new(021, 10, 01001) { Moves = new(64,45,43) }, // Spearow @ Goldenrod City for free - new(213, 15, 00518), // Shuckle @ Cianwood City for free - }; - - private const string tradeGSC = "tradegsc"; - private static readonly string[][] TradeGift_GSC_OTs = Util.GetLanguageStrings8(tradeGSC); - - internal static readonly EncounterStatic2[] StaticGSC = Encounter_GSC; - internal static readonly EncounterStatic2[] StaticGS = Encounter_GS; - internal static readonly EncounterStatic2[] StaticC = Encounter_C; - - internal static readonly EncounterStatic2E[] StaticEventsVC = - { - new(251, 30, C) { Location = 014, Language = EncounterGBLanguage.Any }, // Celebi @ Ilex Forest (VC) - }; - - private static readonly string[] PCNYx = {"PCNYa", "PCNYb", "PCNYc", "PCNYd"}; - - internal static readonly EncounterStatic2E[] StaticEventsGB = - { - // Stadium 2 Baton Pass Farfetch'd - new(083, 05, C) {Moves = new(226, 14, 97, 163), Location = 127, TID16 = 2000, OT_Name = "スタジアム"}, - new(083, 05, C) {Moves = new(226, 14, 97, 163), Location = 127, TID16 = 2000, OT_Name = "Stadium", Language = International}, - new(083, 05, C) {Moves = new(226, 14, 97, 163), Location = 127, TID16 = 2001, OT_Names = new[]{"Stade", "Stadion", "Stadio", "Estadio"}, Language = International}, - - // Stadium 2 Earthquake Gligar - new(207, 05, C) {Moves = new(89, 68, 17), Location = 127, TID16 = 2000, OT_Name = "スタジアム"}, - new(207, 05, C) {Moves = new(89, 68, 17), Location = 127, TID16 = 2000, OT_Name = "Stadium", Language = International}, - new(207, 05, C) {Moves = new(89, 68, 17), Location = 127, TID16 = 2001, OT_Names = new[]{"Stade", "Stadion", "Stadio", "Estadio"}, Language = International}, - - //New York Pokémon Center Events - - // Legendary Beasts (November 22 to 29, 2001) - new(243, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Raikou - new(244, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Entei - new(245, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Suicune - - // Legendary Birds (November 30 to December 6, 2001) - new(144, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Articuno - new(145, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Zapdos - new(146, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Moltres - - // Christmas Week (December 21 to 27, 2001) - new(225, 05, GS) {OT_Names = PCNYx, Moves = new(006), EggLocation = 256, EggCycles = 10, Language = International}, // Pay Day Delibird - new(251, 05, C) {OT_Names = PCNYx, Location = 127, Language = International}, // Celebi - - // The Initial Three Set (December 28, 2001 to January 31, 2002) - new(001, 05, GS) {OT_Names = PCNYx, Moves = new(246), EggLocation = 256, EggCycles = 10, Language = International}, // Bulbasaur Ancientpower - new(004, 05, GS) {OT_Names = PCNYx, Moves = new(242), EggLocation = 256, EggCycles = 10, Language = International}, // Charmander Crunch - new(007, 05, GS) {OT_Names = PCNYx, Moves = new(192), EggLocation = 256, EggCycles = 10, Language = International}, // Squirtle Zap Cannon - new(152, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggLocation = 256, EggCycles = 10, Language = International}, // Chikorita Petal Dance - new(155, 05, GS) {OT_Names = PCNYx, Moves = new(038), EggLocation = 256, EggCycles = 10, Language = International}, // Cyndaquil Double-Edge - new(158, 05, GS) {OT_Names = PCNYx, Moves = new(066), EggLocation = 256, EggCycles = 10, Language = International}, // Totodile Submission - - // Valentine Week (February 1 to 14, 2002) - new(029, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Lovely Kiss - new(029, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Sweet Kiss - new(032, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Lovely Kiss - new(032, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Sweet Kiss - new(069, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggLocation = 256, EggCycles = 10, Language = International}, // Bellsprout Lovely Kiss - new(069, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggLocation = 256, EggCycles = 10, Language = International}, // Bellsprout Sweet Kiss - - // Swarm Week (February 22 to March 14, 2002) - new(183, 05, GS) {OT_Names = PCNYx, Moves = new(056), EggLocation = 256, EggCycles = 10, Language = International}, // Marill Hydro Pump - new(193, 05, GS) {OT_Names = PCNYx, Moves = new(211), EggLocation = 256, EggCycles = 10, Language = International}, // Yanma Steel Wing - new(206, 05, GS) {OT_Names = PCNYx, Moves = new(032), EggLocation = 256, EggCycles = 10, Language = International}, // Dunsparce Horn Drill - new(209, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggLocation = 256, EggCycles = 10, Language = International}, // Snubbull Lovely Kiss - new(211, 05, GS) {OT_Names = PCNYx, Moves = new(038), EggLocation = 256, EggCycles = 10, Language = International}, // Qwilfish Double-Edge - new(223, 05, GS) {OT_Names = PCNYx, Moves = new(133), EggLocation = 256, EggCycles = 10, Language = International}, // Remoraid Amnesia - - // Shiny RBY Starters (March 15 to 21, 2002) - new(003, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Venusaur - new(006, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Charizard - new(009, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Blastoise - - // Babies Week (March 22 to April 11, 2002) - new(172, 05, GS) {OT_Names = PCNYx, Moves = new(047), EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Sing - new(173, 05, GS) {OT_Names = PCNYx, Moves = new(129), EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Swift - new(174, 05, GS) {OT_Names = PCNYx, Moves = new(102), EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Mimic - new(238, 05, GS) {OT_Names = PCNYx, Moves = new(118), EggLocation = 256, EggCycles = 10, Language = International}, // Smoochum Metronome - new(239, 05, GS) {OT_Names = PCNYx, Moves = new(228), EggLocation = 256, EggCycles = 10, Language = International}, // Elekid Pursuit - new(240, 05, GS) {OT_Names = PCNYx, Moves = new(185), EggLocation = 256, EggCycles = 10, Language = International}, // Magby Faint Attack - - // Spring Into Spring (April 12 to May 4, 2002) - new(054, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggLocation = 256, EggCycles = 10, Language = International}, // Psyduck Petal Dance - new(152, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggLocation = 256, EggCycles = 10, Language = International}, // Chikorita Petal Dance - new(172, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Petal Dance - new(173, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Petal Dance - new(174, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Petal Dance - new(238, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggLocation = 256, EggCycles = 10, Language = International}, // Smoochum Petal Dance - - // Baby Weeks (May 5 to June 7, 2002) - new(194, 05, GS) {Moves = new(187), EggLocation = 256, EggCycles = 10, Language = International}, // Wooper Belly Drum - - // Tropical Promotion to Summer Festival 1 (June 8 to 21, 2002) - new(060, 05, GS) {OT_Names = PCNYx, Moves = new(074), EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Growth - new(116, 05, GS) {OT_Names = PCNYx, Moves = new(114), EggLocation = 256, EggCycles = 10, Language = International}, // Horsea Haze - new(118, 05, GS) {OT_Names = PCNYx, Moves = new(014), EggLocation = 256, EggCycles = 10, Language = International}, // Goldeen Swords Dance - new(129, 05, GS) {OT_Names = PCNYx, Moves = new(179), EggLocation = 256, EggCycles = 10, Language = International}, // Magikarp Reversal - new(183, 05, GS) {OT_Names = PCNYx, Moves = new(146), EggLocation = 256, EggCycles = 10, Language = International}, // Marill Dizzy Punch - - // Tropical Promotion to Summer Festival 2 (July 12 to August 8, 2002) - new(054, 05, GS) {OT_Names = PCNYx, Moves = new(161), EggLocation = 256, EggCycles = 10, Language = International}, // Psyduck Tri Attack - new(072, 05, GS) {OT_Names = PCNYx, Moves = new(109), EggLocation = 256, EggCycles = 10, Language = International}, // Tentacool Confuse Ray - new(131, 05, GS) {OT_Names = PCNYx, Moves = new(044), EggLocation = 256, EggCycles = 10, Language = International}, // Lapras Bite - new(170, 05, GS) {OT_Names = PCNYx, Moves = new(113), EggLocation = 256, EggCycles = 10, Language = International}, // Chinchou Light Screen - new(223, 05, GS) {OT_Names = PCNYx, Moves = new(054), EggLocation = 256, EggCycles = 10, Language = International}, // Remoraid Mist - new(226, 05, GS) {OT_Names = PCNYx, Moves = new(016), EggLocation = 256, EggCycles = 10, Language = International}, // Mantine Gust - - // Safari Week (August 9 to 29, 2002) - new(029, 05, GS) {OT_Names = PCNYx, Moves = new(236), EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Moonlight - new(032, 05, GS) {OT_Names = PCNYx, Moves = new(234), EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Morning Sun - new(113, 05, GS) {OT_Names = PCNYx, Moves = new(230), EggLocation = 256, EggCycles = 10, Language = International}, // Chansey Sweet Scent - new(115, 05, GS) {OT_Names = PCNYx, Moves = new(185), EggLocation = 256, EggCycles = 10, Language = International}, // Kangaskhan Faint Attack - new(128, 05, GS) {OT_Names = PCNYx, Moves = new(098), EggLocation = 256, EggCycles = 10, Language = International}, // Tauros Quick Attack - new(147, 05, GS) {OT_Names = PCNYx, Moves = new(056), EggLocation = 256, EggCycles = 10, Language = International}, // Dratini Hydro Pump - - // Sky Week (August 30 to September 26, 2002) - new(021, 05, GS) {OT_Names = PCNYx, Moves = new(049), EggLocation = 256, EggCycles = 10, Language = International}, // Spearow SonicBoom - new(083, 05, GS) {OT_Names = PCNYx, Moves = new(210), EggLocation = 256, EggCycles = 10, Language = International}, // Farfetch'd Fury Cutter - new(084, 05, GS) {OT_Names = PCNYx, Moves = new(067), EggLocation = 256, EggCycles = 10, Language = International}, // Doduo Low Kick - new(177, 05, GS) {OT_Names = PCNYx, Moves = new(219), EggLocation = 256, EggCycles = 10, Language = International}, // Natu Safeguard - new(198, 05, GS) {OT_Names = PCNYx, Moves = new(251), EggLocation = 256, EggCycles = 10, Language = International}, // Murkrow Beat Up - new(227, 05, GS) {OT_Names = PCNYx, Moves = new(210), EggLocation = 256, EggCycles = 10, Language = International}, // Skarmory Fury Cutter - - // The Kanto Initial Three Pokémon (September 27 to October 3, 2002) - new(150, 05, C) {OT_Names = PCNYx, CurrentLevel = 70, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Mewtwo - - // Power Plant Pokémon (October 4 to October 10, 2002) - new(172, 05, GS) {OT_Names = PCNYx, Moves = new(146), EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Dizzy Punch - new(081, 05, GS) {OT_Names = PCNYx, Moves = new(097), EggLocation = 256, EggCycles = 10, Language = International}, // Magnemite Agility - new(239, 05, GS) {OT_Names = PCNYx, Moves = new(146), EggLocation = 256, EggCycles = 10, Language = International}, // Elekid Dizzy Punch - new(100, 05, GS) {OT_Names = PCNYx, Moves = new(097), EggLocation = 256, EggCycles = 10, Language = International}, // Voltorb Agility - - // Scary Face Pokémon (October 25 to October 31, 2002) - new(173, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Scary Face - new(174, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Scary Face - new(183, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggLocation = 256, EggCycles = 10, Language = International}, // Marill Scary Face - new(172, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Scary Face - new(194, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggLocation = 256, EggCycles = 10, Language = International}, // Wooper Scary Face - - // Silver Cave (November 1 to November 7, 2002) - new(114, 05, GS) {OT_Names = PCNYx, Moves = new(235), EggLocation = 256, EggCycles = 10, Language = International}, // Tangela Synthesis - new(077, 05, GS) {OT_Names = PCNYx, Moves = new(067), EggLocation = 256, EggCycles = 10, Language = International}, // Ponyta Low Kick - new(200, 05, GS) {OT_Names = PCNYx, Moves = new(095), EggLocation = 256, EggCycles = 10, Language = International}, // Misdreavus Hypnosis - new(246, 05, GS) {OT_Names = PCNYx, Moves = new(099), EggLocation = 256, EggCycles = 10, Language = International}, // Larvitar Rage - - // Union Cave Pokémon (November 8 to 14, 2002) - new(120, 05, GS) {OT_Names = PCNYx, Moves = new(239), EggLocation = 256, EggCycles = 10, Language = International}, // Staryu Twister - new(098, 05, GS) {OT_Names = PCNYx, Moves = new(232), EggLocation = 256, EggCycles = 10, Language = International}, // Krabby Metal Claw - new(095, 05, GS) {OT_Names = PCNYx, Moves = new(159), EggLocation = 256, EggCycles = 10, Language = International}, // Onix Sharpen - new(131, 05, GS) {OT_Names = PCNYx, Moves = new(248), EggLocation = 256, EggCycles = 10, Language = International}, // Lapras Future Sight - - // Johto Legendary (November 15 to 21, 2002) - new(250, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Ho-Oh - new(249, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Lugia - - // Celebi Present SP (November 22 to 28, 2002) - new(151, 05, C) {OT_Names = PCNYx, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Mew - - // Psychic Type Pokémon (November 29 to December 5, 2002) - new(063, 05, GS) {OT_Names = PCNYx, Moves = new(193), EggLocation = 256, EggCycles = 10, Language = International}, // Abra Foresight - new(096, 05, GS) {OT_Names = PCNYx, Moves = new(133), EggLocation = 256, EggCycles = 10, Language = International}, // Drowzee Amnesia - new(102, 05, GS) {OT_Names = PCNYx, Moves = new(230), EggLocation = 256, EggCycles = 10, Language = International}, // Exeggcute Sweet Scent - new(122, 05, GS) {OT_Names = PCNYx, Moves = new(170), EggLocation = 256, EggCycles = 10, Language = International}, // Mr. Mime Mind Reader - - // The Johto Initial Three Pokémon (December 6 to 12, 2002) - new(154, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Meganium - new(157, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Typhlosion - new(160, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Feraligatr - - // Rock Tunnel Pokémon (December 13 to December 19, 2002) - new(074, 05, GS) {OT_Names = PCNYx, Moves = new(229), EggLocation = 256, EggCycles = 10, Language = International}, // Geodude Rapid Spin - new(041, 05, GS) {OT_Names = PCNYx, Moves = new(175), EggLocation = 256, EggCycles = 10, Language = International}, // Zubat Flail - new(066, 05, GS) {OT_Names = PCNYx, Moves = new(037), EggLocation = 256, EggCycles = 10, Language = International}, // Machop Thrash - new(104, 05, GS) {OT_Names = PCNYx, Moves = new(031), EggLocation = 256, EggCycles = 10, Language = International}, // Cubone Fury Attack - - // Ice Type Pokémon (December 20 to 26, 2002) - new(225, 05, GS) {OT_Names = PCNYx, Moves = new(191), EggLocation = 256, EggCycles = 10, Language = International}, // Delibird Spikes - new(086, 05, GS) {OT_Names = PCNYx, Moves = new(175), EggLocation = 256, EggCycles = 10, Language = International}, // Seel Flail - new(220, 05, GS) {OT_Names = PCNYx, Moves = new(018), EggLocation = 256, EggCycles = 10, Language = International}, // Swinub Whirlwind - - // Pokémon that Appear at Night only (December 27, 2002 to January 2, 2003) - new(163, 05, GS) {OT_Names = PCNYx, Moves = new(101), EggLocation = 256, EggCycles = 10, Language = International}, // Hoothoot Night Shade - new(215, 05, GS) {OT_Names = PCNYx, Moves = new(236), EggLocation = 256, EggCycles = 10, Language = International}, // Sneasel Moonlight - - // Grass Type ( January 3 to 9, 2003) - new(191, 05, GS) {OT_Names = PCNYx, Moves = new(150), EggLocation = 256, EggCycles = 10, Language = International}, // Sunkern Splash - new(046, 05, GS) {OT_Names = PCNYx, Moves = new(235), EggLocation = 256, EggCycles = 10, Language = International}, // Paras Synthesis - new(187, 05, GS) {OT_Names = PCNYx, Moves = new(097), EggLocation = 256, EggCycles = 10, Language = International}, // Hoppip Agility - new(043, 05, GS) {OT_Names = PCNYx, Moves = new(073), EggLocation = 256, EggCycles = 10, Language = International}, // Oddish Leech Seed - - // Normal Pokémon (January 10 to 16, 2003) - new(161, 05, GS) {OT_Names = PCNYx, Moves = new(146), EggLocation = 256, EggCycles = 10, Language = International}, // Sentret Dizzy Punch - new(234, 05, GS) {OT_Names = PCNYx, Moves = new(219), EggLocation = 256, EggCycles = 10, Language = International}, // Stantler Safeguard - new(241, 05, GS) {OT_Names = PCNYx, Moves = new(025), EggLocation = 256, EggCycles = 10, Language = International}, // Miltank Mega Kick - new(190, 05, GS) {OT_Names = PCNYx, Moves = new(102), EggLocation = 256, EggCycles = 10, Language = International}, // Aipom Mimic - new(108, 05, GS) {OT_Names = PCNYx, Moves = new(003), EggLocation = 256, EggCycles = 10, Language = International}, // Lickitung DoubleSlap - new(143, 05, GS) {OT_Names = PCNYx, Moves = new(150), EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Splash - - // Mt. Mortar (January 24 to 30, 2003) - new(066, 05, GS) {OT_Names = PCNYx, Moves = new(206), EggLocation = 256, EggCycles = 10, Language = International}, // Machop False Swipe - new(129, 05, GS) {OT_Names = PCNYx, Moves = new(145), EggLocation = 256, EggCycles = 10, Language = International}, // Magikarp Bubble - new(236, 05, GS) {OT_Names = PCNYx, Moves = new(099), EggLocation = 256, EggCycles = 10, Language = International}, // Tyrogue Rage - - // Dark Cave Pokémon (January 31 to February 6, 2003) - new(206, 05, GS) {OT_Names = PCNYx, Moves = new(031), EggLocation = 256, EggCycles = 10, Language = International}, // Dunsparce Fury Attack - new(202, 05, GS) {OT_Names = PCNYx, Moves = new(102), EggLocation = 256, EggCycles = 10, Language = International}, // Wobbuffet Mimic - new(231, 05, GS) {OT_Names = PCNYx, Moves = new(071), EggLocation = 256, EggCycles = 10, Language = International}, // Phanpy Absorb - new(216, 05, GS) {OT_Names = PCNYx, Moves = new(230), EggLocation = 256, EggCycles = 10, Language = International}, // Teddiursa Sweet Scent - - // Valentine's Day Special (February 7 to 13, 2003) - new(060, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Sweet Kiss - new(060, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Lovely Kiss - new(143, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Sweet Kiss - new(143, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Lovely Kiss - - // Rare Pokémon (February 21 to 27, 2003) - new(140, 05, GS) {OT_Names = PCNYx, Moves = new(088), EggLocation = 256, EggCycles = 10, Language = International}, // Kabuto Rock Throw - new(138, 05, GS) {OT_Names = PCNYx, Moves = new(088), EggLocation = 256, EggCycles = 10, Language = International}, // Omanyte Rock Throw - new(142, 05, GS) {OT_Names = PCNYx, Moves = new(088), EggLocation = 256, EggCycles = 10, Language = International}, // Aerodactyl Rock Throw - new(137, 05, GS) {OT_Names = PCNYx, Moves = new(112), EggLocation = 256, EggCycles = 10, Language = International}, // Porygon Barrier - new(133, 05, GS) {OT_Names = PCNYx, Moves = new(074), EggLocation = 256, EggCycles = 10, Language = International}, // Eevee Growth - new(185, 05, GS) {OT_Names = PCNYx, Moves = new(164), EggLocation = 256, EggCycles = 10, Language = International}, // Sudowoodo Substitute - - // Bug Type Pokémon (February 28 to March 6, 2003) - new(123, 05, GS) {OT_Names = PCNYx, Moves = new(049), EggLocation = 256, EggCycles = 10, Language = International}, // Scyther SonicBoom - new(214, 05, GS) {OT_Names = PCNYx, Moves = new(069), EggLocation = 256, EggCycles = 10, Language = International}, // Heracross Seismic Toss - new(127, 05, GS) {OT_Names = PCNYx, Moves = new(088), EggLocation = 256, EggCycles = 10, Language = International}, // Pinsir Rock Throw - new(165, 05, GS) {OT_Names = PCNYx, Moves = new(112), EggLocation = 256, EggCycles = 10, Language = International}, // Ledyba Barrier - new(167, 05, GS) {OT_Names = PCNYx, Moves = new(074), EggLocation = 256, EggCycles = 10, Language = International}, // Spinarak Growth - new(193, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggLocation = 256, EggCycles = 10, Language = International}, // Yanma Sweet Kiss - new(204, 05, GS) {OT_Names = PCNYx, Moves = new(164), EggLocation = 256, EggCycles = 10, Language = International}, // Pineco Substitute - - // Japanese Only (all below) - new(251, 30, GSC) {Location = 014}, // Celebi @ Ilex Forest (GBC) - - // Gen2 Events - // Egg Cycles Subject to Change. OTs for Eggs are unknown. - // Pokémon Center Mystery Egg #1 (December 15, 2001 to January 14, 2002) - new(152, 05, GSC) {Moves = new(080), EggLocation = 256, EggCycles = 10}, // Chikorita Petal Dance - new(172, 05, GSC) {Moves = new(047), EggLocation = 256, EggCycles = 10}, // Pichu Sing - new(173, 05, GSC) {Moves = new(129), EggLocation = 256, EggCycles = 10}, // Cleffa Swift - new(194, 05, GSC) {Moves = new(187), EggLocation = 256, EggCycles = 10}, // Wooper Belly Drum - new(231, 05, GSC) {Moves = new(227), EggLocation = 256, EggCycles = 10}, // Phanpy Encore - new(238, 05, GSC) {Moves = new(118), EggLocation = 256, EggCycles = 10}, // Smoochum Metronome - - // Pokémon Center Mystery Egg #2 (March 16 to April 7, 2002) - new(054, 05, GSC) {Moves = new(080), EggLocation = 256, EggCycles = 10}, // Psyduck Petal Dance - new(152, 05, GSC) {Moves = new(080), EggLocation = 256, EggCycles = 10}, // Chikorita Petal Dance - new(172, 05, GSC) {Moves = new(080), EggLocation = 256, EggCycles = 10}, // Pichu Petal Dance - new(173, 05, GSC) {Moves = new(080), EggLocation = 256, EggCycles = 10}, // Cleffa Petal Dance - new(174, 05, GSC) {Moves = new(080), EggLocation = 256, EggCycles = 10}, // Igglybuff Petal Dance - new(238, 05, GSC) {Moves = new(080), EggLocation = 256, EggCycles = 10}, // Smoochum Petal Dance - - // Pokémon Center Mystery Egg #3 (April 27 to May 12, 2002) - new(001, 05, GSC) {Moves = new(246), EggLocation = 256, EggCycles = 10}, // Bulbasaur Ancientpower - new(004, 05, GSC) {Moves = new(242), EggLocation = 256, EggCycles = 10}, // Charmander Crunch - new(158, 05, GSC) {Moves = new(066), EggLocation = 256, EggCycles = 10}, // Totodile Submission - new(163, 05, GSC) {Moves = new(101), EggLocation = 256, EggCycles = 10}, // Hoot-Hoot Night Shade - }; -} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2.cs b/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2.cs new file mode 100644 index 000000000..001142682 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2.cs @@ -0,0 +1,125 @@ +using static PKHeX.Core.EncounterUtil; +using static PKHeX.Core.GameVersion; + +namespace PKHeX.Core; + +/// +/// Generation 2 Encounters +/// +internal static class Encounters2 +{ + internal static readonly EncounterArea2[] SlotsGD = EncounterArea2.GetAreas(Get("gold", "g2"), GD); + internal static readonly EncounterArea2[] SlotsSI = EncounterArea2.GetAreas(Get("silver", "g2"), SI); + internal static readonly EncounterArea2[] SlotsC = EncounterArea2.GetAreas(Get("crystal", "g2"), C); + + private const string tradeGSC = "tradegsc"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings8(tradeGSC); + + public static readonly EncounterStatic2[] StaticGSC = + { + new(152, 05, GSC) { Location = 001 }, // Chikorita @ New Bark Town + new(155, 05, GSC) { Location = 001 }, // Cyndaquil @ New Bark Town + new(158, 05, GSC) { Location = 001 }, // Totodile @ New Bark Town + + new(175, 05, GSC) { Location = 000, EggEncounter = true }, // Togepi + new(131, 20, GSC) { Location = 010 }, // Lapras @ Union Cave + new(133, 20, GSC) { Location = 016 }, // Eevee @ Goldenrod City + + new(185, 20, GSC) { Location = 020 }, // Sudowoodo @ Route 36 + new(236, 10, GSC) { Location = 035 }, // Tyrogue @ Mt. Mortar + + new(130, 30, GSC) { Location = 038, Shiny = Shiny.Always, Gender = 0, IVs = new(0, 14, 10, 10, 10, 10) }, // Gyarados @ Lake of Rage (forcing shiny IVs result in always Male) + new(074, 21, GSC) { Location = 036 }, // Geodude @ Rocket Hideout (Mahogany Town) + new(109, 21, GSC) { Location = 036 }, // Koffing @ Rocket Hideout (Mahogany Town) + new(100, 23, GSC) { Location = 036 }, // Voltorb @ Rocket Hideout (Mahogany Town) + new(101, 23, GSC) { Location = 036 }, // Electrode @ Rocket Hideout (Mahogany Town) + new(143, 50, GSC) { Location = 061 }, // Snorlax @ Vermillion City + + new(211, 05, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Old Rod) + new(211, 20, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Good Rod) + new(211, 40, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Super Rod) + + new(137, 15, GSC) { Location = 071 }, // Porygon @ Celadon Game Corner + + // Roamer + new(243, 40, GSC) { Location = 002 }, // Raikou + new(244, 40, GSC) { Location = 002 }, // Entei + }; + + public static readonly EncounterStatic2[] StaticGS = + { + new(133, 15, GS) { Location = 071 }, // Eevee @ Celadon Game Corner + new(122, 15, GS) { Location = 071 }, // Mr. Mime @ Celadon Game Corner + + new(063, 10, GS) { Location = 016 }, // Abra @ Goldenrod City (Game Corner) + new(147, 10, GS) { Location = 016 }, // Dratini @ Goldenrod City (Game Corner) + new(023, 10, GS) { Location = 016 }, // Ekans @ Goldenrod City (Game Corner) (Gold) + new(027, 10, GS) { Location = 016 }, // Sandshrew @ Goldenrod City (Game Corner) (Silver) + + new(223, 05, GS) { Location = 039 }, // Remoraid Swarm @ Route 44 (Old Rod) + new(223, 20, GS) { Location = 039 }, // Remoraid Swarm @ Route 44 (Good Rod) + new(223, 40, GS) { Location = 039 }, // Remoraid Swarm @ Route 44 (Super Rod) + + // Roamer + new(245, 40, GS) { Location = 002 }, // Suicune + }; + + public static readonly EncounterStatic2[] StaticGD = + { + new(249, 70, GD) { Location = 031 }, // Lugia @ Whirl Islands + new(250, 40, GD) { Location = 023 }, // Ho-Oh @ Tin Tower + }; + + public static readonly EncounterStatic2[] StaticSI = + { + new(249, 40, SI) { Location = 031 }, // Lugia @ Whirl Islands + new(250, 70, SI) { Location = 023 }, // Ho-Oh @ Tin Tower + }; + + public static readonly EncounterStatic2[] StaticC = + { + new(245, 40, C) { Location = 023 }, // Suicune @ Tin Tower + + new(147, 15, C) { Location = 042, Moves = new((int)Move.ExtremeSpeed, (int)Move.Wrap, (int)Move.ThunderWave, (int)Move.Twister) }, // Dratini ExtremeSpeed + + new(249, 60, C) { Location = 031 }, // Lugia @ Whirl Islands + new(250, 60, C) { Location = 023 }, // Ho-Oh @ Tin Tower + + new(025, 25, C) { Location = 071 }, // Pikachu @ Celadon Game Corner + new(246, 40, C) { Location = 071 }, // Larvitar @ Celadon Game Corner + + new(063, 05, C) { Location = 016 }, // Abra @ Goldenrod City (Game Corner) + new(104, 15, C) { Location = 016 }, // Cubone @ Goldenrod City (Game Corner) + new(202, 15, C) { Location = 016 }, // Wobbuffet @ Goldenrod City (Game Corner) + }; + + public static readonly EncounterStatic2[] StaticOddEggC = + { + new(172, 05, C) { EggEncounter = true, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Pichu + new(173, 05, C) { EggEncounter = true, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Cleffa + new(174, 05, C) { EggEncounter = true, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Igglybuff + new(236, 05, C) { EggEncounter = true, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Tyrogue + new(238, 05, C) { EggEncounter = true, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Smoochum + new(239, 05, C) { EggEncounter = true, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Elekid + new(240, 05, C) { EggEncounter = true, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Magby + }; + + internal static readonly EncounterStatic2 CelebiVC = new(251, 30, C) { Location = 014 }; // Celebi @ Ilex Forest (VC) + + internal static readonly EncounterTrade2[] TradeGift_GSC = + { + new(TradeNames, 0, 095, 03, 48926) { Gender = 0, IVs = new(08, 09, 06, 06, 06, 06) }, // Onix @ Violet City for Bellsprout [wild] + new(TradeNames, 1, 066, 05, 37460) { Gender = 1, IVs = new(12, 03, 07, 06, 06, 06) }, // Machop @ Goldenrod City for Drowzee [wild 9, hatched egg 5] + new(TradeNames, 2, 100, 05, 29189) { Gender = 2, IVs = new(08, 09, 08, 08, 08, 08) }, // Voltorb @ Olivine City for Krabby [egg] + new(TradeNames, 3, 112, 10, 00283) { Gender = 1, IVs = new(12, 07, 07, 06, 06, 06) }, // Rhydon @ Blackthorn City for Dragonair [wild] + new(TradeNames, 4, 142, 05, 26491) { Gender = 0, IVs = new(08, 09, 06, 06, 06, 06), OTGender = 1}, // Aerodactyl @ Route 14 for Chansey [egg] + new(TradeNames, 5, 078, 14, 15616) { Gender = 0, IVs = new(08, 09, 06, 06, 06, 06) }, // Rapidash @ Pewter City for Gloom [wild] + + new(TradeNames, 6, 085, 10, 00283) { Gender = 1, IVs = new(12, 07, 07, 06, 06, 06), OTGender = 1}, // Dodrio @ Blackthorn City for Dragonair [wild] + new(TradeNames, 7, 178, 15, 15616) { Gender = 0, IVs = new(08, 09, 06, 08, 06, 06) }, // Xatu @ Pewter City for Haunter [wild] + new(TradeNames, 8, 082, 05, 50082) { Gender = 2, IVs = new(08, 09, 06, 06, 06, 06) }, // Magneton @ Power Plant for Dugtrio [traded for Lickitung] + + new(TradeNames, 9, 021, 10, 01001), // Spearow @ Goldenrod City for free + new(TradeNames, 10, 213, 15, 00518), // Shuckle @ Cianwood City for free + }; +} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2GBEra.cs b/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2GBEra.cs new file mode 100644 index 000000000..7c077b0df --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2GBEra.cs @@ -0,0 +1,253 @@ +using static PKHeX.Core.GameVersion; +using static PKHeX.Core.EncounterGBLanguage; + +namespace PKHeX.Core; + +internal static class Encounters2GBEra +{ + private static readonly string[] PCNYx = { "PCNYa", "PCNYb", "PCNYc", "PCNYd" }; + + internal static readonly EncounterGift2[] StaticEventsGB = + { + // Stadium 2 Baton Pass Farfetch'd + new(083, 05, C) {Moves = new(226, 14, 97, 163), Location = 127, TID16 = 2000, OT_Name = "スタジアム"}, + new(083, 05, C) {Moves = new(226, 14, 97, 163), Location = 127, TID16 = 2000, OT_Name = "Stadium", Language = International}, + new(083, 05, C) {Moves = new(226, 14, 97, 163), Location = 127, TID16 = 2001, OT_Names = new[]{"Stade", "Stadion", "Stadio", "Estadio"}, Language = International}, + + // Stadium 2 Earthquake Gligar + new(207, 05, C) {Moves = new(89, 68, 17), Location = 127, TID16 = 2000, OT_Name = "スタジアム"}, + new(207, 05, C) {Moves = new(89, 68, 17), Location = 127, TID16 = 2000, OT_Name = "Stadium", Language = International}, + new(207, 05, C) {Moves = new(89, 68, 17), Location = 127, TID16 = 2001, OT_Names = new[]{"Stade", "Stadion", "Stadio", "Estadio"}, Language = International}, + + //New York Pokémon Center Events + + // Legendary Beasts (November 22 to 29, 2001) + new(243, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Raikou + new(244, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Entei + new(245, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Suicune + + // Legendary Birds (November 30 to December 6, 2001) + new(144, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Articuno + new(145, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Zapdos + new(146, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Moltres + + // Christmas Week (December 21 to 27, 2001) + new(225, 05, GS) {OT_Names = PCNYx, Moves = new(006), EggCycles = 10, Language = International}, // Pay Day Delibird + new(251, 05, C) {OT_Names = PCNYx, Location = 127, Language = International}, // Celebi + + // The Initial Three Set (December 28, 2001 to January 31, 2002) + new(001, 05, GS) {OT_Names = PCNYx, Moves = new(246), EggCycles = 10, Language = International}, // Bulbasaur Ancientpower + new(004, 05, GS) {OT_Names = PCNYx, Moves = new(242), EggCycles = 10, Language = International}, // Charmander Crunch + new(007, 05, GS) {OT_Names = PCNYx, Moves = new(192), EggCycles = 10, Language = International}, // Squirtle Zap Cannon + new(152, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggCycles = 10, Language = International}, // Chikorita Petal Dance + new(155, 05, GS) {OT_Names = PCNYx, Moves = new(038), EggCycles = 10, Language = International}, // Cyndaquil Double-Edge + new(158, 05, GS) {OT_Names = PCNYx, Moves = new(066), EggCycles = 10, Language = International}, // Totodile Submission + + // Valentine Week (February 1 to 14, 2002) + new(029, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggCycles = 10, Language = International}, // Nidoran (F) Lovely Kiss + new(029, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggCycles = 10, Language = International}, // Nidoran (F) Sweet Kiss + new(032, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggCycles = 10, Language = International}, // Nidoran (M) Lovely Kiss + new(032, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggCycles = 10, Language = International}, // Nidoran (M) Sweet Kiss + new(069, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggCycles = 10, Language = International}, // Bellsprout Lovely Kiss + new(069, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggCycles = 10, Language = International}, // Bellsprout Sweet Kiss + + // Swarm Week (February 22 to March 14, 2002) + new(183, 05, GS) {OT_Names = PCNYx, Moves = new(056), EggCycles = 10, Language = International}, // Marill Hydro Pump + new(193, 05, GS) {OT_Names = PCNYx, Moves = new(211), EggCycles = 10, Language = International}, // Yanma Steel Wing + new(206, 05, GS) {OT_Names = PCNYx, Moves = new(032), EggCycles = 10, Language = International}, // Dunsparce Horn Drill + new(209, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggCycles = 10, Language = International}, // Snubbull Lovely Kiss + new(211, 05, GS) {OT_Names = PCNYx, Moves = new(038), EggCycles = 10, Language = International}, // Qwilfish Double-Edge + new(223, 05, GS) {OT_Names = PCNYx, Moves = new(133), EggCycles = 10, Language = International}, // Remoraid Amnesia + + // Shiny RBY Starters (March 15 to 21, 2002) + new(003, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Venusaur + new(006, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Charizard + new(009, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Blastoise + + // Babies Week (March 22 to April 11, 2002) + new(172, 05, GS) {OT_Names = PCNYx, Moves = new(047), EggCycles = 10, Language = International}, // Pichu Sing + new(173, 05, GS) {OT_Names = PCNYx, Moves = new(129), EggCycles = 10, Language = International}, // Cleffa Swift + new(174, 05, GS) {OT_Names = PCNYx, Moves = new(102), EggCycles = 10, Language = International}, // Igglybuff Mimic + new(238, 05, GS) {OT_Names = PCNYx, Moves = new(118), EggCycles = 10, Language = International}, // Smoochum Metronome + new(239, 05, GS) {OT_Names = PCNYx, Moves = new(228), EggCycles = 10, Language = International}, // Elekid Pursuit + new(240, 05, GS) {OT_Names = PCNYx, Moves = new(185), EggCycles = 10, Language = International}, // Magby Faint Attack + + // Spring Into Spring (April 12 to May 4, 2002) + new(054, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggCycles = 10, Language = International}, // Psyduck Petal Dance + new(152, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggCycles = 10, Language = International}, // Chikorita Petal Dance + new(172, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggCycles = 10, Language = International}, // Pichu Petal Dance + new(173, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggCycles = 10, Language = International}, // Cleffa Petal Dance + new(174, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggCycles = 10, Language = International}, // Igglybuff Petal Dance + new(238, 05, GS) {OT_Names = PCNYx, Moves = new(080), EggCycles = 10, Language = International}, // Smoochum Petal Dance + + // Baby Weeks (May 5 to June 7, 2002) + new(194, 05, GS) {Moves = new(187), EggCycles = 10, Language = International}, // Wooper Belly Drum + + // Tropical Promotion to Summer Festival 1 (June 8 to 21, 2002) + new(060, 05, GS) {OT_Names = PCNYx, Moves = new(074), EggCycles = 10, Language = International}, // Poliwag Growth + new(116, 05, GS) {OT_Names = PCNYx, Moves = new(114), EggCycles = 10, Language = International}, // Horsea Haze + new(118, 05, GS) {OT_Names = PCNYx, Moves = new(014), EggCycles = 10, Language = International}, // Goldeen Swords Dance + new(129, 05, GS) {OT_Names = PCNYx, Moves = new(179), EggCycles = 10, Language = International}, // Magikarp Reversal + new(183, 05, GS) {OT_Names = PCNYx, Moves = new(146), EggCycles = 10, Language = International}, // Marill Dizzy Punch + + // Tropical Promotion to Summer Festival 2 (July 12 to August 8, 2002) + new(054, 05, GS) {OT_Names = PCNYx, Moves = new(161), EggCycles = 10, Language = International}, // Psyduck Tri Attack + new(072, 05, GS) {OT_Names = PCNYx, Moves = new(109), EggCycles = 10, Language = International}, // Tentacool Confuse Ray + new(131, 05, GS) {OT_Names = PCNYx, Moves = new(044), EggCycles = 10, Language = International}, // Lapras Bite + new(170, 05, GS) {OT_Names = PCNYx, Moves = new(113), EggCycles = 10, Language = International}, // Chinchou Light Screen + new(223, 05, GS) {OT_Names = PCNYx, Moves = new(054), EggCycles = 10, Language = International}, // Remoraid Mist + new(226, 05, GS) {OT_Names = PCNYx, Moves = new(016), EggCycles = 10, Language = International}, // Mantine Gust + + // Safari Week (August 9 to 29, 2002) + new(029, 05, GS) {OT_Names = PCNYx, Moves = new(236), EggCycles = 10, Language = International}, // Nidoran (F) Moonlight + new(032, 05, GS) {OT_Names = PCNYx, Moves = new(234), EggCycles = 10, Language = International}, // Nidoran (M) Morning Sun + new(113, 05, GS) {OT_Names = PCNYx, Moves = new(230), EggCycles = 10, Language = International}, // Chansey Sweet Scent + new(115, 05, GS) {OT_Names = PCNYx, Moves = new(185), EggCycles = 10, Language = International}, // Kangaskhan Faint Attack + new(128, 05, GS) {OT_Names = PCNYx, Moves = new(098), EggCycles = 10, Language = International}, // Tauros Quick Attack + new(147, 05, GS) {OT_Names = PCNYx, Moves = new(056), EggCycles = 10, Language = International}, // Dratini Hydro Pump + + // Sky Week (August 30 to September 26, 2002) + new(021, 05, GS) {OT_Names = PCNYx, Moves = new(049), EggCycles = 10, Language = International}, // Spearow SonicBoom + new(083, 05, GS) {OT_Names = PCNYx, Moves = new(210), EggCycles = 10, Language = International}, // Farfetch'd Fury Cutter + new(084, 05, GS) {OT_Names = PCNYx, Moves = new(067), EggCycles = 10, Language = International}, // Doduo Low Kick + new(177, 05, GS) {OT_Names = PCNYx, Moves = new(219), EggCycles = 10, Language = International}, // Natu Safeguard + new(198, 05, GS) {OT_Names = PCNYx, Moves = new(251), EggCycles = 10, Language = International}, // Murkrow Beat Up + new(227, 05, GS) {OT_Names = PCNYx, Moves = new(210), EggCycles = 10, Language = International}, // Skarmory Fury Cutter + + // The Kanto Initial Three Pokémon (September 27 to October 3, 2002) + new(150, 05, C) {OT_Names = PCNYx, CurrentLevel = 70, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Mewtwo + + // Power Plant Pokémon (October 4 to October 10, 2002) + new(172, 05, GS) {OT_Names = PCNYx, Moves = new(146), EggCycles = 10, Language = International}, // Pichu Dizzy Punch + new(081, 05, GS) {OT_Names = PCNYx, Moves = new(097), EggCycles = 10, Language = International}, // Magnemite Agility + new(239, 05, GS) {OT_Names = PCNYx, Moves = new(146), EggCycles = 10, Language = International}, // Elekid Dizzy Punch + new(100, 05, GS) {OT_Names = PCNYx, Moves = new(097), EggCycles = 10, Language = International}, // Voltorb Agility + + // Scary Face Pokémon (October 25 to October 31, 2002) + new(173, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggCycles = 10, Language = International}, // Cleffa Scary Face + new(174, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggCycles = 10, Language = International}, // Igglybuff Scary Face + new(183, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggCycles = 10, Language = International}, // Marill Scary Face + new(172, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggCycles = 10, Language = International}, // Pichu Scary Face + new(194, 05, GS) {OT_Names = PCNYx, Moves = new(184), EggCycles = 10, Language = International}, // Wooper Scary Face + + // Silver Cave (November 1 to November 7, 2002) + new(114, 05, GS) {OT_Names = PCNYx, Moves = new(235), EggCycles = 10, Language = International}, // Tangela Synthesis + new(077, 05, GS) {OT_Names = PCNYx, Moves = new(067), EggCycles = 10, Language = International}, // Ponyta Low Kick + new(200, 05, GS) {OT_Names = PCNYx, Moves = new(095), EggCycles = 10, Language = International}, // Misdreavus Hypnosis + new(246, 05, GS) {OT_Names = PCNYx, Moves = new(099), EggCycles = 10, Language = International}, // Larvitar Rage + + // Union Cave Pokémon (November 8 to 14, 2002) + new(120, 05, GS) {OT_Names = PCNYx, Moves = new(239), EggCycles = 10, Language = International}, // Staryu Twister + new(098, 05, GS) {OT_Names = PCNYx, Moves = new(232), EggCycles = 10, Language = International}, // Krabby Metal Claw + new(095, 05, GS) {OT_Names = PCNYx, Moves = new(159), EggCycles = 10, Language = International}, // Onix Sharpen + new(131, 05, GS) {OT_Names = PCNYx, Moves = new(248), EggCycles = 10, Language = International}, // Lapras Future Sight + + // Johto Legendary (November 15 to 21, 2002) + new(250, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Ho-Oh + new(249, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Lugia + + // Celebi Present SP (November 22 to 28, 2002) + new(151, 05, C) {OT_Names = PCNYx, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Mew + + // Psychic Type Pokémon (November 29 to December 5, 2002) + new(063, 05, GS) {OT_Names = PCNYx, Moves = new(193), EggCycles = 10, Language = International}, // Abra Foresight + new(096, 05, GS) {OT_Names = PCNYx, Moves = new(133), EggCycles = 10, Language = International}, // Drowzee Amnesia + new(102, 05, GS) {OT_Names = PCNYx, Moves = new(230), EggCycles = 10, Language = International}, // Exeggcute Sweet Scent + new(122, 05, GS) {OT_Names = PCNYx, Moves = new(170), EggCycles = 10, Language = International}, // Mr. Mime Mind Reader + + // The Johto Initial Three Pokémon (December 6 to 12, 2002) + new(154, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Meganium + new(157, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Typhlosion + new(160, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Feraligatr + + // Rock Tunnel Pokémon (December 13 to December 19, 2002) + new(074, 05, GS) {OT_Names = PCNYx, Moves = new(229), EggCycles = 10, Language = International}, // Geodude Rapid Spin + new(041, 05, GS) {OT_Names = PCNYx, Moves = new(175), EggCycles = 10, Language = International}, // Zubat Flail + new(066, 05, GS) {OT_Names = PCNYx, Moves = new(037), EggCycles = 10, Language = International}, // Machop Thrash + new(104, 05, GS) {OT_Names = PCNYx, Moves = new(031), EggCycles = 10, Language = International}, // Cubone Fury Attack + + // Ice Type Pokémon (December 20 to 26, 2002) + new(225, 05, GS) {OT_Names = PCNYx, Moves = new(191), EggCycles = 10, Language = International}, // Delibird Spikes + new(086, 05, GS) {OT_Names = PCNYx, Moves = new(175), EggCycles = 10, Language = International}, // Seel Flail + new(220, 05, GS) {OT_Names = PCNYx, Moves = new(018), EggCycles = 10, Language = International}, // Swinub Whirlwind + + // Pokémon that Appear at Night only (December 27, 2002 to January 2, 2003) + new(163, 05, GS) {OT_Names = PCNYx, Moves = new(101), EggCycles = 10, Language = International}, // Hoothoot Night Shade + new(215, 05, GS) {OT_Names = PCNYx, Moves = new(236), EggCycles = 10, Language = International}, // Sneasel Moonlight + + // Grass Type ( January 3 to 9, 2003) + new(191, 05, GS) {OT_Names = PCNYx, Moves = new(150), EggCycles = 10, Language = International}, // Sunkern Splash + new(046, 05, GS) {OT_Names = PCNYx, Moves = new(235), EggCycles = 10, Language = International}, // Paras Synthesis + new(187, 05, GS) {OT_Names = PCNYx, Moves = new(097), EggCycles = 10, Language = International}, // Hoppip Agility + new(043, 05, GS) {OT_Names = PCNYx, Moves = new(073), EggCycles = 10, Language = International}, // Oddish Leech Seed + + // Normal Pokémon (January 10 to 16, 2003) + new(161, 05, GS) {OT_Names = PCNYx, Moves = new(146), EggCycles = 10, Language = International}, // Sentret Dizzy Punch + new(234, 05, GS) {OT_Names = PCNYx, Moves = new(219), EggCycles = 10, Language = International}, // Stantler Safeguard + new(241, 05, GS) {OT_Names = PCNYx, Moves = new(025), EggCycles = 10, Language = International}, // Miltank Mega Kick + new(190, 05, GS) {OT_Names = PCNYx, Moves = new(102), EggCycles = 10, Language = International}, // Aipom Mimic + new(108, 05, GS) {OT_Names = PCNYx, Moves = new(003), EggCycles = 10, Language = International}, // Lickitung DoubleSlap + new(143, 05, GS) {OT_Names = PCNYx, Moves = new(150), EggCycles = 10, Language = International}, // Snorlax Splash + + // Mt. Mortar (January 24 to 30, 2003) + new(066, 05, GS) {OT_Names = PCNYx, Moves = new(206), EggCycles = 10, Language = International}, // Machop False Swipe + new(129, 05, GS) {OT_Names = PCNYx, Moves = new(145), EggCycles = 10, Language = International}, // Magikarp Bubble + new(236, 05, GS) {OT_Names = PCNYx, Moves = new(099), EggCycles = 10, Language = International}, // Tyrogue Rage + + // Dark Cave Pokémon (January 31 to February 6, 2003) + new(206, 05, GS) {OT_Names = PCNYx, Moves = new(031), EggCycles = 10, Language = International}, // Dunsparce Fury Attack + new(202, 05, GS) {OT_Names = PCNYx, Moves = new(102), EggCycles = 10, Language = International}, // Wobbuffet Mimic + new(231, 05, GS) {OT_Names = PCNYx, Moves = new(071), EggCycles = 10, Language = International}, // Phanpy Absorb + new(216, 05, GS) {OT_Names = PCNYx, Moves = new(230), EggCycles = 10, Language = International}, // Teddiursa Sweet Scent + + // Valentine's Day Special (February 7 to 13, 2003) + new(060, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggCycles = 10, Language = International}, // Poliwag Sweet Kiss + new(060, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggCycles = 10, Language = International}, // Poliwag Lovely Kiss + new(143, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggCycles = 10, Language = International}, // Snorlax Sweet Kiss + new(143, 05, GS) {OT_Names = PCNYx, Moves = new(142), EggCycles = 10, Language = International}, // Snorlax Lovely Kiss + + // Rare Pokémon (February 21 to 27, 2003) + new(140, 05, GS) {OT_Names = PCNYx, Moves = new(088), EggCycles = 10, Language = International}, // Kabuto Rock Throw + new(138, 05, GS) {OT_Names = PCNYx, Moves = new(088), EggCycles = 10, Language = International}, // Omanyte Rock Throw + new(142, 05, GS) {OT_Names = PCNYx, Moves = new(088), EggCycles = 10, Language = International}, // Aerodactyl Rock Throw + new(137, 05, GS) {OT_Names = PCNYx, Moves = new(112), EggCycles = 10, Language = International}, // Porygon Barrier + new(133, 05, GS) {OT_Names = PCNYx, Moves = new(074), EggCycles = 10, Language = International}, // Eevee Growth + new(185, 05, GS) {OT_Names = PCNYx, Moves = new(164), EggCycles = 10, Language = International}, // Sudowoodo Substitute + + // Bug Type Pokémon (February 28 to March 6, 2003) + new(123, 05, GS) {OT_Names = PCNYx, Moves = new(049), EggCycles = 10, Language = International}, // Scyther SonicBoom + new(214, 05, GS) {OT_Names = PCNYx, Moves = new(069), EggCycles = 10, Language = International}, // Heracross Seismic Toss + new(127, 05, GS) {OT_Names = PCNYx, Moves = new(088), EggCycles = 10, Language = International}, // Pinsir Rock Throw + new(165, 05, GS) {OT_Names = PCNYx, Moves = new(112), EggCycles = 10, Language = International}, // Ledyba Barrier + new(167, 05, GS) {OT_Names = PCNYx, Moves = new(074), EggCycles = 10, Language = International}, // Spinarak Growth + new(193, 05, GS) {OT_Names = PCNYx, Moves = new(186), EggCycles = 10, Language = International}, // Yanma Sweet Kiss + new(204, 05, GS) {OT_Names = PCNYx, Moves = new(164), EggCycles = 10, Language = International}, // Pineco Substitute + + // Japanese Only (all below) + new(251, 30, GSC) {Location = 014}, // Celebi @ Ilex Forest (GBC) + + // Gen2 Events + // Egg Cycles Subject to Change. OTs for Eggs are unknown. + // Pokémon Center Mystery Egg #1 (December 15, 2001 to January 14, 2002) + new(152, 05, GSC) {Moves = new(080), EggCycles = 10}, // Chikorita Petal Dance + new(172, 05, GSC) {Moves = new(047), EggCycles = 10}, // Pichu Sing + new(173, 05, GSC) {Moves = new(129), EggCycles = 10}, // Cleffa Swift + new(194, 05, GSC) {Moves = new(187), EggCycles = 10}, // Wooper Belly Drum + new(231, 05, GSC) {Moves = new(227), EggCycles = 10}, // Phanpy Encore + new(238, 05, GSC) {Moves = new(118), EggCycles = 10}, // Smoochum Metronome + + // Pokémon Center Mystery Egg #2 (March 16 to April 7, 2002) + new(054, 05, GSC) {Moves = new(080), EggCycles = 10}, // Psyduck Petal Dance + new(152, 05, GSC) {Moves = new(080), EggCycles = 10}, // Chikorita Petal Dance + new(172, 05, GSC) {Moves = new(080), EggCycles = 10}, // Pichu Petal Dance + new(173, 05, GSC) {Moves = new(080), EggCycles = 10}, // Cleffa Petal Dance + new(174, 05, GSC) {Moves = new(080), EggCycles = 10}, // Igglybuff Petal Dance + new(238, 05, GSC) {Moves = new(080), EggCycles = 10}, // Smoochum Petal Dance + + // Pokémon Center Mystery Egg #3 (April 27 to May 12, 2002) + new(001, 05, GSC) {Moves = new(246), EggCycles = 10}, // Bulbasaur Ancientpower + new(004, 05, GSC) {Moves = new(242), EggCycles = 10}, // Charmander Crunch + new(158, 05, GSC) {Moves = new(066), EggCycles = 10}, // Totodile Submission + new(163, 05, GSC) {Moves = new(101), EggCycles = 10}, // Hoot-Hoot Night Shade + }; +} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3Colo.cs b/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3Colo.cs index 1cfb8d3dd..b897edced 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3Colo.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3Colo.cs @@ -4,103 +4,115 @@ namespace PKHeX.Core; internal static class Encounters3Colo { - internal static readonly EncounterStatic3[] Encounter_ColoGift = + internal static readonly EncounterStatic3Colo[] Starters = { // Colosseum Starters: Gender locked to male - new(196, 25, GameVersion.COLO) { Gift = true, Location = 254, Gender = 0 }, // Espeon - new(197, 26, GameVersion.COLO) { Gift = true, Location = 254, Gender = 0, Moves = new(044) }, // Umbreon (Bite) + new(196, 25) { Moves = new(093, 216, 115, 270) }, // Espeon + new(197, 26) { Moves = new(044, 269, 290, 289) }, // Umbreon (Bite) }; - internal static readonly EncounterStaticShadow[] Encounter_Colo = + internal static readonly string[] TrainerNameDuking = { string.Empty, "ギンザル", "DUKING", "DOKING", "RODRIGO", "GRAND", string.Empty, "GERMÁN", }; + + internal static readonly EncounterGift3Colo[] Gifts = { - new(GameVersion.COLO, 01, 03000, ColoMakuhita) { Species = 296, Level = 30, Moves = new(193,116,233,238), Location = 005 }, // Makuhita: Miror B.Peon Trudly @ Phenac City + // In-Game without Bonus Disk + new(311, 13, TrainerNameDuking, GameVersion.CXD) { Location = 254, TID16 = 37149, OT_Gender = 0, Moves = new(045, 086, 098, 270) }, // Plusle @ Ingame Trade + }; - new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new(241,235,075,034), Location = 003 }, // Bayleef: Cipher Peon Verde @ Phenac City - new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new(241,235,075,034), Location = 069 }, // Bayleef: Cipher Peon Verde @ Shadow PKMN Lab - new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new(241,235,075,034), Location = 115 }, // Bayleef: Cipher Peon Verde @ Realgam Tower - new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new(241,235,075,034), Location = 132 }, // Bayleef: Cipher Peon Verde @ Snagem Hideout - new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new(241,108,091,172), Location = 003 }, // Quilava: Cipher Peon Rosso @ Phenac City - new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new(241,108,091,172), Location = 069 }, // Quilava: Cipher Peon Rosso @ Shadow PKMN Lab - new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new(241,108,091,172), Location = 115 }, // Quilava: Cipher Peon Rosso @ Realgam Tower - new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new(241,108,091,172), Location = 132 }, // Quilava: Cipher Peon Rosso @ Snagem Hideout - new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new(240,184,044,057), Location = 003 }, // Croconaw: Cipher Peon Bluno @ Phenac City - new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new(240,184,044,057), Location = 069 }, // Croconaw: Cipher Peon Bluno @ Shadow PKMN Lab - new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new(240,184,044,057), Location = 115 }, // Croconaw: Cipher Peon Bluno @ Realgam Tower - new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new(240,184,044,057), Location = 132 }, // Croconaw: Cipher Peon Bluno @ Snagem Hideout - new(GameVersion.COLO, 05, 03000, First) { Species = 164, Level = 30, Moves = new(211,095,115,019), Location = 015 }, // Noctowl: Rider Nover @ Pyrite Town - new(GameVersion.COLO, 06, 03000, First) { Species = 180, Level = 30, Moves = new(085,086,178,084), Location = 015 }, // Flaaffy: St.Performer Diogo @ Pyrite Town - new(GameVersion.COLO, 07, 03000, First) { Species = 188, Level = 30, Moves = new(235,079,178,072), Location = 015 }, // Skiploom: Rider Leba @ Pyrite Town - new(GameVersion.COLO, 08, 04000, First) { Species = 195, Level = 30, Moves = new(341,133,021,057), Location = 015 }, // Quagsire: Bandana Guy Divel @ Pyrite Town - new(GameVersion.COLO, 09, 04000, First) { Species = 200, Level = 30, Moves = new(060,109,212,247), Location = 015 }, // Misdreavus: Rider Vant @ Pyrite Town - new(GameVersion.COLO, 10, 05000, First) { Species = 193, Level = 33, Moves = new(197,048,049,253), Location = 025 }, // Yanma: Cipher Peon Nore @ Pyrite Bldg - new(GameVersion.COLO, 10, 05000, First) { Species = 193, Level = 33, Moves = new(197,048,049,253), Location = 132 }, // Yanma: Cipher Peon Nore @ Snagem Hideout - new(GameVersion.COLO, 11, 05000, First) { Species = 162, Level = 33, Moves = new(231,270,098,070), Location = 015 }, // Furret: Rogue Cail @ Pyrite Town - new(GameVersion.COLO, 12, 04000, First) { Species = 218, Level = 30, Moves = new(241,281,088,053), Location = 015 }, // Slugma: Roller Boy Lon @ Pyrite Town - new(GameVersion.COLO, 13, 04000, First) { Species = 223, Level = 20, Moves = new(061,199,060,062), Location = 028 }, // Remoraid: Miror B.Peon Reath @ Pyrite Bldg - new(GameVersion.COLO, 13, 04000, First) { Species = 223, Level = 20, Moves = new(061,199,060,062), Location = 030 }, // Remoraid: Miror B.Peon Reath @ Pyrite Cave - new(GameVersion.COLO, 14, 05000, First) { Species = 226, Level = 33, Moves = new(017,048,061,036), Location = 028 }, // Mantine: Miror B.Peon Ferma @ Pyrite Bldg - new(GameVersion.COLO, 14, 05000, First) { Species = 226, Level = 33, Moves = new(017,048,061,036), Location = 030 }, // Mantine: Miror B.Peon Ferma @ Pyrite Cave - new(GameVersion.COLO, 15, 05000, First) { Species = 211, Level = 33, Moves = new(042,107,040,057), Location = 015 }, // Qwilfish: Hunter Doken @ Pyrite Bldg - new(GameVersion.COLO, 16, 05000, First) { Species = 307, Level = 33, Moves = new(197,347,093,136), Location = 031 }, // Meditite: Rider Twan @ Pyrite Cave - new(GameVersion.COLO, 17, 05000, First) { Species = 206, Level = 33, Moves = new(180,137,281,036), Location = 029 }, // Dunsparce: Rider Sosh @ Pyrite Cave - new(GameVersion.COLO, 18, 05000, First) { Species = 333, Level = 33, Moves = new(119,047,219,019), Location = 032 }, // Swablu: Hunter Zalo @ Pyrite Cave - new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new(175,335,067,157), Location = 104 }, // Sudowoodo: Cipher Admin Miror B. @ Realgam Tower - new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new(175,335,067,157), Location = 125 }, // Sudowoodo: Cipher Admin Miror B. @ Deep Colosseum - new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new(175,335,067,157), Location = 030 }, // Sudowoodo: Cipher Admin Miror B. @ Pyrite Cave - new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new(097,116,167,229), Location = 039 }, // Hitmontop: Cipher Peon Skrub @ Agate Village - new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new(097,116,167,229), Location = 132 }, // Hitmontop: Cipher Peon Skrub @ Snagem Hideout - new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new(097,116,167,229), Location = 068 }, // Hitmontop: Cipher Peon Skrub @ Shadow PKMN Lab - new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new(241,043,044,126), Location = 106 }, // Entei: Cipher Admin Dakim @ Realgam Tower - new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new(241,043,044,126), Location = 125 }, // Entei: Cipher Admin Dakim @ Deep Colosseum - new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new(241,043,044,126), Location = 076 }, // Entei: Cipher Admin Dakim @ Mt. Battle - new(GameVersion.COLO, 22, 06000, First) { Species = 166, Level = 40, Moves = new(226,219,048,004), Location = 047 }, // Ledian: Cipher Peon Kloak @ The Under - new(GameVersion.COLO, 22, 06000, First) { Species = 166, Level = 40, Moves = new(226,219,048,004), Location = 132 }, // Ledian: Cipher Peon Kloak @ Snagem Hideout - new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new(240,043,016,057), Location = 110 }, // Suicune (Surf): Cipher Admin Venus @ Realgam Tower - new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new(240,043,016,056), Location = 125 }, // Suicune (Hydro Pump): Cipher Admin Venus @ Deep Colosseum - new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new(240,043,016,057), Location = 055 }, // Suicune (Surf): Cipher Admin Venus @ The Under - new(GameVersion.COLO, 24, 06000, Gligar) { Species = 207, Level = 43, Moves = new(185,028,040,163), Location = 058 }, // Gligar: Hunter Frena @ The Under Subway - new(GameVersion.COLO, 24, 06000, Gligar) { Species = 207, Level = 43, Moves = new(185,028,040,163), Location = 133 }, // Gligar: Hunter Frena @ Snagem Hideout - new(GameVersion.COLO, 25, 06000, First) { Species = 234, Level = 43, Moves = new(310,095,043,036), Location = 058 }, // Stantler: Chaser Liaks @ The Under Subway - new(GameVersion.COLO, 25, 06000, First) { Species = 234, Level = 43, Moves = new(310,095,043,036), Location = 133 }, // Stantler: Chaser Liaks @ Snagem Hideout - new(GameVersion.COLO, 25, 06000, First) { Species = 221, Level = 43, Moves = new(203,316,091,059), Location = 058 }, // Piloswine: Bodybuilder Lonia @ The Under Subway - new(GameVersion.COLO, 26, 06000, First) { Species = 221, Level = 43, Moves = new(203,316,091,059), Location = 134 }, // Piloswine: Bodybuilder Lonia @ Snagem Hideout - new(GameVersion.COLO, 27, 06000, First) { Species = 215, Level = 43, Moves = new(185,103,154,196), Location = 058 }, // Sneasel: Rider Nelis @ The Under Subway - new(GameVersion.COLO, 27, 06000, First) { Species = 215, Level = 43, Moves = new(185,103,154,196), Location = 134 }, // Sneasel: Rider Nelis @ Snagem Hideout - new(GameVersion.COLO, 28, 06000, First) { Species = 190, Level = 43, Moves = new(226,321,154,129), Location = 067 }, // Aipom: Cipher Peon Cole @ Shadow PKMN Lab - new(GameVersion.COLO, 29, 06000, Murkrow) { Species = 198, Level = 43, Moves = new(185,212,101,019), Location = 067 }, // Murkrow: Cipher Peon Lare @ Shadow PKMN Lab - new(GameVersion.COLO, 30, 06000, First) { Species = 205, Level = 43, Moves = new(153,182,117,229), Location = 067 }, // Forretress: Cipher Peon Vana @ Shadow PKMN Lab - new(GameVersion.COLO, 31, 06000, First) { Species = 210, Level = 43, Moves = new(044,184,046,070), Location = 069 }, // Granbull: Cipher Peon Tanie @ Shadow PKMN Lab - new(GameVersion.COLO, 32, 06000, First) { Species = 329, Level = 43, Moves = new(242,103,328,225), Location = 068 }, // Vibrava: Cipher Peon Remil @ Shadow PKMN Lab - new(GameVersion.COLO, 33, 06000, First) { Species = 168, Level = 43, Moves = new(169,184,141,188), Location = 069 }, // Ariados: Cipher Peon Lesar @ Shadow PKMN Lab + internal static readonly EncounterShadow3Colo[] Shadow = + { + new(01, 03000, ColoMakuhita) { Species = 296, Level = 30, Moves = new(193,116,233,238), Location = 005 }, // Makuhita: Miror B.Peon Trudly @ Phenac City - new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new(240,043,098,087), Location = 113 }, // Raikou: Cipher Admin Ein @ Realgam Tower - new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new(240,043,098,087), Location = 125 }, // Raikou: Cipher Admin Ein @ Deep Colosseum - new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new(240,043,098,087), Location = 069 }, // Raikou: Cipher Admin Ein @ Shadow PKMN Lab + new(02, 03000, First) { Species = 153, Level = 30, Moves = new(241,235,075,034), Location = 003 }, // Bayleef: Cipher Peon Verde @ Phenac City + new(02, 03000, First) { Species = 153, Level = 30, Moves = new(241,235,075,034), Location = 069 }, // Bayleef: Cipher Peon Verde @ Shadow PKMN Lab + new(02, 03000, First) { Species = 153, Level = 30, Moves = new(241,235,075,034), Location = 115 }, // Bayleef: Cipher Peon Verde @ Realgam Tower + new(02, 03000, First) { Species = 153, Level = 30, Moves = new(241,235,075,034), Location = 132 }, // Bayleef: Cipher Peon Verde @ Snagem Hideout + new(03, 03000, First) { Species = 156, Level = 30, Moves = new(241,108,091,172), Location = 003 }, // Quilava: Cipher Peon Rosso @ Phenac City + new(03, 03000, First) { Species = 156, Level = 30, Moves = new(241,108,091,172), Location = 069 }, // Quilava: Cipher Peon Rosso @ Shadow PKMN Lab + new(03, 03000, First) { Species = 156, Level = 30, Moves = new(241,108,091,172), Location = 115 }, // Quilava: Cipher Peon Rosso @ Realgam Tower + new(03, 03000, First) { Species = 156, Level = 30, Moves = new(241,108,091,172), Location = 132 }, // Quilava: Cipher Peon Rosso @ Snagem Hideout + new(04, 03000, First) { Species = 159, Level = 30, Moves = new(240,184,044,057), Location = 003 }, // Croconaw: Cipher Peon Bluno @ Phenac City + new(04, 03000, First) { Species = 159, Level = 30, Moves = new(240,184,044,057), Location = 069 }, // Croconaw: Cipher Peon Bluno @ Shadow PKMN Lab + new(04, 03000, First) { Species = 159, Level = 30, Moves = new(240,184,044,057), Location = 115 }, // Croconaw: Cipher Peon Bluno @ Realgam Tower + new(04, 03000, First) { Species = 159, Level = 30, Moves = new(240,184,044,057), Location = 132 }, // Croconaw: Cipher Peon Bluno @ Snagem Hideout + new(05, 03000, First) { Species = 164, Level = 30, Moves = new(211,095,115,019), Location = 015 }, // Noctowl: Rider Nover @ Pyrite Town + new(06, 03000, First) { Species = 180, Level = 30, Moves = new(085,086,178,084), Location = 015 }, // Flaaffy: St.Performer Diogo @ Pyrite Town + new(07, 03000, First) { Species = 188, Level = 30, Moves = new(235,079,178,072), Location = 015 }, // Skiploom: Rider Leba @ Pyrite Town + new(08, 04000, First) { Species = 195, Level = 30, Moves = new(341,133,021,057), Location = 015 }, // Quagsire: Bandana Guy Divel @ Pyrite Town + new(09, 04000, First) { Species = 200, Level = 30, Moves = new(060,109,212,247), Location = 015 }, // Misdreavus: Rider Vant @ Pyrite Town + new(10, 05000, First) { Species = 193, Level = 33, Moves = new(197,048,049,253), Location = 025 }, // Yanma: Cipher Peon Nore @ Pyrite Bldg + new(10, 05000, First) { Species = 193, Level = 33, Moves = new(197,048,049,253), Location = 132 }, // Yanma: Cipher Peon Nore @ Snagem Hideout + new(11, 05000, First) { Species = 162, Level = 33, Moves = new(231,270,098,070), Location = 015 }, // Furret: Rogue Cail @ Pyrite Town + new(12, 04000, First) { Species = 218, Level = 30, Moves = new(241,281,088,053), Location = 015 }, // Slugma: Roller Boy Lon @ Pyrite Town + new(13, 04000, First) { Species = 223, Level = 20, Moves = new(061,199,060,062), Location = 028 }, // Remoraid: Miror B.Peon Reath @ Pyrite Bldg + new(13, 04000, First) { Species = 223, Level = 20, Moves = new(061,199,060,062), Location = 030 }, // Remoraid: Miror B.Peon Reath @ Pyrite Cave + new(14, 05000, First) { Species = 226, Level = 33, Moves = new(017,048,061,036), Location = 028 }, // Mantine: Miror B.Peon Ferma @ Pyrite Bldg + new(14, 05000, First) { Species = 226, Level = 33, Moves = new(017,048,061,036), Location = 030 }, // Mantine: Miror B.Peon Ferma @ Pyrite Cave + new(15, 05000, First) { Species = 211, Level = 33, Moves = new(042,107,040,057), Location = 015 }, // Qwilfish: Hunter Doken @ Pyrite Bldg + new(16, 05000, First) { Species = 307, Level = 33, Moves = new(197,347,093,136), Location = 031 }, // Meditite: Rider Twan @ Pyrite Cave + new(17, 05000, First) { Species = 206, Level = 33, Moves = new(180,137,281,036), Location = 029 }, // Dunsparce: Rider Sosh @ Pyrite Cave + new(18, 05000, First) { Species = 333, Level = 33, Moves = new(119,047,219,019), Location = 032 }, // Swablu: Hunter Zalo @ Pyrite Cave + new(19, 10000, First) { Species = 185, Level = 35, Moves = new(175,335,067,157), Location = 104 }, // Sudowoodo: Cipher Admin Miror B. @ Realgam Tower + new(19, 10000, First) { Species = 185, Level = 35, Moves = new(175,335,067,157), Location = 125 }, // Sudowoodo: Cipher Admin Miror B. @ Deep Colosseum + new(19, 10000, First) { Species = 185, Level = 35, Moves = new(175,335,067,157), Location = 030 }, // Sudowoodo: Cipher Admin Miror B. @ Pyrite Cave + new(20, 06000, First) { Species = 237, Level = 38, Moves = new(097,116,167,229), Location = 039 }, // Hitmontop: Cipher Peon Skrub @ Agate Village + new(20, 06000, First) { Species = 237, Level = 38, Moves = new(097,116,167,229), Location = 132 }, // Hitmontop: Cipher Peon Skrub @ Snagem Hideout + new(20, 06000, First) { Species = 237, Level = 38, Moves = new(097,116,167,229), Location = 068 }, // Hitmontop: Cipher Peon Skrub @ Shadow PKMN Lab + new(21, 13000, First) { Species = 244, Level = 40, Moves = new(241,043,044,126), Location = 106 }, // Entei: Cipher Admin Dakim @ Realgam Tower + new(21, 13000, First) { Species = 244, Level = 40, Moves = new(241,043,044,126), Location = 125 }, // Entei: Cipher Admin Dakim @ Deep Colosseum + new(21, 13000, First) { Species = 244, Level = 40, Moves = new(241,043,044,126), Location = 076 }, // Entei: Cipher Admin Dakim @ Mt. Battle + new(22, 06000, First) { Species = 166, Level = 40, Moves = new(226,219,048,004), Location = 047 }, // Ledian: Cipher Peon Kloak @ The Under + new(22, 06000, First) { Species = 166, Level = 40, Moves = new(226,219,048,004), Location = 132 }, // Ledian: Cipher Peon Kloak @ Snagem Hideout + new(23, 13000, First) { Species = 245, Level = 40, Moves = new(240,043,016,057), Location = 110 }, // Suicune (Surf): Cipher Admin Venus @ Realgam Tower + new(23, 13000, First) { Species = 245, Level = 40, Moves = new(240,043,016,056), Location = 125 }, // Suicune (Hydro Pump): Cipher Admin Venus @ Deep Colosseum + new(23, 13000, First) { Species = 245, Level = 40, Moves = new(240,043,016,057), Location = 055 }, // Suicune (Surf): Cipher Admin Venus @ The Under + new(24, 06000, Gligar) { Species = 207, Level = 43, Moves = new(185,028,040,163), Location = 058 }, // Gligar: Hunter Frena @ The Under Subway + new(24, 06000, Gligar) { Species = 207, Level = 43, Moves = new(185,028,040,163), Location = 133 }, // Gligar: Hunter Frena @ Snagem Hideout + new(25, 06000, First) { Species = 234, Level = 43, Moves = new(310,095,043,036), Location = 058 }, // Stantler: Chaser Liaks @ The Under Subway + new(25, 06000, First) { Species = 234, Level = 43, Moves = new(310,095,043,036), Location = 133 }, // Stantler: Chaser Liaks @ Snagem Hideout + new(25, 06000, First) { Species = 221, Level = 43, Moves = new(203,316,091,059), Location = 058 }, // Piloswine: Bodybuilder Lonia @ The Under Subway + new(26, 06000, First) { Species = 221, Level = 43, Moves = new(203,316,091,059), Location = 134 }, // Piloswine: Bodybuilder Lonia @ Snagem Hideout + new(27, 06000, First) { Species = 215, Level = 43, Moves = new(185,103,154,196), Location = 058 }, // Sneasel: Rider Nelis @ The Under Subway + new(27, 06000, First) { Species = 215, Level = 43, Moves = new(185,103,154,196), Location = 134 }, // Sneasel: Rider Nelis @ Snagem Hideout + new(28, 06000, First) { Species = 190, Level = 43, Moves = new(226,321,154,129), Location = 067 }, // Aipom: Cipher Peon Cole @ Shadow PKMN Lab + new(29, 06000, Murkrow) { Species = 198, Level = 43, Moves = new(185,212,101,019), Location = 067 }, // Murkrow: Cipher Peon Lare @ Shadow PKMN Lab + new(30, 06000, First) { Species = 205, Level = 43, Moves = new(153,182,117,229), Location = 067 }, // Forretress: Cipher Peon Vana @ Shadow PKMN Lab + new(31, 06000, First) { Species = 210, Level = 43, Moves = new(044,184,046,070), Location = 069 }, // Granbull: Cipher Peon Tanie @ Shadow PKMN Lab + new(32, 06000, First) { Species = 329, Level = 43, Moves = new(242,103,328,225), Location = 068 }, // Vibrava: Cipher Peon Remil @ Shadow PKMN Lab + new(33, 06000, First) { Species = 168, Level = 43, Moves = new(169,184,141,188), Location = 069 }, // Ariados: Cipher Peon Lesar @ Shadow PKMN Lab - new(GameVersion.COLO, 35, 07000, First) { Species = 192, Level = 45, Moves = new(241,074,275,076), Location = 109 }, // Sunflora: Cipher Peon Baila @ Realgam Tower - new(GameVersion.COLO, 35, 07000, First) { Species = 192, Level = 45, Moves = new(241,074,275,076), Location = 132 }, // Sunflora: Cipher Peon Baila @ Snagem Hideout - new(GameVersion.COLO, 36, 07000, First) { Species = 225, Level = 45, Moves = new(059,213,217,019), Location = 109 }, // Delibird: Cipher Peon Arton @ Realgam Tower - new(GameVersion.COLO, 36, 07000, First) { Species = 225, Level = 45, Moves = new(059,213,217,019), Location = 132 }, // Delibird: Cipher Peon Arton @ Snagem Hideout - new(GameVersion.COLO, 37, 07000, Heracross) { Species = 214, Level = 45, Moves = new(179,203,068,280), Location = 111 }, // Heracross: Cipher Peon Dioge @ Realgam Tower - new(GameVersion.COLO, 37, 07000, Heracross) { Species = 214, Level = 45, Moves = new(179,203,068,280), Location = 132 }, // Heracross: Cipher Peon Dioge @ Snagem Hideout - new(GameVersion.COLO, 38, 13000, First) { Species = 227, Level = 47, Moves = new(065,319,314,211), Location = 117 }, // Skarmory: Snagem Head Gonzap @ Realgam Tower - new(GameVersion.COLO, 38, 13000, First) { Species = 227, Level = 47, Moves = new(065,319,314,211), Location = 133 }, // Skarmory: Snagem Head Gonzap @ Snagem Hideout + new(34, 13000, First) { Species = 243, Level = 40, Moves = new(240,043,098,087), Location = 113 }, // Raikou: Cipher Admin Ein @ Realgam Tower + new(34, 13000, First) { Species = 243, Level = 40, Moves = new(240,043,098,087), Location = 125 }, // Raikou: Cipher Admin Ein @ Deep Colosseum + new(34, 13000, First) { Species = 243, Level = 40, Moves = new(240,043,098,087), Location = 069 }, // Raikou: Cipher Admin Ein @ Shadow PKMN Lab - new(GameVersion.COLO, 39, 07000, First) { Species = 241, Level = 48, Moves = new(208,111,205,034), Location = 118 }, // Miltank: Bodybuilder Jomas @ Tower Colosseum - new(GameVersion.COLO, 40, 07000, First) { Species = 359, Level = 48, Moves = new(195,014,163,185), Location = 118 }, // Absol: Rider Delan @ Tower Colosseum - new(GameVersion.COLO, 41, 07000, First) { Species = 229, Level = 48, Moves = new(185,336,123,053), Location = 118 }, // Houndoom: Cipher Peon Nella @ Tower Colosseum - new(GameVersion.COLO, 42, 07000, First) { Species = 357, Level = 49, Moves = new(076,235,345,019), Location = 118 }, // Tropius: Cipher Peon Ston @ Tower Colosseum - new(GameVersion.COLO, 43, 15000, First) { Species = 376, Level = 50, Moves = new(063,334,232,094), Location = 118 }, // Metagross: Cipher Nascour @ Tower Colosseum - new(GameVersion.COLO, 44, 20000, First) { Species = 248, Level = 55, Moves = new(242,087,157,059), Location = 118 }, // Tyranitar: Cipher Head Evice @ Tower Colosseum + new(35, 07000, First) { Species = 192, Level = 45, Moves = new(241,074,275,076), Location = 109 }, // Sunflora: Cipher Peon Baila @ Realgam Tower + new(35, 07000, First) { Species = 192, Level = 45, Moves = new(241,074,275,076), Location = 132 }, // Sunflora: Cipher Peon Baila @ Snagem Hideout + new(36, 07000, First) { Species = 225, Level = 45, Moves = new(059,213,217,019), Location = 109 }, // Delibird: Cipher Peon Arton @ Realgam Tower + new(36, 07000, First) { Species = 225, Level = 45, Moves = new(059,213,217,019), Location = 132 }, // Delibird: Cipher Peon Arton @ Snagem Hideout + new(37, 07000, Heracross) { Species = 214, Level = 45, Moves = new(179,203,068,280), Location = 111 }, // Heracross: Cipher Peon Dioge @ Realgam Tower + new(37, 07000, Heracross) { Species = 214, Level = 45, Moves = new(179,203,068,280), Location = 132 }, // Heracross: Cipher Peon Dioge @ Snagem Hideout + new(38, 13000, First) { Species = 227, Level = 47, Moves = new(065,319,314,211), Location = 117 }, // Skarmory: Snagem Head Gonzap @ Realgam Tower + new(38, 13000, First) { Species = 227, Level = 47, Moves = new(065,319,314,211), Location = 133 }, // Skarmory: Snagem Head Gonzap @ Snagem Hideout - new(GameVersion.COLO, 55, 07000, First) { Species = 235, Level = 45, Moves = new(166,039,003,231), Location = 132 }, // Smeargle: Team Snagem Biden @ Snagem Hideout - new(GameVersion.COLO, 56, 07000, Ursaring) { Species = 217, Level = 45, Moves = new(185,313,122,163), Location = 132 }, // Ursaring: Team Snagem Agrev @ Snagem Hideout - new(GameVersion.COLO, 57, 07000, First) { Species = 213, Level = 45, Moves = new(219,227,156,117), Location = 125 }, // Shuckle: Deep King Agnol @ Deep Colosseum + new(39, 07000, First) { Species = 241, Level = 48, Moves = new(208,111,205,034), Location = 118 }, // Miltank: Bodybuilder Jomas @ Tower Colosseum + new(40, 07000, First) { Species = 359, Level = 48, Moves = new(195,014,163,185), Location = 118 }, // Absol: Rider Delan @ Tower Colosseum + new(41, 07000, First) { Species = 229, Level = 48, Moves = new(185,336,123,053), Location = 118 }, // Houndoom: Cipher Peon Nella @ Tower Colosseum + new(42, 07000, First) { Species = 357, Level = 49, Moves = new(076,235,345,019), Location = 118 }, // Tropius: Cipher Peon Ston @ Tower Colosseum + new(43, 15000, First) { Species = 376, Level = 50, Moves = new(063,334,232,094), Location = 118 }, // Metagross: Cipher Nascour @ Tower Colosseum + new(44, 20000, First) { Species = 248, Level = 55, Moves = new(242,087,157,059), Location = 118 }, // Tyranitar: Cipher Head Evice @ Tower Colosseum - new(GameVersion.COLO, 67, 05000, First) { Species = 176, Level = 20, Moves = new(118,204,186,281), Location = 001 }, // Togetic: Cipher Peon Fein @ Outskirt Stand + new(55, 07000, First) { Species = 235, Level = 45, Moves = new(166,039,003,231), Location = 132 }, // Smeargle: Team Snagem Biden @ Snagem Hideout + new(56, 07000, Ursaring) { Species = 217, Level = 45, Moves = new(185,313,122,163), Location = 132 }, // Ursaring: Team Snagem Agrev @ Snagem Hideout + new(57, 07000, First) { Species = 213, Level = 45, Moves = new(219,227,156,117), Location = 125 }, // Shuckle: Deep King Agnol @ Deep Colosseum - new(GameVersion.COLO, 00, 00000, CTogepi) { Species = 175, Level = 20, Moves = new(118,204,186,281), Location = 128, IVs = new(0, 0, 0, 0, 0, 0) }, // Togepi: Chaser ボデス @ Card e Room (Japanese games only) - new(GameVersion.COLO, 00, 00000, CMareep) { Species = 179, Level = 37, Moves = new(087,084,086,178), Location = 128, IVs = new(0, 0, 0, 0, 0, 0) }, // Mareep: Hunter ホル @ Card e Room (Japanese games only) - new(GameVersion.COLO, 00, 00000, CScizor) { Species = 212, Level = 50, Moves = new(210,232,014,163), Location = 128, IVs = new(0, 0, 0, 0, 0, 0) }, // Scizor: Bodybuilder ワーバン @ Card e Room (Japanese games only) + new(67, 05000, First) { Species = 176, Level = 20, Moves = new(118,204,186,281), Location = 001 }, // Togetic: Cipher Peon Fein @ Outskirt Stand + }; + + public static readonly EncounterShadow3Colo[] EReader = + { + // Japanese E-Reader: 0 IVs + new(00, 00000, CTogepi) { Species = 175, Level = 20, Moves = new(118,204,186,281), Location = 128 }, // Togepi: Chaser ボデス @ Card e Room (Japanese games only) + new(00, 00000, CMareep) { Species = 179, Level = 37, Moves = new(087,084,086,178), Location = 128 }, // Mareep: Hunter ホル @ Card e Room (Japanese games only) + new(00, 00000, CScizor) { Species = 212, Level = 50, Moves = new(210,232,014,163), Location = 128 }, // Scizor: Bodybuilder ワーバン @ Card e Room (Japanese games only) }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3FRLG.cs b/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3FRLG.cs index f366c501f..de27e102d 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3FRLG.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3FRLG.cs @@ -15,47 +15,32 @@ internal static class Encounters3FRLG private static EncounterArea3[] GetRegular(string resource, string ident, GameVersion game) => EncounterArea3.GetAreas(Get(resource, ident), game); - static Encounters3FRLG() => MarkEncounterTradeStrings(TradeGift_FRLG, TradeFRLG); + private const string tradeFRLG = "tradefrlg"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings7(tradeFRLG); - private static readonly EncounterStatic3[] Encounter_FRLG_Roam = + public static readonly EncounterStatic3[] StaticFRLG = { new(243, 50, FRLG) { Roaming = true, Location = 16 }, // Raikou new(244, 50, FRLG) { Roaming = true, Location = 16 }, // Entei new(245, 50, FRLG) { Roaming = true, Location = 16 }, // Suicune - }; - private static readonly EncounterStatic3[] Encounter_FRLG_Stationary = - { // Starters @ Pallet Town - new(001, 05, FRLG) { Gift = true, Location = 088 }, // Bulbasaur - new(004, 05, FRLG) { Gift = true, Location = 088 }, // Charmander - new(007, 05, FRLG) { Gift = true, Location = 088 }, // Squirtle + new(001, 05, FRLG) { FixedBall = Ball.Poke, Location = 088 }, // Bulbasaur + new(004, 05, FRLG) { FixedBall = Ball.Poke, Location = 088 }, // Charmander + new(007, 05, FRLG) { FixedBall = Ball.Poke, Location = 088 }, // Squirtle // Fossil @ Cinnabar Island - new(138, 05, FRLG) { Gift = true, Location = 096 }, // Omanyte - new(140, 05, FRLG) { Gift = true, Location = 096 }, // Kabuto - new(142, 05, FRLG) { Gift = true, Location = 096 }, // Aerodactyl + new(138, 05, FRLG) { FixedBall = Ball.Poke, Location = 096 }, // Omanyte + new(140, 05, FRLG) { FixedBall = Ball.Poke, Location = 096 }, // Kabuto + new(142, 05, FRLG) { FixedBall = Ball.Poke, Location = 096 }, // Aerodactyl // Gift - new(106, 25, FRLG) { Gift = true, Location = 098 }, // Hitmonlee @ Saffron City - new(107, 25, FRLG) { Gift = true, Location = 098 }, // Hitmonchan @ Saffron City - new(129, 05, FRLG) { Gift = true, Location = 099 }, // Magikarp @ Route 4 - new(131, 25, FRLG) { Gift = true, Location = 134 }, // Lapras @ Silph Co. - new(133, 25, FRLG) { Gift = true, Location = 094 }, // Eevee @ Celadon City - new(175, 05, FRLG) { Gift = true, EggLocation = 253, Moves = new(045,204,118) }, // Togepi Egg - - // Celadon City Game Corner - new(063, 09, FR) { Gift = true, Location = 94 }, // Abra - new(035, 08, FR) { Gift = true, Location = 94 }, // Clefairy - new(123, 25, FR) { Gift = true, Location = 94 }, // Scyther - new(147, 18, FR) { Gift = true, Location = 94 }, // Dratini - new(137, 26, FR) { Gift = true, Location = 94 }, // Porygon - - new(063, 07, LG) { Gift = true, Location = 94 }, // Abra - new(035, 12, LG) { Gift = true, Location = 94 }, // Clefairy - new(127, 18, LG) { Gift = true, Location = 94 }, // Pinsir - new(147, 24, LG) { Gift = true, Location = 94 }, // Dratini - new(137, 18, LG) { Gift = true, Location = 94 }, // Porygon + new(106, 25, FRLG) { FixedBall = Ball.Poke, Location = 098 }, // Hitmonlee @ Saffron City + new(107, 25, FRLG) { FixedBall = Ball.Poke, Location = 098 }, // Hitmonchan @ Saffron City + new(129, 05, FRLG) { FixedBall = Ball.Poke, Location = 099 }, // Magikarp @ Route 4 + new(131, 25, FRLG) { FixedBall = Ball.Poke, Location = 134 }, // Lapras @ Silph Co. + new(133, 25, FRLG) { FixedBall = Ball.Poke, Location = 094 }, // Eevee @ Celadon City + new(175, 05, FRLG) { FixedBall = Ball.Poke, Location = 253, EggEncounter = true, Moves = new(045,204,118) }, // Togepi Egg // Stationary new(143, 30, FRLG) { Location = 112 }, // Snorlax @ Route 12 @@ -72,11 +57,36 @@ internal static class Encounters3FRLG // Event new(249, 70, FRLG) { Location = 174, FatefulEncounter = true }, // Lugia @ Navel Rock new(250, 70, FRLG) { Location = 174, FatefulEncounter = true }, // Ho-Oh @ Navel Rock - new(386, 30, FR ) { Location = 187, FatefulEncounter = true, Form = 1 }, // Deoxys @ Birth Island - new(386, 30, LG) { Location = 187, FatefulEncounter = true, Form = 2 }, // Deoxys @ Birth Island }; - private static readonly EncounterStatic3[] Encounter_FRLG = ArrayUtil.ConcatAll(Encounter_FRLG_Roam, Encounter_FRLG_Stationary); + public static readonly EncounterStatic3[] StaticFR = + { + // Celadon City Game Corner + new(063, 09, FR) { FixedBall = Ball.Poke, Location = 94 }, // Abra + new(035, 08, FR) { FixedBall = Ball.Poke, Location = 94 }, // Clefairy + new(123, 25, FR) { FixedBall = Ball.Poke, Location = 94 }, // Scyther + new(147, 18, FR) { FixedBall = Ball.Poke, Location = 94 }, // Dratini + new(137, 26, FR) { FixedBall = Ball.Poke, Location = 94 }, // Porygon + + new(386, 30, FR ) { Location = 187, FatefulEncounter = true, Form = 1 }, // Deoxys @ Birth Island + }; + + public static readonly EncounterStatic3[] StaticLG = + { + // Celadon City Game Corner + new(063, 09, FR) { FixedBall = Ball.Poke, Location = 94 }, // Abra + new(035, 08, FR) { FixedBall = Ball.Poke, Location = 94 }, // Clefairy + new(123, 25, FR) { FixedBall = Ball.Poke, Location = 94 }, // Scyther + new(147, 18, FR) { FixedBall = Ball.Poke, Location = 94 }, // Dratini + new(137, 26, FR) { FixedBall = Ball.Poke, Location = 94 }, // Porygon + + new(063, 07, LG) { FixedBall = Ball.Poke, Location = 94 }, // Abra + new(035, 12, LG) { FixedBall = Ball.Poke, Location = 94 }, // Clefairy + new(127, 18, LG) { FixedBall = Ball.Poke, Location = 94 }, // Pinsir + new(147, 24, LG) { FixedBall = Ball.Poke, Location = 94 }, // Dratini + new(137, 18, LG) { FixedBall = Ball.Poke, Location = 94 }, // Porygon + new(386, 30, LG) { Location = 187, FatefulEncounter = true, Form = 2 }, // Deoxys @ Birth Island + }; private static ReadOnlySpan TradeContest_Cool => new byte[] { 30, 05, 05, 05, 05, 10 }; private static ReadOnlySpan TradeContest_Beauty => new byte[] { 05, 30, 05, 05, 05, 10 }; @@ -86,24 +96,28 @@ internal static class Encounters3FRLG internal static readonly EncounterTrade3[] TradeGift_FRLG = { - new(FRLG, 0x00009CAE, 122, 05) { Ability = OnlyFirst, TID16 = 01985, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(20,15,17,24,23,22), Contest = TradeContest_Clever }, // Abra (Level 5 Breeding) -> Mr. Mime - new(FR , 0x4C970B89, 029, 05) { Ability = OnlyFirst, TID16 = 63184, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(22,18,25,19,15,22), Contest = TradeContest_Tough }, // Nidoran♀ - new( LG, 0x4C970B9E, 032, 05) { Ability = OnlyFirst, TID16 = 63184, SID16 = 00000, OTGender = 1, Gender = 0, IVs = new(19,25,18,22,22,15), Contest = TradeContest_Cool }, // Nidoran♂ * - new(FR , 0x00EECA15, 030, 16) { Ability = OnlyFirst, TID16 = 13637, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(22,25,18,19,22,15), Contest = TradeContest_Cute }, // Nidorina * - new( LG, 0x00EECA19, 033, 16) { Ability = OnlyFirst, TID16 = 13637, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(19,18,25,22,15,22), Contest = TradeContest_Tough }, // Nidorino * - new(FR , 0x451308AB, 108, 25) { Ability = OnlyFirst, TID16 = 01239, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(24,19,21,15,23,21), Contest = TradeContest_Tough }, // Golduck (Level 25) -> Lickitung * - new( LG, 0x451308AB, 108, 25) { Ability = OnlyFirst, TID16 = 01239, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(24,19,21,15,23,21), Contest = TradeContest_Tough }, // Slowbro (Level 25) -> Lickitung * - new(FRLG, 0x498A2E1D, 124, 20) { Ability = OnlyFirst, TID16 = 36728, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(18,17,18,22,25,21), Contest = TradeContest_Beauty }, // Poliwhirl (Level 20) -> Jynx - new(FRLG, 0x151943D7, 083, 03) { Ability = OnlyFirst, TID16 = 08810, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(20,25,21,24,15,20), Contest = TradeContest_Cool }, // Spearow (Level 3 Capture) -> Farfetch'd - new(FRLG, 0x06341016, 101, 03) { Ability = OnlySecond, TID16 = 50298, SID16 = 00000, OTGender = 0, Gender = 2, IVs = new(19,16,18,25,25,19), Contest = TradeContest_Cool }, // Raichu (Level 3) -> Electrode - new(FRLG, 0x5C77ECFA, 114, 05) { Ability = OnlyFirst, TID16 = 60042, SID16 = 00000, OTGender = 1, Gender = 0, IVs = new(22,17,25,16,23,20), Contest = TradeContest_Cute }, // Venonat (Level 5 Breeding) -> Tangela - new(FRLG, 0x482CAC89, 086, 05) { Ability = OnlyFirst, TID16 = 09853, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(24,15,22,16,23,22), Contest = TradeContest_Tough }, // Ponyta (Level 5 Breeding) -> Seel * + new(TradeNames, 00, FRLG, 0x00009CAE, 122, 05) { Ability = OnlyFirst, TID16 = 01985, OTGender = 0, Gender = 0, IVs = new(20,15,17,24,23,22), Contest = TradeContest_Clever }, // Abra (Level 5 Breeding) -> Mr. Mime + new(TradeNames, 07, FRLG, 0x498A2E1D, 124, 20) { Ability = OnlyFirst, TID16 = 36728, OTGender = 0, Gender = 1, IVs = new(18,17,18,22,25,21), Contest = TradeContest_Beauty }, // Poliwhirl (Level 20) -> Jynx + new(TradeNames, 08, FRLG, 0x151943D7, 083, 03) { Ability = OnlyFirst, TID16 = 08810, OTGender = 0, Gender = 0, IVs = new(20,25,21,24,15,20), Contest = TradeContest_Cool }, // Spearow (Level 3 Capture) -> Farfetch'd + new(TradeNames, 09, FRLG, 0x06341016, 101, 03) { Ability = OnlySecond, TID16 = 50298, OTGender = 0, Gender = 2, IVs = new(19,16,18,25,25,19), Contest = TradeContest_Cool }, // Raichu (Level 3) -> Electrode + new(TradeNames, 10, FRLG, 0x5C77ECFA, 114, 05) { Ability = OnlyFirst, TID16 = 60042, OTGender = 1, Gender = 0, IVs = new(22,17,25,16,23,20), Contest = TradeContest_Cute }, // Venonat (Level 5 Breeding) -> Tangela + new(TradeNames, 11, FRLG, 0x482CAC89, 086, 05) { Ability = OnlyFirst, TID16 = 09853, OTGender = 0, Gender = 0, IVs = new(24,15,22,16,23,22), Contest = TradeContest_Tough }, // Ponyta (Level 5 Breeding) -> Seel * // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. }; - private const string tradeFRLG = "tradefrlg"; - private static readonly string[][] TradeFRLG = Util.GetLanguageStrings7(tradeFRLG); + internal static readonly EncounterTrade3[] TradeGift_FR = + { + new(TradeNames, 01, FR , 0x4C970B89, 029, 05) { Ability = OnlyFirst, TID16 = 63184, OTGender = 1, Gender = 1, IVs = new(22,18,25,19,15,22), Contest = TradeContest_Tough }, // Nidoran♀ + new(TradeNames, 03, FR , 0x00EECA15, 030, 16) { Ability = OnlyFirst, TID16 = 13637, OTGender = 0, Gender = 1, IVs = new(22,25,18,19,22,15), Contest = TradeContest_Cute }, // Nidorina * + new(TradeNames, 05, FR , 0x451308AB, 108, 25) { Ability = OnlyFirst, TID16 = 01239, OTGender = 0, Gender = 0, IVs = new(24,19,21,15,23,21), Contest = TradeContest_Tough }, // Golduck (Level 25) -> Lickitung * + // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. + }; - internal static readonly EncounterStatic3[] StaticFR = GetEncounters(Encounter_FRLG, FR); - internal static readonly EncounterStatic3[] StaticLG = GetEncounters(Encounter_FRLG, LG); + internal static readonly EncounterTrade3[] TradeGift_LG = + { + new(TradeNames, 02, LG, 0x4C970B9E, 032, 05) { Ability = OnlyFirst, TID16 = 63184, OTGender = 1, Gender = 0, IVs = new(19,25,18,22,22,15), Contest = TradeContest_Cool }, // Nidoran♂ * + new(TradeNames, 04, LG, 0x00EECA19, 033, 16) { Ability = OnlyFirst, TID16 = 13637, OTGender = 0, Gender = 0, IVs = new(19,18,25,22,15,22), Contest = TradeContest_Tough }, // Nidorino * + new(TradeNames, 06, LG, 0x451308AB, 108, 25) { Ability = OnlyFirst, TID16 = 01239, OTGender = 0, Gender = 0, IVs = new(24,19,21,15,23,21), Contest = TradeContest_Tough }, // Slowbro (Level 25) -> Lickitung * + // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3RSE.cs b/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3RSE.cs index f251af42b..8e33267ea 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3RSE.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3RSE.cs @@ -18,55 +18,87 @@ internal static class Encounters3RSE private static EncounterArea3[] GetRegular(string resource, string ident, GameVersion game) => EncounterArea3.GetAreas(Get(resource, ident), game); private static EncounterArea3[] GetSwarm(string resource, string ident, GameVersion game) => EncounterArea3.GetAreasSwarm(Get(resource, ident), game); - static Encounters3RSE() => MarkEncounterTradeStrings(TradeGift_RSE, TradeRSE); + private static readonly string[] TrainersPikachu = { string.Empty, "コロシアム", "COLOS", "COLOSSEUM", "ARENA", "COLOSSEUM", string.Empty, "CLAUDIO" }; + private static readonly string[] TrainersCelebi = { string.Empty, "アゲト", "AGATE", "SAMARAGD", "SOFO", "EMERITAE", string.Empty, "ÁGATA" }; + private static readonly string[] TrainersMattle = { string.Empty, "バトルやま", "MATTLE", "MT BATAILL", "MONTE LOTT", "DUELLBERG", string.Empty, "ERNESTO" }; // truncated on ck3->pk3 transfer - private static readonly EncounterStatic3[] Encounter_RSE_Roam = + internal static readonly EncounterGift3Colo[] ColoGiftsR = { - new(380, 40, S) { Roaming = true, Location = 016 }, // Latias - new(380, 40, E) { Roaming = true, Location = 016 }, // Latias - new(381, 40, R) { Roaming = true, Location = 016 }, // Latios - new(381, 40, E) { Roaming = true, Location = 016 }, // Latios + // In-Game Bonus Disk (Japan only) + new(025, 10, TrainersPikachu, R) { Location = 255, TID16 = 31121, OT_Gender = 0 }, // Colosseum Pikachu bonus gift + new(251, 10, TrainersCelebi, R) { Location = 255, TID16 = 31121, OT_Gender = 1 }, // Ageto Celebi bonus gift }; - private static readonly EncounterStatic3[] Encounter_RSE_Regular = + internal static readonly EncounterGift3Colo[] ColoGiftsS = + { + // In-Game without Bonus Disk + new(250, 70, TrainersMattle, S) { Location = 255, TID16 = 10048, OT_Gender = 0, Moves = new(105, 126, 241, 129) }, // Ho-oh @ Mt. Battle + }; + + private const string tradeRSE = "traderse"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings7(tradeRSE); + + public static readonly EncounterStatic3[] StaticRSE = { // Starters - new(152, 05, E ) { Gift = true, Location = 000 }, // Chikorita @ Littleroot Town - new(155, 05, E ) { Gift = true, Location = 000 }, // Cyndaquil - new(158, 05, E ) { Gift = true, Location = 000 }, // Totodile - new(252, 05, RSE) { Gift = true, Location = 016 }, // Treecko @ Route 101 - new(255, 05, RSE) { Gift = true, Location = 016 }, // Torchic - new(258, 05, RSE) { Gift = true, Location = 016 }, // Mudkip + new(252, 05, RSE) { FixedBall = Ball.Poke, Location = 016 }, // Treecko @ Route 101 + new(255, 05, RSE) { FixedBall = Ball.Poke, Location = 016 }, // Torchic + new(258, 05, RSE) { FixedBall = Ball.Poke, Location = 016 }, // Mudkip // Fossil @ Rustboro City - new(345, 20, RSE) { Gift = true, Location = 010 }, // Lileep - new(347, 20, RSE) { Gift = true, Location = 010 }, // Anorith + new(345, 20, RSE) { FixedBall = Ball.Poke, Location = 010 }, // Lileep + new(347, 20, RSE) { FixedBall = Ball.Poke, Location = 010 }, // Anorith // Gift - new(351, 25, RSE) { Gift = true, Location = 034 }, // Castform @ Weather Institute - new(374, 05, RSE) { Gift = true, Location = 013 }, // Beldum @ Mossdeep City - new(360, 05, RSE) { Gift = true, EggLocation = 253 }, // Wynaut Egg + new(351, 25, RSE) { FixedBall = Ball.Poke, Location = 034 }, // Castform @ Weather Institute + new(374, 05, RSE) { FixedBall = Ball.Poke, Location = 013 }, // Beldum @ Mossdeep City + new(360, 05, RSE) { FixedBall = Ball.Poke, Location = 253, EggEncounter = true }, // Wynaut Egg // Stationary new(352, 30, RSE) { Location = 034 }, // Kecleon @ Route 119 new(352, 30, RSE) { Location = 035 }, // Kecleon @ Route 120 - new(101, 30, RS ) { Location = 066 }, // Electrode @ Hideout (R:Magma Hideout/S:Aqua Hideout) - new(101, 30, E ) { Location = 197 }, // Electrode @ Aqua Hideout - new(185, 40, E ) { Location = 058 }, // Sudowoodo @ Battle Frontier // Stationary Lengendary new(377, 40, RSE) { Location = 082 }, // Regirock @ Desert Ruins new(378, 40, RSE) { Location = 081 }, // Regice @ Island Cave new(379, 40, RSE) { Location = 083 }, // Registeel @ Ancient Tomb - new(380, 50, R ) { Location = 073 }, // Latias @ Southern Island - new(380, 50, E) { Location = 073, FatefulEncounter = true }, // Latias @ Southern Island - new(381, 50, S ) { Location = 073 }, // Latios @ Southern Island - new(381, 50, E) { Location = 073, FatefulEncounter = true }, // Latios @ Southern Island - new(382, 45, S ) { Location = 072 }, // Kyogre @ Cave of Origin - new(382, 70, E) { Location = 203 }, // Kyogre @ Marine Cave - new(383, 45, R ) { Location = 072 }, // Groudon @ Cave of Origin - new(383, 70, E) { Location = 205 }, // Groudon @ Terra Cave new(384, 70, RSE) { Location = 085 }, // Rayquaza @ Sky Pillar + }; + + public static readonly EncounterStatic3[] StaticR = + { + new(381, 40, R) { Roaming = true, Location = 016 }, // Latios + new(380, 50, R) { Location = 073 }, // Latias @ Southern Island + new(383, 45, R) { Location = 072 }, // Groudon @ Cave of Origin + + new(101, 30, R) { Location = 066 }, // Electrode @ Hideout (R:Magma Hideout/S:Aqua Hideout) + }; + + public static readonly EncounterStatic3[] StaticS = + { + new(380, 40, S) { Roaming = true, Location = 016 }, // Latias + new(381, 50, S) { Location = 073 }, // Latios @ Southern Island + new(382, 45, S) { Location = 072 }, // Kyogre @ Cave of Origin + + new(101, 30, S) { Location = 066 }, // Electrode @ Hideout (R:Magma Hideout/S:Aqua Hideout) + }; + + public static readonly EncounterStatic3[] StaticE = + { + new(380, 40, E) { Roaming = true, Location = 016 }, // Latias + new(381, 40, E) { Roaming = true, Location = 016 }, // Latios + new(382, 70, E) { Location = 203 }, // Kyogre @ Marine Cave + new(383, 70, E) { Location = 205 }, // Groudon @ Terra Cave + + new(101, 30, E) { Location = 197 }, // Electrode @ Aqua Hideout + + // Starters + new(152, 05, E) { FixedBall = Ball.Poke, Location = 000 }, // Chikorita @ Littleroot Town + new(155, 05, E) { FixedBall = Ball.Poke, Location = 000 }, // Cyndaquil + new(158, 05, E) { FixedBall = Ball.Poke, Location = 000 }, // Totodile + new(185, 40, E) { Location = 058 }, // Sudowoodo @ Battle Frontier + new(380, 50, E) { Location = 073, FatefulEncounter = true }, // Latias @ Southern Island + new(381, 50, E) { Location = 073, FatefulEncounter = true }, // Latios @ Southern Island // Event new(151, 30, E) { Location = 201, FatefulEncounter = true }, // Mew @ Faraway Island (Unreleased outside of Japan) @@ -75,30 +107,25 @@ internal static class Encounters3RSE new(386, 30, E) { Location = 200, FatefulEncounter = true, Form = 3 }, // Deoxys @ Birth Island }; - private static readonly EncounterStatic3[] Encounter_RSE = ArrayUtil.ConcatAll(Encounter_RSE_Roam, Encounter_RSE_Regular); - private static ReadOnlySpan TradeContest_Cool => new byte[] { 30, 05, 05, 05, 05, 10 }; private static ReadOnlySpan TradeContest_Beauty => new byte[] { 05, 30, 05, 05, 05, 10 }; private static ReadOnlySpan TradeContest_Cute => new byte[] { 05, 05, 30, 05, 05, 10 }; private static ReadOnlySpan TradeContest_Clever => new byte[] { 05, 05, 05, 30, 05, 10 }; private static ReadOnlySpan TradeContest_Tough => new byte[] { 05, 05, 05, 05, 30, 10 }; - internal static readonly EncounterTrade3[] TradeGift_RSE = + internal static readonly EncounterTrade3[] TradeGift_RS = { - new(RS, 0x00009C40, 296, 05) { Ability = OnlySecond, TID16 = 49562, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(5,5,4,4,4,4), Contest = TradeContest_Tough }, // Slakoth (Level 5 Breeding) -> Makuhita - new(RS, 0x498A2E17, 300, 03) { Ability = OnlyFirst, TID16 = 02259, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(5,4,4,5,4,4), Contest = TradeContest_Cute }, // Pikachu (Level 3 Viridian Forest) -> Skitty - new(RS, 0x4C970B7F, 222, 21) { Ability = OnlySecond, TID16 = 50183, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(4,4,5,4,4,5), Contest = TradeContest_Beauty }, // Bellossom (Level 21 Oddish -> Gloom -> Bellossom) -> Corsola - new(E , 0x00000084, 273, 04) { Ability = OnlySecond, TID16 = 38726, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(5,4,5,4,4,4), Contest = TradeContest_Cool }, // Ralts (Level 4 Route 102) -> Seedot - new(E , 0x0000006F, 311, 05) { Ability = OnlyFirst, TID16 = 08460, SID16 = 00001, OTGender = 0, Gender = 1, IVs = new(4,4,4,5,5,4), Contest = TradeContest_Cute }, // Volbeat (Level 5 Breeding) -> Plusle - new(E , 0x0000007F, 116, 05) { Ability = OnlyFirst, TID16 = 46285, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(5,4,4,4,5,4), Contest = TradeContest_Tough }, // Bagon (Level 5 Breeding) -> Horsea* - new(E , 0x0000008B, 052, 03) { Ability = OnlyFirst, TID16 = 25945, SID16 = 00001, OTGender = 1, Gender = 0, IVs = new(4,5,4,5,4,4), Contest = TradeContest_Clever }, // Skitty (Level 3 Trade)-> Meowth* - // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. + new(TradeNames, 00, RS, 0x00009C40, 296, 05) { Ability = OnlySecond, TID16 = 49562, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(5,5,4,4,4,4), Contest = TradeContest_Tough }, // Slakoth (Level 5 Breeding) -> Makuhita + new(TradeNames, 01, RS, 0x498A2E17, 300, 03) { Ability = OnlyFirst, TID16 = 02259, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(5,4,4,5,4,4), Contest = TradeContest_Cute }, // Pikachu (Level 3 Viridian Forest) -> Skitty + new(TradeNames, 02, RS, 0x4C970B7F, 222, 21) { Ability = OnlySecond, TID16 = 50183, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(4,4,5,4,4,5), Contest = TradeContest_Beauty }, // Bellossom (Level 21 Oddish -> Gloom -> Bellossom) -> Corsola }; - private const string tradeRSE = "traderse"; - private static readonly string[][] TradeRSE = Util.GetLanguageStrings7(tradeRSE); - - internal static readonly EncounterStatic3[] StaticR = GetEncounters(Encounter_RSE, R); - internal static readonly EncounterStatic3[] StaticS = GetEncounters(Encounter_RSE, S); - internal static readonly EncounterStatic3[] StaticE = GetEncounters(Encounter_RSE, E); + internal static readonly EncounterTrade3[] TradeGift_E = + { + new(TradeNames, 03, E , 0x00000084, 273, 04) { Ability = OnlySecond, TID16 = 38726, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(5,4,5,4,4,4), Contest = TradeContest_Cool }, // Ralts (Level 4 Route 102) -> Seedot + new(TradeNames, 04, E , 0x0000006F, 311, 05) { Ability = OnlyFirst, TID16 = 08460, SID16 = 00001, OTGender = 0, Gender = 1, IVs = new(4,4,4,5,5,4), Contest = TradeContest_Cute }, // Volbeat (Level 5 Breeding) -> Plusle + new(TradeNames, 05, E , 0x0000007F, 116, 05) { Ability = OnlyFirst, TID16 = 46285, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(5,4,4,4,5,4), Contest = TradeContest_Tough }, // Bagon (Level 5 Breeding) -> Horsea* + new(TradeNames, 06, E , 0x0000008B, 052, 03) { Ability = OnlyFirst, TID16 = 25945, SID16 = 00001, OTGender = 1, Gender = 0, IVs = new(4,5,4,5,4,4), Contest = TradeContest_Clever }, // Skitty (Level 3 Trade)-> Meowth* + // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3XD.cs b/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3XD.cs index d6e16d7ec..a98da1097 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3XD.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen3/Encounters3XD.cs @@ -4,115 +4,124 @@ namespace PKHeX.Core; internal static class Encounters3XD { - private static readonly EncounterStatic3[] Encounter_XDGift = + internal static readonly EncounterStatic3XD[] Gifts = { - new(133, 10, GameVersion.XD) { FatefulEncounter = true, Gift = true, Location = 000, Moves = new(044) }, // Eevee (Bite) - new(152, 05, GameVersion.XD) { FatefulEncounter = true, Gift = true, Location = 016, Moves = new(246,033,045,338) }, // Chikorita - new(155, 05, GameVersion.XD) { FatefulEncounter = true, Gift = true, Location = 016, Moves = new(179,033,043,307) }, // Cyndaquil - new(158, 05, GameVersion.XD) { FatefulEncounter = true, Gift = true, Location = 016, Moves = new(242,010,043,308) }, // Totodile + new(133, 10) { FixedBall = Ball.Poke, FatefulEncounter = true, Location = 000, Moves = new(033,039,044,028) }, // Eevee (Bite) + new(152, 05) { FixedBall = Ball.Poke, FatefulEncounter = true, Location = 016, Moves = new(246,033,045,338) }, // Chikorita + new(155, 05) { FixedBall = Ball.Poke, FatefulEncounter = true, Location = 016, Moves = new(179,033,043,307) }, // Cyndaquil + new(158, 05) { FixedBall = Ball.Poke, FatefulEncounter = true, Location = 016, Moves = new(242,010,043,308) }, // Totodile }; - private static readonly EncounterStaticShadow[] Encounter_XD = + private static readonly string[] Hordel = { string.Empty, "ダニー", "HORDEL", "VOLKER", "ODINO", "HORAZ", string.Empty, "HORDEL" }; + private static readonly string[] Zaprong = { string.Empty, "コンセント", "ZAPRONG", "ZAPRONG", "ZAPRONG", "ZAPRONG", string.Empty, "ZAPRONG" }; + private static string[] Duking => Encounters3Colo.TrainerNameDuking; + + internal static readonly EncounterTrade3XD[] Trades = { - new(GameVersion.XD, 01, 03000, First) { FatefulEncounter = true, Species = 216, Level = 11, Moves = new(216,287,122,232), Location = 143, Gift = true }, // Teddiursa: Cipher Peon Naps @ Pokémon HQ Lab -- treat as Gift as it can only be captured in a Poké Ball - new(GameVersion.XD, 02, 02000, Vulpix) { FatefulEncounter = true, Species = 037, Level = 18, Moves = new(257,204,052,091), Location = 109 }, // Vulpix: Cipher Peon Mesin @ ONBS Building - new(GameVersion.XD, 03, 01500, Spheal) { FatefulEncounter = true, Species = 363, Level = 17, Moves = new(062,204,055,189), Location = 011 }, // Spheal: Cipher Peon Blusix @ Cipher Lab - new(GameVersion.XD, 03, 01500, Spheal) { FatefulEncounter = true, Species = 363, Level = 17, Moves = new(062,204,055,189), Location = 100 }, // Spheal: Cipher Peon Blusix @ Phenac City - new(GameVersion.XD, 04, 01500, First) { FatefulEncounter = true, Species = 343, Level = 17, Moves = new(317,287,189,060), Location = 011 }, // Baltoy: Cipher Peon Browsix @ Cipher Lab - new(GameVersion.XD, 04, 01500, First) { FatefulEncounter = true, Species = 343, Level = 17, Moves = new(317,287,189,060), Location = 096 }, // Baltoy: Cipher Peon Browsix @ Phenac City - new(GameVersion.XD, 05, 01500, First) { FatefulEncounter = true, Species = 179, Level = 17, Moves = new(034,215,084,086), Location = 011 }, // Mareep: Cipher Peon Yellosix @ Cipher Lab - new(GameVersion.XD, 05, 01500, First) { FatefulEncounter = true, Species = 179, Level = 17, Moves = new(034,215,084,086), Location = 096 }, // Mareep: Cipher Peon Yellosix @ Phenac City - new(GameVersion.XD, 06, 01500, Gulpin) { FatefulEncounter = true, Species = 316, Level = 17, Moves = new(351,047,124,092), Location = 011 }, // Gulpin: Cipher Peon Purpsix @ Cipher Lab - new(GameVersion.XD, 06, 01500, Gulpin) { FatefulEncounter = true, Species = 316, Level = 17, Moves = new(351,047,124,092), Location = 100 }, // Gulpin: Cipher Peon Purpsix @ Phenac City - new(GameVersion.XD, 07, 01500, Seedot) { FatefulEncounter = true, Species = 273, Level = 17, Moves = new(202,287,331,290), Location = 011 }, // Seedot: Cipher Peon Greesix @ Cipher Lab - new(GameVersion.XD, 07, 01500, Seedot) { FatefulEncounter = true, Species = 273, Level = 17, Moves = new(202,287,331,290), Location = 100 }, // Seedot: Cipher Peon Greesix @ Phenac City - new(GameVersion.XD, 08, 01500, Spinarak) { FatefulEncounter = true, Species = 167, Level = 14, Moves = new(091,287,324,101), Location = 010 }, // Spinarak: Cipher Peon Nexir @ Cipher Lab - new(GameVersion.XD, 09, 01500, Numel) { FatefulEncounter = true, Species = 322, Level = 14, Moves = new(036,204,091,052), Location = 009 }, // Numel: Cipher Peon Solox @ Cipher Lab - new(GameVersion.XD, 10, 01700, First) { FatefulEncounter = true, Species = 318, Level = 15, Moves = new(352,287,184,044), Location = 008 }, // Carvanha: Cipher Peon Cabol @ Cipher Lab - new(GameVersion.XD, 11, 03000, Roselia) { FatefulEncounter = true, Species = 315, Level = 22, Moves = new(345,186,320,073), Location = 094 }, // Roselia: Cipher Peon Fasin @ Phenac City - new(GameVersion.XD, 12, 02500, Delcatty) { FatefulEncounter = true, Species = 301, Level = 18, Moves = new(290,186,213,351), Location = 008 }, // Delcatty: Cipher Admin Lovrina @ Cipher Lab - new(GameVersion.XD, 13, 04000, Nosepass) { FatefulEncounter = true, Species = 299, Level = 26, Moves = new(085,270,086,157), Location = 090 }, // Nosepass: Wanderer Miror B. @ Poké Spots - new(GameVersion.XD, 14, 01500, First) { FatefulEncounter = true, Species = 228, Level = 17, Moves = new(185,204,052,046), Location = 100 }, // Houndour: Cipher Peon Resix @ Phenac City - new(GameVersion.XD, 14, 01500, First) { FatefulEncounter = true, Species = 228, Level = 17, Moves = new(185,204,052,046), Location = 011 }, // Houndour: Cipher Peon Resix @ Cipher Lab - new(GameVersion.XD, 15, 02000, Makuhita) { FatefulEncounter = true, Species = 296, Level = 18, Moves = new(280,287,292,317), Location = 109 }, // Makuhita: Cipher Peon Torkin @ ONBS Building - new(GameVersion.XD, 16, 02200, Duskull) { FatefulEncounter = true, Species = 355, Level = 19, Moves = new(247,270,310,109), Location = 110 }, // Duskull: Cipher Peon Lobar @ ONBS Building - new(GameVersion.XD, 17, 02200, Ralts) { FatefulEncounter = true, Species = 280, Level = 20, Moves = new(351,047,115,093), Location = 119 }, // Ralts: Cipher Peon Feldas @ ONBS Building - new(GameVersion.XD, 18, 02500, Mawile) { FatefulEncounter = true, Species = 303, Level = 22, Moves = new(206,047,011,334), Location = 111 }, // Mawile: Cipher Cmdr Exol @ ONBS Building - new(GameVersion.XD, 19, 02500, Snorunt) { FatefulEncounter = true, Species = 361, Level = 20, Moves = new(352,047,044,196), Location = 097 }, // Snorunt: Cipher Peon Exinn @ Phenac City - new(GameVersion.XD, 20, 02500, Pineco) { FatefulEncounter = true, Species = 204, Level = 20, Moves = new(042,287,191,068), Location = 096 }, // Pineco: Cipher Peon Gonrap @ Phenac City - new(GameVersion.XD, 21, 02500, Swinub) { FatefulEncounter = true, Species = 220, Level = 22, Moves = new(246,204,054,341), Location = 100 }, // Swinub: Cipher Peon Greck @ Phenac City - new(GameVersion.XD, 22, 02500, Natu) { FatefulEncounter = true, Species = 177, Level = 22, Moves = new(248,226,101,332), Location = 094 }, // Natu: Cipher Peon Eloin @ Phenac City - new(GameVersion.XD, 23, 01800, Shroomish) { FatefulEncounter = true, Species = 285, Level = 15, Moves = new(206,287,072,078), Location = 008 }, // Shroomish: Cipher R&D Klots @ Cipher Lab - new(GameVersion.XD, 24, 03500, Meowth) { FatefulEncounter = true, Species = 052, Level = 22, Moves = new(163,047,006,044), Location = 094 }, // Meowth: Cipher Peon Fostin @ Phenac City - new(GameVersion.XD, 25, 04500, Spearow) { FatefulEncounter = true, Species = 021, Level = 22, Moves = new(206,226,043,332), Location = 107 }, // Spearow: Cipher Peon Ezin @ Phenac Stadium - new(GameVersion.XD, 26, 03000, Grimer) { FatefulEncounter = true, Species = 088, Level = 23, Moves = new(188,270,325,107), Location = 107 }, // Grimer: Cipher Peon Faltly @ Phenac Stadium - new(GameVersion.XD, 27, 03500, Seel) { FatefulEncounter = true, Species = 086, Level = 23, Moves = new(057,270,219,058), Location = 107 }, // Seel: Cipher Peon Egrog @ Phenac Stadium - new(GameVersion.XD, 28, 05000, Lunatone) { FatefulEncounter = true, Species = 337, Level = 25, Moves = new(094,226,240,317), Location = 107 }, // Lunatone: Cipher Admin Snattle @ Phenac Stadium - new(GameVersion.XD, 29, 02500, Voltorb) { FatefulEncounter = true, Species = 100, Level = 19, Moves = new(243,287,209,129), Location = 092 }, // Voltorb: Wanderer Miror B. @ Cave Poké Spot - new(GameVersion.XD, 30, 05000, First) { FatefulEncounter = true, Species = 335, Level = 28, Moves = new(280,287,068,306), Location = 071 }, // Zangoose: Thug Zook @ Cipher Key Lair - new(GameVersion.XD, 31, 04000, Growlithe) { FatefulEncounter = true, Species = 058, Level = 28, Moves = new(053,204,044,036), Location = 064 }, // Growlithe: Cipher Peon Humah @ Cipher Key Lair - new(GameVersion.XD, 32, 04000, Paras) { FatefulEncounter = true, Species = 046, Level = 28, Moves = new(147,287,163,206), Location = 064 }, // Paras: Cipher Peon Humah @ Cipher Key Lair - new(GameVersion.XD, 33, 04000, First) { FatefulEncounter = true, Species = 090, Level = 29, Moves = new(036,287,057,062), Location = 065 }, // Shellder: Cipher Peon Gorog @ Cipher Key Lair - new(GameVersion.XD, 34, 04500, First) { FatefulEncounter = true, Species = 015, Level = 30, Moves = new(188,226,041,014), Location = 066 }, // Beedrill: Cipher Peon Lok @ Cipher Key Lair - new(GameVersion.XD, 35, 04000, Pidgeotto) { FatefulEncounter = true, Species = 017, Level = 30, Moves = new(017,287,211,297), Location = 066 }, // Pidgeotto: Cipher Peon Lok @ Cipher Key Lair - new(GameVersion.XD, 36, 04000, Butterfree){ FatefulEncounter = true, Species = 012, Level = 30, Moves = new(094,234,079,332), Location = 067 }, // Butterfree: Cipher Peon Targ @ Cipher Key Lair - new(GameVersion.XD, 37, 04000, Tangela) { FatefulEncounter = true, Species = 114, Level = 30, Moves = new(076,234,241,275), Location = 067 }, // Tangela: Cipher Peon Targ @ Cipher Key Lair - new(GameVersion.XD, 38, 06000, Raticate) { FatefulEncounter = true, Species = 020, Level = 34, Moves = new(162,287,184,158), Location = 076 }, // Raticate: Chaser Furgy @ Citadark Isle - new(GameVersion.XD, 39, 04000, Venomoth) { FatefulEncounter = true, Species = 049, Level = 32, Moves = new(318,287,164,094), Location = 070 }, // Venomoth: Cipher Peon Angic @ Cipher Key Lair - new(GameVersion.XD, 40, 04000, Weepinbell){ FatefulEncounter = true, Species = 070, Level = 32, Moves = new(345,234,188,230), Location = 070 }, // Weepinbell: Cipher Peon Angic @ Cipher Key Lair - new(GameVersion.XD, 41, 05000, Arbok) { FatefulEncounter = true, Species = 024, Level = 33, Moves = new(188,287,137,044), Location = 070 }, // Arbok: Cipher Peon Smarton @ Cipher Key Lair - new(GameVersion.XD, 42, 06000, Primeape) { FatefulEncounter = true, Species = 057, Level = 34, Moves = new(238,270,116,179), Location = 069 }, // Primeape: Cipher Admin Gorigan @ Cipher Key Lair - new(GameVersion.XD, 43, 05500, Hypno) { FatefulEncounter = true, Species = 097, Level = 34, Moves = new(094,226,096,247), Location = 069 }, // Hypno: Cipher Admin Gorigan @ Cipher Key Lair - new(GameVersion.XD, 44, 06500, Golduck) { FatefulEncounter = true, Species = 055, Level = 33, Moves = new(127,204,244,280), Location = 088 }, // Golduck: Navigator Abson @ Citadark Isle - new(GameVersion.XD, 45, 07000, Sableye) { FatefulEncounter = true, Species = 302, Level = 33, Moves = new(247,270,185,105), Location = 088 }, // Sableye: Navigator Abson @ Citadark Isle - new(GameVersion.XD, 46, 04500, Magneton) { FatefulEncounter = true, Species = 082, Level = 30, Moves = new(038,287,240,087), Location = 067 }, // Magneton: Cipher Peon Snidle @ Cipher Key Lair - new(GameVersion.XD, 47, 08000, Dodrio) { FatefulEncounter = true, Species = 085, Level = 34, Moves = new(065,226,097,161), Location = 076 }, // Dodrio: Chaser Furgy @ Citadark Isle - new(GameVersion.XD, 48, 05500, Farfetchd) { FatefulEncounter = true, Species = 083, Level = 36, Moves = new(163,226,014,332), Location = 076 }, // Farfetch'd: Cipher Admin Lovrina @ Citadark Isle - new(GameVersion.XD, 49, 06500, Altaria) { FatefulEncounter = true, Species = 334, Level = 36, Moves = new(225,215,076,332), Location = 076 }, // Altaria: Cipher Admin Lovrina @ Citadark Isle - new(GameVersion.XD, 50, 06000, Kangaskhan){ FatefulEncounter = true, Species = 115, Level = 35, Moves = new(089,047,039,146), Location = 085 }, // Kangaskhan: Cipher Peon Litnar @ Citadark Isle - new(GameVersion.XD, 51, 07000, Banette) { FatefulEncounter = true, Species = 354, Level = 37, Moves = new(185,270,247,174), Location = 085 }, // Banette: Cipher Peon Litnar @ Citadark Isle - new(GameVersion.XD, 52, 07000, Magmar) { FatefulEncounter = true, Species = 126, Level = 36, Moves = new(126,266,238,009), Location = 077 }, // Magmar: Cipher Peon Grupel @ Citadark Isle - new(GameVersion.XD, 53, 07000, Pinsir) { FatefulEncounter = true, Species = 127, Level = 35, Moves = new(012,270,206,066), Location = 077 }, // Pinsir: Cipher Peon Grupel @ Citadark Isle - new(GameVersion.XD, 54, 05500, Magcargo) { FatefulEncounter = true, Species = 219, Level = 38, Moves = new(257,287,089,053), Location = 080 }, // Magcargo: Cipher Peon Kolest @ Citadark Isle - new(GameVersion.XD, 55, 06000, Rapidash) { FatefulEncounter = true, Species = 078, Level = 40, Moves = new(076,226,241,053), Location = 080 }, // Rapidash: Cipher Peon Kolest @ Citadark Isle - new(GameVersion.XD, 56, 06000, Hitmonchan){ FatefulEncounter = true, Species = 107, Level = 38, Moves = new(005,270,170,327), Location = 081 }, // Hitmonchan: Cipher Peon Karbon @ Citadark Isle - new(GameVersion.XD, 57, 07000, Hitmonlee) { FatefulEncounter = true, Species = 106, Level = 38, Moves = new(136,287,170,025), Location = 081 }, // Hitmonlee: Cipher Peon Petro @ Citadark Isle - new(GameVersion.XD, 58, 05000, Lickitung) { FatefulEncounter = true, Species = 108, Level = 38, Moves = new(038,270,111,205), Location = 084 }, // Lickitung: Cipher Peon Geftal @ Citadark Isle - new(GameVersion.XD, 59, 08000, Scyther) { FatefulEncounter = true, Species = 123, Level = 40, Moves = new(013,234,318,163), Location = 084 }, // Scyther: Cipher Peon Leden @ Citadark Isle - new(GameVersion.XD, 60, 04000, Chansey) { FatefulEncounter = true, Species = 113, Level = 39, Moves = new(085,186,135,285), Location = 084 }, // Chansey: Cipher Peon Leden @ Citadark Isle - new(GameVersion.XD, 60, 04000, Chansey) { FatefulEncounter = true, Species = 113, Level = 39, Moves = new(085,186,135,285), Location = 087 }, // Chansey: Cipher Peon Leden @ Citadark Isle - new(GameVersion.XD, 61, 07500, Solrock) { FatefulEncounter = true, Species = 338, Level = 41, Moves = new(094,226,241,322), Location = 087 }, // Solrock: Cipher Admin Snattle @ Citadark Isle - new(GameVersion.XD, 62, 07500, Starmie) { FatefulEncounter = true, Species = 121, Level = 41, Moves = new(127,287,058,105), Location = 087 }, // Starmie: Cipher Admin Snattle @ Citadark Isle - new(GameVersion.XD, 63, 07000, Electabuzz){ FatefulEncounter = true, Species = 125, Level = 43, Moves = new(238,266,086,085), Location = 087 }, // Electabuzz: Cipher Admin Ardos @ Citadark Isle - new(GameVersion.XD, 64, 07000, First) { FatefulEncounter = true, Species = 277, Level = 43, Moves = new(143,226,097,263), Location = 087 }, // Swellow: Cipher Admin Ardos @ Citadark Isle - new(GameVersion.XD, 65, 09000, Snorlax) { FatefulEncounter = true, Species = 143, Level = 43, Moves = new(090,287,174,034), Location = 087 }, // Snorlax: Cipher Admin Ardos @ Citadark Isle - new(GameVersion.XD, 66, 07500, Poliwrath) { FatefulEncounter = true, Species = 062, Level = 42, Moves = new(056,270,240,280), Location = 087 }, // Poliwrath: Cipher Admin Gorigan @ Citadark Isle - new(GameVersion.XD, 67, 06500, MrMime) { FatefulEncounter = true, Species = 122, Level = 42, Moves = new(094,266,227,009), Location = 087 }, // Mr. Mime: Cipher Admin Gorigan @ Citadark Isle - new(GameVersion.XD, 68, 05000, Dugtrio) { FatefulEncounter = true, Species = 051, Level = 40, Moves = new(089,204,201,161), Location = 075 }, // Dugtrio: Cipher Peon Kolax @ Citadark Isle - new(GameVersion.XD, 69, 07000, Manectric) { FatefulEncounter = true, Species = 310, Level = 44, Moves = new(087,287,240,044), Location = 073 }, // Manectric: Cipher Admin Eldes @ Citadark Isle - new(GameVersion.XD, 70, 09000, Salamence) { FatefulEncounter = true, Species = 373, Level = 50, Moves = new(337,287,349,332), Location = 073 }, // Salamence: Cipher Admin Eldes @ Citadark Isle - new(GameVersion.XD, 71, 06500, Marowak) { FatefulEncounter = true, Species = 105, Level = 44, Moves = new(089,047,014,157), Location = 073 }, // Marowak: Cipher Admin Eldes @ Citadark Isle - new(GameVersion.XD, 72, 06000, Lapras) { FatefulEncounter = true, Species = 131, Level = 44, Moves = new(056,215,240,059), Location = 073 }, // Lapras: Cipher Admin Eldes @ Citadark Isle - new(GameVersion.XD, 73, 12000, First) { FatefulEncounter = true, Species = 249, Level = 50, Moves = new(354,297,089,056), Location = 074 }, // Lugia: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 74, 10000, Zapdos) { FatefulEncounter = true, Species = 145, Level = 50, Moves = new(326,226,319,085), Location = 074 }, // Zapdos: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 75, 10000, Moltres) { FatefulEncounter = true, Species = 146, Level = 50, Moves = new(326,234,261,053), Location = 074 }, // Moltres: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 76, 10000, Articuno) { FatefulEncounter = true, Species = 144, Level = 50, Moves = new(326,215,114,058), Location = 074 }, // Articuno: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 77, 09000, Tauros) { FatefulEncounter = true, Species = 128, Level = 46, Moves = new(089,287,039,034), Location = 074 }, // Tauros: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 78, 07000, First) { FatefulEncounter = true, Species = 112, Level = 46, Moves = new(224,270,184,089), Location = 074 }, // Rhydon: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 79, 09000, Exeggutor) { FatefulEncounter = true, Species = 103, Level = 46, Moves = new(094,287,095,246), Location = 074 }, // Exeggutor: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 80, 09000, Dragonite) { FatefulEncounter = true, Species = 149, Level = 55, Moves = new(063,215,349,089), Location = 162 }, // Dragonite: Wanderer Miror B. @ Gateon Port - new(GameVersion.XD, 81, 04500, First) { FatefulEncounter = true, Species = 175, Level = 25, Moves = new(266,161,246,270), Location = 164, Gift = true }, // Togepi: Pokémon Trainer Hordel @ Outskirt Stand - new(GameVersion.XD, 82, 02500, Poochyena) { FatefulEncounter = true, Species = 261, Level = 10, Moves = new(091,215,305,336), Location = 162 }, // Poochyena: Bodybuilder Kilen @ Gateon Port - new(GameVersion.XD, 83, 02500, Ledyba) { FatefulEncounter = true, Species = 165, Level = 10, Moves = new(060,287,332,048), Location = 153 }, // Ledyba: Casual Guy Cyle @ Gateon Port + new(239, 20, Hordel, Zaprong) { Location = 164, TID16 = 41400, Moves = new(008, 007, 009, 238) }, // Elekid @ Snagem Hideout + new(307, 20, Duking) { Location = 116, TID16 = 37149, Moves = new(223, 093, 247, 197) }, // Meditite @ Pyrite Town + new(213, 20, Duking) { Location = 116, TID16 = 37149, Moves = new(092, 164, 188, 227) }, // Shuckle @ Pyrite Town + new(246, 20, Duking) { Location = 116, TID16 = 37149, Moves = new(201, 349, 044, 200) }, // Larvitar @ Pyrite Town }; - internal static readonly EncounterArea3XD[] SlotsXD = + internal static readonly EncounterShadow3XD[] Shadow = + { + new(01, 03000, First) { Species = 216, Level = 11, Moves = new(216,287,122,232), Location = 143, FixedBall = Ball.Poke }, // Teddiursa: Cipher Peon Naps @ Pokémon HQ Lab -- treat as Gift as it can only be captured in a Poké Ball + new(02, 02000, Vulpix) { Species = 037, Level = 18, Moves = new(257,204,052,091), Location = 109 }, // Vulpix: Cipher Peon Mesin @ ONBS Building + new(03, 01500, Spheal) { Species = 363, Level = 17, Moves = new(062,204,055,189), Location = 011 }, // Spheal: Cipher Peon Blusix @ Cipher Lab + new(03, 01500, Spheal) { Species = 363, Level = 17, Moves = new(062,204,055,189), Location = 100 }, // Spheal: Cipher Peon Blusix @ Phenac City + new(04, 01500, First) { Species = 343, Level = 17, Moves = new(317,287,189,060), Location = 011 }, // Baltoy: Cipher Peon Browsix @ Cipher Lab + new(04, 01500, First) { Species = 343, Level = 17, Moves = new(317,287,189,060), Location = 096 }, // Baltoy: Cipher Peon Browsix @ Phenac City + new(05, 01500, First) { Species = 179, Level = 17, Moves = new(034,215,084,086), Location = 011 }, // Mareep: Cipher Peon Yellosix @ Cipher Lab + new(05, 01500, First) { Species = 179, Level = 17, Moves = new(034,215,084,086), Location = 096 }, // Mareep: Cipher Peon Yellosix @ Phenac City + new(06, 01500, Gulpin) { Species = 316, Level = 17, Moves = new(351,047,124,092), Location = 011 }, // Gulpin: Cipher Peon Purpsix @ Cipher Lab + new(06, 01500, Gulpin) { Species = 316, Level = 17, Moves = new(351,047,124,092), Location = 100 }, // Gulpin: Cipher Peon Purpsix @ Phenac City + new(07, 01500, Seedot) { Species = 273, Level = 17, Moves = new(202,287,331,290), Location = 011 }, // Seedot: Cipher Peon Greesix @ Cipher Lab + new(07, 01500, Seedot) { Species = 273, Level = 17, Moves = new(202,287,331,290), Location = 100 }, // Seedot: Cipher Peon Greesix @ Phenac City + new(08, 01500, Spinarak) { Species = 167, Level = 14, Moves = new(091,287,324,101), Location = 010 }, // Spinarak: Cipher Peon Nexir @ Cipher Lab + new(09, 01500, Numel) { Species = 322, Level = 14, Moves = new(036,204,091,052), Location = 009 }, // Numel: Cipher Peon Solox @ Cipher Lab + new(10, 01700, First) { Species = 318, Level = 15, Moves = new(352,287,184,044), Location = 008 }, // Carvanha: Cipher Peon Cabol @ Cipher Lab + new(11, 03000, Roselia) { Species = 315, Level = 22, Moves = new(345,186,320,073), Location = 094 }, // Roselia: Cipher Peon Fasin @ Phenac City + new(12, 02500, Delcatty) { Species = 301, Level = 18, Moves = new(290,186,213,351), Location = 008 }, // Delcatty: Cipher Admin Lovrina @ Cipher Lab + new(13, 04000, Nosepass) { Species = 299, Level = 26, Moves = new(085,270,086,157), Location = 090 }, // Nosepass: Wanderer Miror B. @ Poké Spots + new(14, 01500, First) { Species = 228, Level = 17, Moves = new(185,204,052,046), Location = 100 }, // Houndour: Cipher Peon Resix @ Phenac City + new(14, 01500, First) { Species = 228, Level = 17, Moves = new(185,204,052,046), Location = 011 }, // Houndour: Cipher Peon Resix @ Cipher Lab + new(15, 02000, Makuhita) { Species = 296, Level = 18, Moves = new(280,287,292,317), Location = 109 }, // Makuhita: Cipher Peon Torkin @ ONBS Building + new(16, 02200, Duskull) { Species = 355, Level = 19, Moves = new(247,270,310,109), Location = 110 }, // Duskull: Cipher Peon Lobar @ ONBS Building + new(17, 02200, Ralts) { Species = 280, Level = 20, Moves = new(351,047,115,093), Location = 119 }, // Ralts: Cipher Peon Feldas @ ONBS Building + new(18, 02500, Mawile) { Species = 303, Level = 22, Moves = new(206,047,011,334), Location = 111 }, // Mawile: Cipher Cmdr Exol @ ONBS Building + new(19, 02500, Snorunt) { Species = 361, Level = 20, Moves = new(352,047,044,196), Location = 097 }, // Snorunt: Cipher Peon Exinn @ Phenac City + new(20, 02500, Pineco) { Species = 204, Level = 20, Moves = new(042,287,191,068), Location = 096 }, // Pineco: Cipher Peon Gonrap @ Phenac City + new(21, 02500, Swinub) { Species = 220, Level = 22, Moves = new(246,204,054,341), Location = 100 }, // Swinub: Cipher Peon Greck @ Phenac City + new(22, 02500, Natu) { Species = 177, Level = 22, Moves = new(248,226,101,332), Location = 094 }, // Natu: Cipher Peon Eloin @ Phenac City + new(23, 01800, Shroomish) { Species = 285, Level = 15, Moves = new(206,287,072,078), Location = 008 }, // Shroomish: Cipher R&D Klots @ Cipher Lab + new(24, 03500, Meowth) { Species = 052, Level = 22, Moves = new(163,047,006,044), Location = 094 }, // Meowth: Cipher Peon Fostin @ Phenac City + new(25, 04500, Spearow) { Species = 021, Level = 22, Moves = new(206,226,043,332), Location = 107 }, // Spearow: Cipher Peon Ezin @ Phenac Stadium + new(26, 03000, Grimer) { Species = 088, Level = 23, Moves = new(188,270,325,107), Location = 107 }, // Grimer: Cipher Peon Faltly @ Phenac Stadium + new(27, 03500, Seel) { Species = 086, Level = 23, Moves = new(057,270,219,058), Location = 107 }, // Seel: Cipher Peon Egrog @ Phenac Stadium + new(28, 05000, Lunatone) { Species = 337, Level = 25, Moves = new(094,226,240,317), Location = 107 }, // Lunatone: Cipher Admin Snattle @ Phenac Stadium + new(29, 02500, Voltorb) { Species = 100, Level = 19, Moves = new(243,287,209,129), Location = 092 }, // Voltorb: Wanderer Miror B. @ Cave Poké Spot + new(30, 05000, First) { Species = 335, Level = 28, Moves = new(280,287,068,306), Location = 071 }, // Zangoose: Thug Zook @ Cipher Key Lair + new(31, 04000, Growlithe) { Species = 058, Level = 28, Moves = new(053,204,044,036), Location = 064 }, // Growlithe: Cipher Peon Humah @ Cipher Key Lair + new(32, 04000, Paras) { Species = 046, Level = 28, Moves = new(147,287,163,206), Location = 064 }, // Paras: Cipher Peon Humah @ Cipher Key Lair + new(33, 04000, First) { Species = 090, Level = 29, Moves = new(036,287,057,062), Location = 065 }, // Shellder: Cipher Peon Gorog @ Cipher Key Lair + new(34, 04500, First) { Species = 015, Level = 30, Moves = new(188,226,041,014), Location = 066 }, // Beedrill: Cipher Peon Lok @ Cipher Key Lair + new(35, 04000, Pidgeotto) { Species = 017, Level = 30, Moves = new(017,287,211,297), Location = 066 }, // Pidgeotto: Cipher Peon Lok @ Cipher Key Lair + new(36, 04000, Butterfree){ Species = 012, Level = 30, Moves = new(094,234,079,332), Location = 067 }, // Butterfree: Cipher Peon Targ @ Cipher Key Lair + new(37, 04000, Tangela) { Species = 114, Level = 30, Moves = new(076,234,241,275), Location = 067 }, // Tangela: Cipher Peon Targ @ Cipher Key Lair + new(38, 06000, Raticate) { Species = 020, Level = 34, Moves = new(162,287,184,158), Location = 076 }, // Raticate: Chaser Furgy @ Citadark Isle + new(39, 04000, Venomoth) { Species = 049, Level = 32, Moves = new(318,287,164,094), Location = 070 }, // Venomoth: Cipher Peon Angic @ Cipher Key Lair + new(40, 04000, Weepinbell){ Species = 070, Level = 32, Moves = new(345,234,188,230), Location = 070 }, // Weepinbell: Cipher Peon Angic @ Cipher Key Lair + new(41, 05000, Arbok) { Species = 024, Level = 33, Moves = new(188,287,137,044), Location = 070 }, // Arbok: Cipher Peon Smarton @ Cipher Key Lair + new(42, 06000, Primeape) { Species = 057, Level = 34, Moves = new(238,270,116,179), Location = 069 }, // Primeape: Cipher Admin Gorigan @ Cipher Key Lair + new(43, 05500, Hypno) { Species = 097, Level = 34, Moves = new(094,226,096,247), Location = 069 }, // Hypno: Cipher Admin Gorigan @ Cipher Key Lair + new(44, 06500, Golduck) { Species = 055, Level = 33, Moves = new(127,204,244,280), Location = 088 }, // Golduck: Navigator Abson @ Citadark Isle + new(45, 07000, Sableye) { Species = 302, Level = 33, Moves = new(247,270,185,105), Location = 088 }, // Sableye: Navigator Abson @ Citadark Isle + new(46, 04500, Magneton) { Species = 082, Level = 30, Moves = new(038,287,240,087), Location = 067 }, // Magneton: Cipher Peon Snidle @ Cipher Key Lair + new(47, 08000, Dodrio) { Species = 085, Level = 34, Moves = new(065,226,097,161), Location = 076 }, // Dodrio: Chaser Furgy @ Citadark Isle + new(48, 05500, Farfetchd) { Species = 083, Level = 36, Moves = new(163,226,014,332), Location = 076 }, // Farfetch'd: Cipher Admin Lovrina @ Citadark Isle + new(49, 06500, Altaria) { Species = 334, Level = 36, Moves = new(225,215,076,332), Location = 076 }, // Altaria: Cipher Admin Lovrina @ Citadark Isle + new(50, 06000, Kangaskhan){ Species = 115, Level = 35, Moves = new(089,047,039,146), Location = 085 }, // Kangaskhan: Cipher Peon Litnar @ Citadark Isle + new(51, 07000, Banette) { Species = 354, Level = 37, Moves = new(185,270,247,174), Location = 085 }, // Banette: Cipher Peon Litnar @ Citadark Isle + new(52, 07000, Magmar) { Species = 126, Level = 36, Moves = new(126,266,238,009), Location = 077 }, // Magmar: Cipher Peon Grupel @ Citadark Isle + new(53, 07000, Pinsir) { Species = 127, Level = 35, Moves = new(012,270,206,066), Location = 077 }, // Pinsir: Cipher Peon Grupel @ Citadark Isle + new(54, 05500, Magcargo) { Species = 219, Level = 38, Moves = new(257,287,089,053), Location = 080 }, // Magcargo: Cipher Peon Kolest @ Citadark Isle + new(55, 06000, Rapidash) { Species = 078, Level = 40, Moves = new(076,226,241,053), Location = 080 }, // Rapidash: Cipher Peon Kolest @ Citadark Isle + new(56, 06000, Hitmonchan){ Species = 107, Level = 38, Moves = new(005,270,170,327), Location = 081 }, // Hitmonchan: Cipher Peon Karbon @ Citadark Isle + new(57, 07000, Hitmonlee) { Species = 106, Level = 38, Moves = new(136,287,170,025), Location = 081 }, // Hitmonlee: Cipher Peon Petro @ Citadark Isle + new(58, 05000, Lickitung) { Species = 108, Level = 38, Moves = new(038,270,111,205), Location = 084 }, // Lickitung: Cipher Peon Geftal @ Citadark Isle + new(59, 08000, Scyther) { Species = 123, Level = 40, Moves = new(013,234,318,163), Location = 084 }, // Scyther: Cipher Peon Leden @ Citadark Isle + new(60, 04000, Chansey) { Species = 113, Level = 39, Moves = new(085,186,135,285), Location = 084 }, // Chansey: Cipher Peon Leden @ Citadark Isle + new(60, 04000, Chansey) { Species = 113, Level = 39, Moves = new(085,186,135,285), Location = 087 }, // Chansey: Cipher Peon Leden @ Citadark Isle + new(61, 07500, Solrock) { Species = 338, Level = 41, Moves = new(094,226,241,322), Location = 087 }, // Solrock: Cipher Admin Snattle @ Citadark Isle + new(62, 07500, Starmie) { Species = 121, Level = 41, Moves = new(127,287,058,105), Location = 087 }, // Starmie: Cipher Admin Snattle @ Citadark Isle + new(63, 07000, Electabuzz){ Species = 125, Level = 43, Moves = new(238,266,086,085), Location = 087 }, // Electabuzz: Cipher Admin Ardos @ Citadark Isle + new(64, 07000, First) { Species = 277, Level = 43, Moves = new(143,226,097,263), Location = 087 }, // Swellow: Cipher Admin Ardos @ Citadark Isle + new(65, 09000, Snorlax) { Species = 143, Level = 43, Moves = new(090,287,174,034), Location = 087 }, // Snorlax: Cipher Admin Ardos @ Citadark Isle + new(66, 07500, Poliwrath) { Species = 062, Level = 42, Moves = new(056,270,240,280), Location = 087 }, // Poliwrath: Cipher Admin Gorigan @ Citadark Isle + new(67, 06500, MrMime) { Species = 122, Level = 42, Moves = new(094,266,227,009), Location = 087 }, // Mr. Mime: Cipher Admin Gorigan @ Citadark Isle + new(68, 05000, Dugtrio) { Species = 051, Level = 40, Moves = new(089,204,201,161), Location = 075 }, // Dugtrio: Cipher Peon Kolax @ Citadark Isle + new(69, 07000, Manectric) { Species = 310, Level = 44, Moves = new(087,287,240,044), Location = 073 }, // Manectric: Cipher Admin Eldes @ Citadark Isle + new(70, 09000, Salamence) { Species = 373, Level = 50, Moves = new(337,287,349,332), Location = 073 }, // Salamence: Cipher Admin Eldes @ Citadark Isle + new(71, 06500, Marowak) { Species = 105, Level = 44, Moves = new(089,047,014,157), Location = 073 }, // Marowak: Cipher Admin Eldes @ Citadark Isle + new(72, 06000, Lapras) { Species = 131, Level = 44, Moves = new(056,215,240,059), Location = 073 }, // Lapras: Cipher Admin Eldes @ Citadark Isle + new(73, 12000, First) { Species = 249, Level = 50, Moves = new(354,297,089,056), Location = 074 }, // Lugia: Grand Master Greevil @ Citadark Isle + new(74, 10000, Zapdos) { Species = 145, Level = 50, Moves = new(326,226,319,085), Location = 074 }, // Zapdos: Grand Master Greevil @ Citadark Isle + new(75, 10000, Moltres) { Species = 146, Level = 50, Moves = new(326,234,261,053), Location = 074 }, // Moltres: Grand Master Greevil @ Citadark Isle + new(76, 10000, Articuno) { Species = 144, Level = 50, Moves = new(326,215,114,058), Location = 074 }, // Articuno: Grand Master Greevil @ Citadark Isle + new(77, 09000, Tauros) { Species = 128, Level = 46, Moves = new(089,287,039,034), Location = 074 }, // Tauros: Grand Master Greevil @ Citadark Isle + new(78, 07000, First) { Species = 112, Level = 46, Moves = new(224,270,184,089), Location = 074 }, // Rhydon: Grand Master Greevil @ Citadark Isle + new(79, 09000, Exeggutor) { Species = 103, Level = 46, Moves = new(094,287,095,246), Location = 074 }, // Exeggutor: Grand Master Greevil @ Citadark Isle + new(80, 09000, Dragonite) { Species = 149, Level = 55, Moves = new(063,215,349,089), Location = 162 }, // Dragonite: Wanderer Miror B. @ Gateon Port + new(81, 04500, First) { Species = 175, Level = 25, Moves = new(266,161,246,270), Location = 164, FixedBall = Ball.Poke }, // Togepi: Pokémon Trainer Hordel @ Outskirt Stand + new(82, 02500, Poochyena) { Species = 261, Level = 10, Moves = new(091,215,305,336), Location = 162 }, // Poochyena: Bodybuilder Kilen @ Gateon Port + new(83, 02500, Ledyba) { Species = 165, Level = 10, Moves = new(060,287,332,048), Location = 153 }, // Ledyba: Casual Guy Cyle @ Gateon Port + }; + + internal static readonly EncounterArea3XD[] Slots = { new(90, 027, 23, 207, 20, 328, 20), // Rock (Sandshrew, Gligar, Trapinch) new(91, 187, 20, 231, 20, 283, 20), // Oasis (Hoppip, Phanpy, Surskit) new(92, 041, 21, 304, 21, 194, 21), // Cave (Zubat, Aron, Wooper) }; - - internal static readonly EncounterStatic3[] Encounter_CXDGift = ArrayUtil.ConcatAll(Encounters3Colo.Encounter_ColoGift, Encounter_XDGift); - internal static readonly EncounterStaticShadow[] Encounter_CXDShadow = ArrayUtil.ConcatAll(Encounters3Colo.Encounter_Colo, Encounter_XD); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen3/EncountersWC3.cs b/PKHeX.Core/Legality/Encounters/Data/Gen3/EncountersWC3.cs index bd6124364..3dd600245 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen3/EncountersWC3.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen3/EncountersWC3.cs @@ -1,5 +1,3 @@ -using System; - namespace PKHeX.Core; /// @@ -16,60 +14,7 @@ internal static class EncountersWC3 new() { Species = 385, Level = 05, ID32 = 20043, OT_Gender = 0, Version = GameVersion.R, Method = PIDType.BACD_R, OT_Name = "WISHMKR", CardTitle = "Wishmaker Jirachi", Language = (int)LanguageID.English }, }; - private static WC3[] GetIngameCXDData() - { - var langs = LanguageCXD; - string[] h = {string.Empty, "ダニー", "HORDEL", "VOLKER", "ODINO", "HORAZ", string.Empty, "HORDEL"}; - string[] d = {string.Empty, "ギンザル", "DUKING", "DOKING", "RODRIGO", "GRAND", string.Empty, "GERMÁN"}; - string[] z = {string.Empty, "コンセント", "ZAPRONG", "ZAPRONG", "ZAPRONG", "ZAPRONG", string.Empty, "ZAPRONG"}; - - const int count = 5; - var result = new WC3[count * langs.Length]; - for (int i = 0, ofs = 0; i < langs.Length; i++) - { - var id = langs[i]; - var l = (int)id; - - // Colosseum - result[ofs++] = new() { Species = 311, Level = 13, Language = l, Location = 254, ID32 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.COLO, CardTitle = $"Special Gift ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new(045, 086, 098, 270) }; // Plusle @ Ingame Trade - // XD - result[ofs++] = new(true) { Species = 239, Level = 20, Language = l, Location = 164, TID16 = 41400, OT_Gender = 0, OT_Name = h[l], Version = GameVersion.XD, CardTitle = $"Trade Togepi ({id})", Method = PIDType.CXD, Moves = new(008, 007, 009, 238), Nickname = z[l] }; // Elekid @ Snagem Hideout - result[ofs++] = new(true) { Species = 307, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Trapinch ({id})", Method = PIDType.CXD, Moves = new(223, 093, 247, 197) }; // Meditite @ Pyrite Town - result[ofs++] = new(true) { Species = 213, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Surskit ({id})", Method = PIDType.CXD, Moves = new(092, 164, 188, 227) }; // Shuckle @ Pyrite Town - result[ofs++] = new(true) { Species = 246, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Wooper ({id})", Method = PIDType.CXD, Moves = new(201, 349, 044, 200) }; // Larvitar @ Pyrite Town - } - return result; - } - - private static ReadOnlySpan LanguageCXD => new[] { LanguageID.Japanese, LanguageID.English, LanguageID.French, LanguageID.Italian, LanguageID.German, LanguageID.Spanish }; - - private static WC3[] GetIngameCXDDataMainline() - { - var langs = LanguageCXD; - string[] p = { string.Empty, "コロシアム", "COLOS", "COLOSSEUM", "ARENA", "COLOSSEUM", string.Empty, "CLAUDIO" }; - string[] c = { string.Empty, "アゲト", "AGATE", "SAMARAGD", "SOFO", "EMERITAE", string.Empty, "ÁGATA" }; - string[] m = { string.Empty, "バトルやま", "MATTLE", "MT BATAILL", "MONTE LOTT", "DUELLBERG", string.Empty, "ERNESTO" }; // truncated on ck3->pk3 transfer - - const int count = 3; - var result = new WC3[count * langs.Length]; - for (int i = 0, ofs = 0; i < langs.Length; i++) - { - var id = langs[i]; - var l = (int)id; - var nd = id != LanguageID.Japanese; - - // Colosseum - result[ofs++] = new() { Species = 025, Level = 10, Language = l, Location = 255, ID32 = 31121, OT_Gender = 0, OT_Name = p[l], Version = GameVersion.R, CardTitle = $"Colosseum Pikachu ({id})",Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }; // Colosseum Pikachu bonus gift - result[ofs++] = new() { Species = 251, Level = 10, Language = l, Location = 255, ID32 = 31121, OT_Gender = 1, OT_Name = c[l], Version = GameVersion.R, CardTitle = $"Agate Celebi ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }; // Ageto Celebi bonus gift - result[ofs++] = new() { Species = 250, Level = 70, Language = l, Location = 255, ID32 = 10048, OT_Gender = 0, OT_Name = m[l], Version = GameVersion.S, CardTitle = $"Mt. Battle Ho-Oh ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new(105, 126, 241, 129) }; // Ho-oh @ Mt. Battle - } - return result; - } - - internal static readonly WC3[] Encounter_WC3CXD = GetIngameCXDData(); - internal static readonly WC3[] Encounter_WC3CXDMain = GetIngameCXDDataMainline(); - - internal static readonly WC3[] Encounter_Event3 = ArrayUtil.ConcatAll(Encounter_Event3_Special, Encounter_WC3CXD, Encounter_WC3CXDMain); + internal static readonly WC3[] Encounter_Event3 = Encounter_Event3_Special; internal static readonly WC3[] Encounter_Event3_FRLG = { diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen4/Encounters4DPPt.cs b/PKHeX.Core/Legality/Encounters/Data/Gen4/Encounters4DPPt.cs new file mode 100644 index 000000000..abcf7c4cb --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Data/Gen4/Encounters4DPPt.cs @@ -0,0 +1,148 @@ +using static PKHeX.Core.EncounterUtil; +using static PKHeX.Core.GameVersion; +using static PKHeX.Core.GroundTileAllowed; +using static PKHeX.Core.AbilityPermission; + +namespace PKHeX.Core; + +/// +/// Generation 4 Encounters +/// +internal static class Encounters4DPPt +{ + internal static readonly EncounterArea4[] SlotsD = EncounterArea4.GetAreas(Get("d", "da"), D); + internal static readonly EncounterArea4[] SlotsP = EncounterArea4.GetAreas(Get("p", "pe"), P); + internal static readonly EncounterArea4[] SlotsPt = EncounterArea4.GetAreas(Get("pt", "pt"), Pt); + + private const string tradeDPPt = "tradedppt"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings8(tradeDPPt); + + #region Static Encounter/Gift Tables + + public static readonly EncounterStatic4[] StaticDPPt = + { + // Gift + new(DPPt) { FixedBall = Ball.Poke, Species = 447, Level = 01, Location = 000, EggLocation = 2010 }, // Riolu Egg from Riley + new(DPPt) { Species = 442, Level = 25, Location = 24 }, // Spiritomb @ Route 209 + new(DPPt) { Species = 480, Level = 50, Location = 089, GroundTile = Cave }, // Uxie @ Acuity Cavern + new(DPPt) { Species = 482, Level = 50, Location = 088, GroundTile = Cave }, // Azelf @ Valor Cavern + + // Roamers + new(DPPt) { Roaming = true, Location = 16, Species = 481, Level = 50, GroundTile = Grass | Water }, // Mesprit + new(DPPt) { Roaming = true, Location = 16, Species = 488, Level = 50, GroundTile = Grass | Water }, // Cresselia + }; + + public static readonly EncounterStatic4[] StaticDP = + { + new(DP) { FixedBall = Ball.Poke, Species = 133, Level = 05, Location = 010, GroundTile = Max_DP }, // Eevee @ Hearthome City + new(DP) { FixedBall = Ball.Poke, Species = 440, Level = 01, Location = 000, EggLocation = 2009 }, // Happiny Egg from Traveling Man + + new(DP) { FixedBall = Ball.Poke, Species = 387, Level = 5, Location = 076, GroundTile = Max_DP }, // Turtwig @ Lake Verity + new(DP) { FixedBall = Ball.Poke, Species = 390, Level = 5, Location = 076, GroundTile = Max_DP }, // Chimchar + new(DP) { FixedBall = Ball.Poke, Species = 393, Level = 5, Location = 076, GroundTile = Max_DP }, // Piplup + + new(DP) { FixedBall = Ball.Poke, Species = 138, Level = 20, Location = 094, GroundTile = Max_DP }, // Omanyte + new(DP) { FixedBall = Ball.Poke, Species = 140, Level = 20, Location = 094, GroundTile = Max_DP }, // Kabuto + new(DP) { FixedBall = Ball.Poke, Species = 142, Level = 20, Location = 094, GroundTile = Max_DP }, // Aerodactyl + new(DP) { FixedBall = Ball.Poke, Species = 345, Level = 20, Location = 094, GroundTile = Max_DP }, // Lileep + new(DP) { FixedBall = Ball.Poke, Species = 347, Level = 20, Location = 094, GroundTile = Max_DP }, // Anorith + new(DP) { FixedBall = Ball.Poke, Species = 408, Level = 20, Location = 094, GroundTile = Max_DP }, // Cranidos + new(DP) { FixedBall = Ball.Poke, Species = 410, Level = 20, Location = 094, GroundTile = Max_DP }, // Shieldon + + new(DP) { Species = 425, Level = 22, Location = 47 }, // Drifloon @ Valley Windworks + new(DP) { Species = 479, Level = 15, Location = 70, GroundTile = Building }, // Rotom @ Old Chateau + + new(DP) { Species = 485, Level = 70, Location = 084, GroundTile = Cave }, // Heatran @ Stark Mountain + new(DP) { Species = 486, Level = 70, Location = 064, GroundTile = Cave }, // Regigigas @ Snowpoint Temple + new(DP) { Species = 487, Level = 70, Location = 062, GroundTile = Cave, Form = 0 }, // Giratina @ Turnback Cave + //new(DP) { Species = 492, Form = 0, Level = 30, Location = 063, Fateful = false }, // Shaymin @ Flower Paradise (Unreleased in Diamond and Pearl) + }; + + public static readonly EncounterStatic4[] StaticPt = + { + new(Pt) { FixedBall = Ball.Poke, Species = 387, Level = 5, Location = 016, GroundTile = Max_Pt }, // Turtwig @ Route 201 + new(Pt) { FixedBall = Ball.Poke, Species = 390, Level = 5, Location = 016, GroundTile = Max_Pt }, // Chimchar + new(Pt) { FixedBall = Ball.Poke, Species = 393, Level = 5, Location = 016, GroundTile = Max_Pt }, // Piplup + + new(Pt) { FixedBall = Ball.Poke, Species = 138, Level = 20, Location = 094, GroundTile = Max_Pt }, // Omanyte + new(Pt) { FixedBall = Ball.Poke, Species = 140, Level = 20, Location = 094, GroundTile = Max_Pt }, // Kabuto + new(Pt) { FixedBall = Ball.Poke, Species = 142, Level = 20, Location = 094, GroundTile = Max_Pt }, // Aerodactyl + new(Pt) { FixedBall = Ball.Poke, Species = 345, Level = 20, Location = 094, GroundTile = Max_Pt }, // Lileep + new(Pt) { FixedBall = Ball.Poke, Species = 347, Level = 20, Location = 094, GroundTile = Max_Pt }, // Anorith + new(Pt) { FixedBall = Ball.Poke, Species = 408, Level = 20, Location = 094, GroundTile = Max_Pt }, // Cranidos + new(Pt) { FixedBall = Ball.Poke, Species = 410, Level = 20, Location = 094, GroundTile = Max_Pt }, // Shieldon + + new(Pt) { FixedBall = Ball.Poke, Species = 133, Level = 20, Location = 010, GroundTile = Max_Pt }, // Eevee @ Hearthome City + new(Pt) { FixedBall = Ball.Poke, Species = 137, Level = 25, Location = 012, GroundTile = Max_Pt }, // Porygon @ Veilstone City + new(Pt) { FixedBall = Ball.Poke, Species = 175, Level = 01, Location = 000, EggLocation = 2011 }, // Togepi Egg from Cynthia + + new(Pt) { Species = 425, Level = 15, Location = 47 }, // Drifloon @ Valley Windworks + new(Pt) { Species = 479, Level = 20, Location = 70, GroundTile = Building }, // Rotom @ Old Chateau + + // Stationary Legendary + new(Pt) { Species = 377, Level = 30, Location = 125, GroundTile = Cave }, // Regirock @ Rock Peak Ruins + new(Pt) { Species = 378, Level = 30, Location = 124, GroundTile = Cave }, // Regice @ Iceberg Ruins + new(Pt) { Species = 379, Level = 30, Location = 123, GroundTile = Cave }, // Registeel @ Iron Ruins + new(Pt) { Species = 483, Level = 70, Location = 051, GroundTile = Rock }, // Dialga @ Spear Pillar + new(Pt) { Species = 484, Level = 70, Location = 051, GroundTile = Rock }, // Palkia @ Spear Pillar + new(Pt) { Species = 485, Level = 50, Location = 084, GroundTile = Cave }, // Heatran @ Stark Mountain + new(Pt) { Species = 486, Level = 01, Location = 064, GroundTile = Cave }, // Regigigas @ Snowpoint Temple + new(Pt) { Species = 487, Level = 47, Location = 062, GroundTile = Cave, Form = 0 }, // Giratina @ Turnback Cave + new(Pt) { Species = 487, Level = 47, Location = 117, GroundTile = Distortion, Form = 1, HeldItem = 112 }, // Giratina @ Distortion World + + new(Pt) { Species = 491, Level = 50, Location = 079, GroundTile = Grass }, // Darkrai @ Newmoon Island + new(Pt) { Species = 492, Form = 0, Level = 30, Location = 063, FatefulEncounter = true }, // Shaymin @ Flower Paradise + new(Pt) { Roaming = true, Location = 16, Species = 144, Level = 60, GroundTile = Grass | Water }, // Articuno + new(Pt) { Roaming = true, Location = 16, Species = 145, Level = 60, GroundTile = Grass | Water }, // Zapdos + new(Pt) { Roaming = true, Location = 16, Species = 146, Level = 60, GroundTile = Grass | Water }, // Moltres + }; + + public static readonly EncounterStatic4[] StaticD = + { + new(D ) { Species = 483, Level = 47, Location = 051, GroundTile = Rock }, // Dialga @ Spear Pillar + }; + + public static readonly EncounterStatic4[] StaticP = + { + new( P) { Species = 484, Level = 47, Location = 051, GroundTile = Rock }, // Palkia @ Spear Pillar + }; + + #endregion + #region Trade Tables + + internal static readonly EncounterTrade4RanchGift[] RanchGifts = + { + new(323975838, 025, 18) { Moves = new(447,085,148,104), TID16 = 1000, SID16 = 19840, OTGender = 1, MetLocation = 0068, Gender = 0, Ability = OnlyFirst, CurrentLevel = 20 }, // Pikachu + new(323977664, 037, 16) { Moves = new(412,109,053,219), TID16 = 1000, SID16 = 21150, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlyFirst, CurrentLevel = 30 }, // Vulpix + new(323975579, 077, 13) { Moves = new(036,033,039,052), TID16 = 1000, SID16 = 01123, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlySecond, CurrentLevel = 16 }, // Ponyta + new(323975564, 108, 34) { Moves = new(076,111,014,205), TID16 = 1000, SID16 = 03050, OTGender = 1, MetLocation = 0077, Gender = 0, Ability = OnlyFirst, CurrentLevel = 40 }, // Lickitung + new(323977579, 114, 01) { Moves = new(437,438,079,246), TID16 = 1000, SID16 = 49497, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlySecond }, // Tangela + new(323977675, 133, 16) { Moves = new(363,270,098,247), TID16 = 1000, SID16 = 47710, OTGender = 1, MetLocation = 0068, Gender = 0, Ability = OnlySecond, CurrentLevel = 30 }, // Eevee + new(323977588, 142, 20) { Moves = new(363,089,444,332), TID16 = 1000, SID16 = 43066, OTGender = 1, MetLocation = 0094, Gender = 0, Ability = OnlyFirst, CurrentLevel = 50 }, // Aerodactyl + new(232975554, 193, 22) { Moves = new(318,095,246,138), TID16 = 1000, SID16 = 42301, OTGender = 1, MetLocation = 0052, Gender = 0, Ability = OnlyFirst, CurrentLevel = 45, FixedBall = Ball.Safari }, // Yanma + new(323975570, 241, 16) { Moves = new(208,215,360,359), TID16 = 1000, SID16 = 02707, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlyFirst, CurrentLevel = 48 }, // Miltank + new(323975563, 285, 22) { Moves = new(402,147,206,078), TID16 = 1000, SID16 = 02788, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlySecond, CurrentLevel = 45, FixedBall = Ball.Safari }, // Shroomish + new(323975559, 320, 30) { Moves = new(156,323,133,058), TID16 = 1000, SID16 = 27046, OTGender = 1, MetLocation = 0038, Gender = 0, Ability = OnlySecond, CurrentLevel = 45 }, // Wailmer + new(323977657, 360, 01) { Moves = new(204,150,227,000), TID16 = 1000, SID16 = 01788, OTGender = 1, MetLocation = 0004, Gender = 0, Ability = OnlySecond, EggLocation = 2000 }, // Wynaut + new(323975563, 397, 02) { Moves = new(355,017,283,018), TID16 = 1000, SID16 = 59298, OTGender = 1, MetLocation = 0016, Gender = 0, Ability = OnlySecond, CurrentLevel = 23 }, // Staravia + new(323970584, 415, 05) { Moves = new(230,016,000,000), TID16 = 1000, SID16 = 54140, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlyFirst, CurrentLevel = 20 }, // Combee + new(323977539, 417, 09) { Moves = new(447,045,351,098), TID16 = 1000, SID16 = 18830, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlySecond, CurrentLevel = 10 }, // Pachirisu + new(323974107, 422, 20) { Moves = new(363,352,426,104), TID16 = 1000, SID16 = 39272, OTGender = 1, MetLocation = 0028, Gender = 0, Ability = OnlySecond, CurrentLevel = 25, Form = 1 }, // Shellos + new(323977566, 427, 10) { Moves = new(204,193,409,098), TID16 = 1000, SID16 = 31045, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlyFirst, CurrentLevel = 16 }, // Buneary + new(323975579, 453, 22) { Moves = new(310,207,426,389), TID16 = 1000, SID16 = 41342, OTGender = 1, MetLocation = 0052, Gender = 0, Ability = OnlySecond, CurrentLevel = 31, FixedBall = Ball.Safari }, // Croagunk + new(323977566, 456, 15) { Moves = new(213,352,219,392), TID16 = 1000, SID16 = 48348, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlyFirst, CurrentLevel = 35 }, // Finneon + new(323975582, 459, 32) { Moves = new(452,420,275,059), TID16 = 1000, SID16 = 23360, OTGender = 1, MetLocation = 0031, Gender = 0, Ability = OnlyFirst, CurrentLevel = 41 }, // Snover + new(151, 50) { Moves = new(235,216,095,100), TID16 = 1000, SID16 = 59228, OTGender = 1, FixedBall = Ball.Cherish, Gender = 2, Ability = Any12 }, // Mew + new(489, 01) { Moves = new(447,240,156,057), TID16 = 1000, SID16 = 09248, OTGender = 1, FixedBall = Ball.Cherish, Gender = 2, Ability = Any12, CurrentLevel = 50, EggLocation = 3000 }, // Phione + }; + + internal static readonly EncounterTrade4PID[] TradeGift_DPPtIngame = + { + new(TradeNames, 00, DPPt, 0x0000008E, 063, 01) { Ability = OnlyFirst, TID16 = 25643, SID16 = 00000, OTGender = 1, Gender = 0, IVs = new(15,15,15,20,25,25) }, // Machop -> Abra + new(TradeNames, 01, DPPt, 0x00000867, 441, 01) { Ability = OnlySecond, TID16 = 44142, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(15,20,15,25,25,15), Contest = 20 }, // Buizel -> Chatot + new(TradeNames, 02, DPPt, 0x00000088, 093, 35) { Ability = OnlyFirst, TID16 = 19248, SID16 = 00000, OTGender = 1, Gender = 0, IVs = new(20,25,15,25,15,15) }, // Medicham (35 from Route 217) -> Haunter + new(TradeNames, 03, DPPt, 0x0000045C, 129, 01) { Ability = OnlyFirst, TID16 = 53277, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(15,25,15,20,25,15) }, // Finneon -> Magikarp + }; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen4/Encounters4HGSS.cs b/PKHeX.Core/Legality/Encounters/Data/Gen4/Encounters4HGSS.cs new file mode 100644 index 000000000..78f5328ad --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Data/Gen4/Encounters4HGSS.cs @@ -0,0 +1,147 @@ +using static PKHeX.Core.EncounterUtil; +using static PKHeX.Core.GameVersion; +using static PKHeX.Core.GroundTileAllowed; +using static PKHeX.Core.AbilityPermission; + +namespace PKHeX.Core; + +/// +/// Generation 4 Encounters +/// +internal static class Encounters4HGSS +{ + internal static readonly EncounterArea4[] SlotsHG = EncounterArea4.GetAreas(Get("hg", "hg"), HG); + internal static readonly EncounterArea4[] SlotsSS = EncounterArea4.GetAreas(Get("ss", "ss"), SS); + + internal static readonly EncounterStatic4Pokewalker[] Encounter_PokeWalker = EncounterStatic4Pokewalker.GetAll(Util.GetBinaryResource("encounter_walker4.pkl")); + + private const string tradeHGSS = "tradehgss"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings8(tradeHGSS); + + #region Static Encounter/Gift Tables + + internal static readonly EncounterStatic4[] Encounter_HGSS = + { + // Starters + new(HGSS) { FixedBall = Ball.Poke, Species = 001, Level = 05, Location = 138, GroundTile = Max_Pt }, // Bulbasaur @ Pallet Town + new(HGSS) { FixedBall = Ball.Poke, Species = 004, Level = 05, Location = 138, GroundTile = Max_Pt }, // Charmander + new(HGSS) { FixedBall = Ball.Poke, Species = 007, Level = 05, Location = 138, GroundTile = Max_Pt }, // Squirtle + new(HGSS) { FixedBall = Ball.Poke, Species = 152, Level = 05, Location = 126, GroundTile = Max_DP }, // Chikorita @ New Bark Town + new(HGSS) { FixedBall = Ball.Poke, Species = 155, Level = 05, Location = 126, GroundTile = Max_DP }, // Cyndaquil + new(HGSS) { FixedBall = Ball.Poke, Species = 158, Level = 05, Location = 126, GroundTile = Max_DP }, // Totodile + new(HGSS) { FixedBall = Ball.Poke, Species = 252, Level = 05, Location = 148, GroundTile = Max_Pt }, // Treecko @ Saffron City + new(HGSS) { FixedBall = Ball.Poke, Species = 255, Level = 05, Location = 148, GroundTile = Max_Pt }, // Torchic + new(HGSS) { FixedBall = Ball.Poke, Species = 258, Level = 05, Location = 148, GroundTile = Max_Pt }, // Mudkip + + // Fossils @ Pewter City + new(HGSS) { FixedBall = Ball.Poke, Species = 138, Level = 20, Location = 140, GroundTile = Max_Pt }, // Omanyte + new(HGSS) { FixedBall = Ball.Poke, Species = 140, Level = 20, Location = 140, GroundTile = Max_Pt }, // Kabuto + new(HGSS) { FixedBall = Ball.Poke, Species = 142, Level = 20, Location = 140, GroundTile = Max_Pt }, // Aerodactyl + new(HGSS) { FixedBall = Ball.Poke, Species = 345, Level = 20, Location = 140, GroundTile = Max_Pt }, // Lileep + new(HGSS) { FixedBall = Ball.Poke, Species = 347, Level = 20, Location = 140, GroundTile = Max_Pt }, // Anorith + new(HGSS) { FixedBall = Ball.Poke, Species = 408, Level = 20, Location = 140, GroundTile = Max_Pt }, // Cranidos + new(HGSS) { FixedBall = Ball.Poke, Species = 410, Level = 20, Location = 140, GroundTile = Max_Pt }, // Shieldon + + // Gift + new(HGSS) { FixedBall = Ball.Poke, Species = 072, Level = 15, Location = 130, GroundTile = Max_Pt }, // Tentacool @ Cianwood City + new(HGSS) { FixedBall = Ball.Poke, Species = 133, Level = 05, Location = 131, GroundTile = Max_Pt }, // Eevee @ Goldenrod City + new(HGSS) { FixedBall = Ball.Poke, Species = 147, Level = 15, Location = 222, GroundTile = Max_Pt, Moves = new(245) }, // Dratini @ Dragon's Den (ExtremeSpeed) + new(HGSS) { FixedBall = Ball.Poke, Species = 236, Level = 10, Location = 216, GroundTile = Max_Pt }, // Tyrogue @ Mt. Mortar + new(HGSS) { FixedBall = Ball.Poke, Species = 175, Level = 01, Location = 000, EggLocation = 2013, Moves = new((int)Move.Growl, (int)Move.Charm, (int)Move.Extrasensory) }, // Togepi Egg from Mr. Pokemon (Extrasensory as Egg move) + new(HGSS) { FixedBall = Ball.Poke, Species = 179, Level = 01, Location = 000, EggLocation = 2014 }, // Mareep Egg from Primo + new(HGSS) { FixedBall = Ball.Poke, Species = 194, Level = 01, Location = 000, EggLocation = 2014 }, // Wooper Egg from Primo + new(HGSS) { FixedBall = Ball.Poke, Species = 218, Level = 01, Location = 000, EggLocation = 2014 }, // Slugma Egg from Primo + + // Celadon City Game Corner + new(HGSS) { FixedBall = Ball.Poke, Species = 122, Level = 15, Location = 144, GroundTile = Max_Pt }, // Mr. Mime + new(HGSS) { FixedBall = Ball.Poke, Species = 133, Level = 15, Location = 144, GroundTile = Max_Pt }, // Eevee + new(HGSS) { FixedBall = Ball.Poke, Species = 137, Level = 15, Location = 144, GroundTile = Max_Pt }, // Porygon + + // Goldenrod City Game Corner + new(HGSS) { FixedBall = Ball.Poke, Species = 063, Level = 15, Location = 131, GroundTile = Max_Pt }, // Abra + new(HGSS) { FixedBall = Ball.Poke, Species = 147, Level = 15, Location = 131, GroundTile = Max_Pt }, // Dratini + + // Team Rocket HQ Trap Floor + new(HGSS) { Species = 100, Level = 23, Location = 213, GroundTile = Building }, // Voltorb + new(HGSS) { Species = 074, Level = 21, Location = 213, GroundTile = Building }, // Geodude + new(HGSS) { Species = 109, Level = 21, Location = 213, GroundTile = Building }, // Koffing + + // Stationary + new(HGSS) { Species = 130, Level = 30, Location = 135, GroundTile = Water, Shiny = Shiny.Always }, // Gyarados @ Lake of Rage + new(HGSS) { Species = 131, Level = 20, Location = 210, GroundTile = Water }, // Lapras @ Union Cave Friday Only + new(HGSS) { Species = 101, Level = 23, Location = 213, GroundTile = Building }, // Electrode @ Team Rocket HQ + new(HGSS) { Species = 143, Level = 50, Location = 159 }, // Snorlax @ Route 11 + new(HGSS) { Species = 143, Level = 50, Location = 160 }, // Snorlax @ Route 12 + new(HGSS) { Species = 185, Level = 20, Location = 184 }, // Sudowoodo @ Route 36, Encounter does not have type + + new(HGSS) // Spiky-Eared Pichu @ Ilex Forest + { + Species = 172, + Level = 30, + Gender = 1, + Form = 1, + Nature = Nature.Naughty, + Location = 214, + Moves = new(344, 270, 207, 220), + GroundTile = Max_Pt, + Shiny = Shiny.Never, + }, + + // Stationary Legendary + new(HGSS) { Species = 144, Level = 50, Location = 203, GroundTile = Cave }, // Articuno @ Seafoam Islands + new(HGSS) { Species = 145, Level = 50, Location = 158 }, // Zapdos @ Route 10 + new(HGSS) { Species = 146, Level = 50, Location = 219, GroundTile = Cave }, // Moltres @ Mt. Silver Cave + new(HGSS) { Species = 150, Level = 70, Location = 199, GroundTile = Cave }, // Mewtwo @ Cerulean Cave + new(HGSS) { Species = 245, Level = 40, Location = 173 }, // Suicune @ Route 25 + new(HGSS) { Species = 245, Level = 40, Location = 206, GroundTile = Cave }, // Suicune @ Burned Tower + new(HGSS) { Species = 384, Level = 50, Location = 232, GroundTile = Cave }, // Rayquaza @ Embedded Tower + new(HGSS) { Species = 483, Level = 01, Location = 231, FixedBall = Ball.Poke, GroundTile = Max_Pt }, // Dialga @ Sinjoh Ruins + new(HGSS) { Species = 484, Level = 01, Location = 231, FixedBall = Ball.Poke, GroundTile = Max_Pt }, // Palkia @ Sinjoh Ruins + new(HGSS) { Species = 487, Level = 01, Location = 231, FixedBall = Ball.Poke, GroundTile = Max_Pt, Form = 1, HeldItem = 112 }, // Giratina @ Sinjoh Ruins + + // Johto Roamers + new(HGSS) { Roaming = true, Species = 243, Location = 177, Level = 40, GroundTile = Grass | Water }, // Raikou + new(HGSS) { Roaming = true, Species = 244, Location = 177, Level = 40, GroundTile = Grass | Water }, // Entei + }; + + internal static readonly EncounterStatic4[] StaticHG = + { + new(HG ) { FixedBall = Ball.Poke, Species = 023, Level = 15, Location = 131, GroundTile = Max_Pt }, // Ekans + new(HG ) { Species = 249, Level = 70, Location = 218, GroundTile = Water }, // Lugia @ Whirl Islands + new(HG ) { Species = 250, Level = 45, Location = 205, GroundTile = Building }, // Ho-Oh @ Bell Tower + new(HG ) { Species = 381, Level = 40, Location = 140, GroundTile = Building }, // Latios @ Pewter City + new(HG ) { Species = 382, Level = 50, Location = 232, GroundTile = Cave }, // Kyogre @ Embedded Tower + new(HG ) { Roaming = true, Species = 380, Location = 149, Level = 35, GroundTile = Grass | Water }, // Latias + }; + + internal static readonly EncounterStatic4[] StaticSS = + { + new( SS) { FixedBall = Ball.Poke, Species = 027, Level = 15, Location = 131, GroundTile = Max_Pt }, // Sandshrew + new( SS) { Species = 249, Level = 45, Location = 218, GroundTile = Water }, // Lugia @ Whirl Islands + new( SS) { Species = 250, Level = 70, Location = 205, GroundTile = Building }, // Ho-Oh @ Bell Tower + new( SS) { Species = 380, Level = 40, Location = 140, GroundTile = Building }, // Latias @ Pewter City + new( SS) { Species = 383, Level = 50, Location = 232, GroundTile = Cave }, // Groudon @ Embedded Tower + new( SS) { Roaming = true, Species = 381, Location = 149, Level = 35, GroundTile = Grass | Water }, // Latios + }; + + #endregion + #region Trade Tables + internal static readonly EncounterTrade4PID[] TradeGift_HGSS = + { + new(TradeNames, 00, HGSS, 0x000025EF, 095, 01) { Ability = OnlySecond, TID16 = 48926, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(25,20,25,15,15,15) }, // Bellsprout -> Onix + new(TradeNames, 01, HGSS, 0x00002310, 066, 01) { Ability = OnlyFirst, TID16 = 37460, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(15,25,20,20,15,15) }, // Drowzee -> Machop + new(TradeNames, 02, HGSS, 0x000001DB, 100, 01) { Ability = OnlySecond, TID16 = 29189, SID16 = 00000, OTGender = 0, Gender = 2, IVs = new(15,20,15,25,25,15) }, // Krabby -> Voltorb + new(TradeNames, 03, HGSS, 0x0001FC0A, 085, 15) { Ability = OnlyFirst, TID16 = 00283, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(20,20,20,15,15,15) }, // Dragonair (15 from DPPt) -> Dodrio + new(TradeNames, 04, HGSS, 0x0000D136, 082, 19) { Ability = OnlyFirst, TID16 = 50082, SID16 = 00000, OTGender = 0, Gender = 2, IVs = new(15,20,15,20,20,20) }, // Dugtrio (19 from Diglett's Cave) -> Magneton + new(TradeNames, 05, HGSS, 0x000034E4, 178, 16) { Ability = OnlyFirst, TID16 = 15616, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(15,20,15,20,20,20) }, // Haunter (16 from Old Chateau) -> Xatu + new(TradeNames, 06, HGSS, 0x00485876, 025, 02) { Ability = OnlyFirst, TID16 = 33038, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(20,25,18,31,25,13) }, // Pikachu + new(TradeNames, 07, HGSS, 0x0012B6D4, 374, 31) { Ability = OnlyFirst, TID16 = 23478, SID16 = 00000, OTGender = 0, Gender = 2, IVs = new(28,29,24,23,24,25) }, // Forretress -> Beldum + new(TradeNames, 08, HGSS, 0x0012971C, 111, 01) { Ability = OnlyFirst, TID16 = 06845, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(22,31,13,00,22,09), Moves = new(422) }, // Bonsly -> Rhyhorn w/ Thunder Fang + new(TradeNames, 09, HGSS, 0x00101596, 208, 01) { Ability = OnlyFirst, TID16 = 26491, SID16 = 00000, OTGender = 1, Gender = 0, IVs = new(08,30,28,06,18,20) }, // Any -> Steelix + + //Gift + new(TradeNames, 10, HGSS, 0x00006B5E, 021, 20) { Ability = OnlyFirst, TID16 = 01001, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(15,20,15,20,20,20), MetLocation = 183, Moves = new(043,031,228,332) },// Webster's Spearow + new(TradeNames, 11, HGSS, 0x000214D7, 213, 20) { Ability = OnlySecond, TID16 = 04336, SID16 = 00001, OTGender = 0, Gender = 0, IVs = new(15,20,15,20,20,20), MetLocation = 130, Moves = new(132,117,227,219) },// Kirk's Shuckle + }; + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters4DPPt.cs b/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters4DPPt.cs deleted file mode 100644 index 6d45a04f7..000000000 --- a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters4DPPt.cs +++ /dev/null @@ -1,139 +0,0 @@ -using static PKHeX.Core.EncounterUtil; -using static PKHeX.Core.GameVersion; -using static PKHeX.Core.GroundTileAllowed; -using static PKHeX.Core.AbilityPermission; - -namespace PKHeX.Core; - -/// -/// Generation 4 Encounters -/// -internal static class Encounters4DPPt -{ - internal static readonly EncounterArea4[] SlotsD = EncounterArea4.GetAreas(Get("d", "da"), D); - internal static readonly EncounterArea4[] SlotsP = EncounterArea4.GetAreas(Get("p", "pe"), P); - internal static readonly EncounterArea4[] SlotsPt = EncounterArea4.GetAreas(Get("pt", "pt"), Pt); - - static Encounters4DPPt() => MarkEncounterTradeStrings(TradeGift_DPPt, TradeDPPt); - - #region Static Encounter/Gift Tables - private static readonly EncounterStatic4[] Encounter_DPPt = - { - // Starters - new(DP) { Gift = true, Species = 387, Level = 5, Location = 076, GroundTile = Max_DP }, // Turtwig @ Lake Verity - new(DP) { Gift = true, Species = 390, Level = 5, Location = 076, GroundTile = Max_DP }, // Chimchar - new(DP) { Gift = true, Species = 393, Level = 5, Location = 076, GroundTile = Max_DP }, // Piplup - new(Pt) { Gift = true, Species = 387, Level = 5, Location = 016, GroundTile = Max_Pt }, // Turtwig @ Route 201 - new(Pt) { Gift = true, Species = 390, Level = 5, Location = 016, GroundTile = Max_Pt }, // Chimchar - new(Pt) { Gift = true, Species = 393, Level = 5, Location = 016, GroundTile = Max_Pt }, // Piplup - - // Fossil @ Mining Museum - new(DP) { Gift = true, Species = 138, Level = 20, Location = 094, GroundTile = Max_DP }, // Omanyte - new(DP) { Gift = true, Species = 140, Level = 20, Location = 094, GroundTile = Max_DP }, // Kabuto - new(DP) { Gift = true, Species = 142, Level = 20, Location = 094, GroundTile = Max_DP }, // Aerodactyl - new(DP) { Gift = true, Species = 345, Level = 20, Location = 094, GroundTile = Max_DP }, // Lileep - new(DP) { Gift = true, Species = 347, Level = 20, Location = 094, GroundTile = Max_DP }, // Anorith - new(DP) { Gift = true, Species = 408, Level = 20, Location = 094, GroundTile = Max_DP }, // Cranidos - new(DP) { Gift = true, Species = 410, Level = 20, Location = 094, GroundTile = Max_DP }, // Shieldon - new(Pt) { Gift = true, Species = 138, Level = 20, Location = 094, GroundTile = Max_Pt }, // Omanyte - new(Pt) { Gift = true, Species = 140, Level = 20, Location = 094, GroundTile = Max_Pt }, // Kabuto - new(Pt) { Gift = true, Species = 142, Level = 20, Location = 094, GroundTile = Max_Pt }, // Aerodactyl - new(Pt) { Gift = true, Species = 345, Level = 20, Location = 094, GroundTile = Max_Pt }, // Lileep - new(Pt) { Gift = true, Species = 347, Level = 20, Location = 094, GroundTile = Max_Pt }, // Anorith - new(Pt) { Gift = true, Species = 408, Level = 20, Location = 094, GroundTile = Max_Pt }, // Cranidos - new(Pt) { Gift = true, Species = 410, Level = 20, Location = 094, GroundTile = Max_Pt }, // Shieldon - - // Gift - new(DP) { Gift = true, Species = 133, Level = 05, Location = 010, GroundTile = Max_DP }, // Eevee @ Hearthome City - new(Pt) { Gift = true, Species = 133, Level = 20, Location = 010, GroundTile = Max_Pt }, // Eevee @ Hearthome City - new(Pt) { Gift = true, Species = 137, Level = 25, Location = 012, GroundTile = Max_Pt }, // Porygon @ Veilstone City - new(Pt) { Gift = true, Species = 175, Level = 01, EggLocation = 2011 }, // Togepi Egg from Cynthia - new(DP) { Gift = true, Species = 440, Level = 01, EggLocation = 2009 }, // Happiny Egg from Traveling Man - new(DPPt) { Gift = true, Species = 447, Level = 01, EggLocation = 2010 }, // Riolu Egg from Riley - - // Stationary - new(DP) { Species = 425, Level = 22, Location = 47 }, // Drifloon @ Valley Windworks - new(Pt) { Species = 425, Level = 15, Location = 47 }, // Drifloon @ Valley Windworks - new(DP) { Species = 479, Level = 15, Location = 70, GroundTile = Building }, // Rotom @ Old Chateau - new(Pt) { Species = 479, Level = 20, Location = 70, GroundTile = Building }, // Rotom @ Old Chateau - new(DPPt) { Species = 442, Level = 25, Location = 24 }, // Spiritomb @ Route 209 - - // Stationary Legendary - new(Pt) { Species = 377, Level = 30, Location = 125, GroundTile = Cave }, // Regirock @ Rock Peak Ruins - new(Pt) { Species = 378, Level = 30, Location = 124, GroundTile = Cave }, // Regice @ Iceberg Ruins - new(Pt) { Species = 379, Level = 30, Location = 123, GroundTile = Cave }, // Registeel @ Iron Ruins - new(DPPt) { Species = 480, Level = 50, Location = 089, GroundTile = Cave }, // Uxie @ Acuity Cavern - new(DPPt) { Species = 482, Level = 50, Location = 088, GroundTile = Cave }, // Azelf @ Valor Cavern - new(D ) { Species = 483, Level = 47, Location = 051, GroundTile = Rock }, // Dialga @ Spear Pillar - new( P) { Species = 484, Level = 47, Location = 051, GroundTile = Rock }, // Palkia @ Spear Pillar - new(Pt) { Species = 483, Level = 70, Location = 051, GroundTile = Rock }, // Dialga @ Spear Pillar - new(Pt) { Species = 484, Level = 70, Location = 051, GroundTile = Rock }, // Palkia @ Spear Pillar - new(DP) { Species = 485, Level = 70, Location = 084, GroundTile = Cave }, // Heatran @ Stark Mountain - new(Pt) { Species = 485, Level = 50, Location = 084, GroundTile = Cave }, // Heatran @ Stark Mountain - new(DP) { Species = 486, Level = 70, Location = 064, GroundTile = Cave }, // Regigigas @ Snowpoint Temple - new(Pt) { Species = 486, Level = 01, Location = 064, GroundTile = Cave }, // Regigigas @ Snowpoint Temple - new(DP) { Species = 487, Level = 70, Location = 062, GroundTile = Cave, Form = 0 }, // Giratina @ Turnback Cave - new(Pt) { Species = 487, Level = 47, Location = 062, GroundTile = Cave, Form = 0 }, // Giratina @ Turnback Cave - new(Pt) { Species = 487, Level = 47, Location = 117, GroundTile = Distortion, Form = 1, HeldItem = 112 }, // Giratina @ Distortion World - - // Event - //new(DP) { Species = 491, Level = 40, Location = 079, GroundTile = Grass }, // Darkrai @ Newmoon Island (Unreleased in Diamond and Pearl) - new(Pt) { Species = 491, Level = 50, Location = 079, GroundTile = Grass }, // Darkrai @ Newmoon Island - new(Pt) { Species = 492, Form = 0, Level = 30, Location = 063, FatefulEncounter = true }, // Shaymin @ Flower Paradise - //new(DP) { Species = 492, Form = 0, Level = 30, Location = 063, Fateful = false }, // Shaymin @ Flower Paradise (Unreleased in Diamond and Pearl) - //new(DPPt) { Species = 493, Form = 0, Level = 80, Location = 086, GroundTile = Cave }, // Arceus @ Hall of Origin (Unreleased) - - // Roamers - new(DPPt) { Roaming = true, Location = 16, Species = 481, Level = 50, GroundTile = Grass | Water }, // Mesprit - new(DPPt) { Roaming = true, Location = 16, Species = 488, Level = 50, GroundTile = Grass | Water }, // Cresselia - new(Pt) { Roaming = true, Location = 16, Species = 144, Level = 60, GroundTile = Grass | Water }, // Articuno - new(Pt) { Roaming = true, Location = 16, Species = 145, Level = 60, GroundTile = Grass | Water }, // Zapdos - new(Pt) { Roaming = true, Location = 16, Species = 146, Level = 60, GroundTile = Grass | Water }, // Moltres - }; - #endregion - #region Trade Tables - - private static readonly EncounterTrade4[] RanchGifts = - { - new EncounterTrade4RanchGift(323975838, 025, 18) { Moves = new(447,085,148,104), TID16 = 1000, SID16 = 19840, OTGender = 1, MetLocation = 0068, Gender = 0, Ability = OnlyFirst, CurrentLevel = 20 }, // Pikachu - new EncounterTrade4RanchGift(323977664, 037, 16) { Moves = new(412,109,053,219), TID16 = 1000, SID16 = 21150, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlyFirst, CurrentLevel = 30 }, // Vulpix - new EncounterTrade4RanchGift(323975579, 077, 13) { Moves = new(036,033,039,052), TID16 = 1000, SID16 = 01123, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlySecond, CurrentLevel = 16 }, // Ponyta - new EncounterTrade4RanchGift(323975564, 108, 34) { Moves = new(076,111,014,205), TID16 = 1000, SID16 = 03050, OTGender = 1, MetLocation = 0077, Gender = 0, Ability = OnlyFirst, CurrentLevel = 40 }, // Lickitung - new EncounterTrade4RanchGift(323977579, 114, 01) { Moves = new(437,438,079,246), TID16 = 1000, SID16 = 49497, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlySecond }, // Tangela - new EncounterTrade4RanchGift(323977675, 133, 16) { Moves = new(363,270,098,247), TID16 = 1000, SID16 = 47710, OTGender = 1, MetLocation = 0068, Gender = 0, Ability = OnlySecond, CurrentLevel = 30 }, // Eevee - new EncounterTrade4RanchGift(323977588, 142, 20) { Moves = new(363,089,444,332), TID16 = 1000, SID16 = 43066, OTGender = 1, MetLocation = 0094, Gender = 0, Ability = OnlyFirst, CurrentLevel = 50 }, // Aerodactyl - new EncounterTrade4RanchGift(232975554, 193, 22) { Moves = new(318,095,246,138), TID16 = 1000, SID16 = 42301, OTGender = 1, MetLocation = 0052, Gender = 0, Ability = OnlyFirst, CurrentLevel = 45, Ball = 5 }, // Yanma - new EncounterTrade4RanchGift(323975570, 241, 16) { Moves = new(208,215,360,359), TID16 = 1000, SID16 = 02707, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlyFirst, CurrentLevel = 48 }, // Miltank - new EncounterTrade4RanchGift(323975563, 285, 22) { Moves = new(402,147,206,078), TID16 = 1000, SID16 = 02788, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlySecond, CurrentLevel = 45, Ball = 5 }, // Shroomish - new EncounterTrade4RanchGift(323975559, 320, 30) { Moves = new(156,323,133,058), TID16 = 1000, SID16 = 27046, OTGender = 1, MetLocation = 0038, Gender = 0, Ability = OnlySecond, CurrentLevel = 45 }, // Wailmer - new EncounterTrade4RanchGift(323977657, 360, 01) { Moves = new(204,150,227,000), TID16 = 1000, SID16 = 01788, OTGender = 1, MetLocation = 0004, Gender = 0, Ability = OnlySecond, EggLocation = 2000 }, // Wynaut - new EncounterTrade4RanchGift(323975563, 397, 02) { Moves = new(355,017,283,018), TID16 = 1000, SID16 = 59298, OTGender = 1, MetLocation = 0016, Gender = 0, Ability = OnlySecond, CurrentLevel = 23 }, // Staravia - new EncounterTrade4RanchGift(323970584, 415, 05) { Moves = new(230,016,000,000), TID16 = 1000, SID16 = 54140, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlyFirst, CurrentLevel = 20 }, // Combee - new EncounterTrade4RanchGift(323977539, 417, 09) { Moves = new(447,045,351,098), TID16 = 1000, SID16 = 18830, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlySecond, CurrentLevel = 10 }, // Pachirisu - new EncounterTrade4RanchGift(323974107, 422, 20) { Moves = new(363,352,426,104), TID16 = 1000, SID16 = 39272, OTGender = 1, MetLocation = 0028, Gender = 0, Ability = OnlySecond, CurrentLevel = 25, Form = 1 }, // Shellos - new EncounterTrade4RanchGift(323977566, 427, 10) { Moves = new(204,193,409,098), TID16 = 1000, SID16 = 31045, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlyFirst, CurrentLevel = 16 }, // Buneary - new EncounterTrade4RanchGift(323975579, 453, 22) { Moves = new(310,207,426,389), TID16 = 1000, SID16 = 41342, OTGender = 1, MetLocation = 0052, Gender = 0, Ability = OnlySecond, CurrentLevel = 31, Ball = 5 }, // Croagunk - new EncounterTrade4RanchGift(323977566, 456, 15) { Moves = new(213,352,219,392), TID16 = 1000, SID16 = 48348, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlyFirst, CurrentLevel = 35 }, // Finneon - new EncounterTrade4RanchGift(323975582, 459, 32) { Moves = new(452,420,275,059), TID16 = 1000, SID16 = 23360, OTGender = 1, MetLocation = 0031, Gender = 0, Ability = OnlyFirst, CurrentLevel = 41 }, // Snover - new EncounterTrade4RanchSpecial(151, 50) { Moves = new(235,216,095,100), TID16 = 1000, SID16 = 59228, OTGender = 1, Ball = 0x10, Gender = 2 }, // Mew - new EncounterTrade4RanchSpecial(489, 01) { Moves = new(447,240,156,057), TID16 = 1000, SID16 = 09248, OTGender = 1, Ball = 0x10, Gender = 2, CurrentLevel = 50, EggLocation = 3000 }, // Phione - }; - - private static readonly EncounterTrade4PID[] TradeGift_DPPtIngame = - { - new(DPPt, 0x0000008E, 063, 01) { Ability = OnlyFirst, TID16 = 25643, SID16 = 00000, OTGender = 1, Gender = 0, IVs = new(15,15,15,20,25,25) }, // Machop -> Abra - new(DPPt, 0x00000867, 441, 01) { Ability = OnlySecond, TID16 = 44142, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(15,20,15,25,25,15), Contest = 20 }, // Buizel -> Chatot - new(DPPt, 0x00000088, 093, 35) { Ability = OnlyFirst, TID16 = 19248, SID16 = 00000, OTGender = 1, Gender = 0, IVs = new(20,25,15,25,15,15) }, // Medicham (35 from Route 217) -> Haunter - new(DPPt, 0x0000045C, 129, 01) { Ability = OnlyFirst, TID16 = 53277, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(15,25,15,20,25,15) }, // Finneon -> Magikarp - }; - - internal static readonly EncounterTrade4[] TradeGift_DPPt = ArrayUtil.ConcatAll(TradeGift_DPPtIngame, RanchGifts); - - private const string tradeDPPt = "tradedppt"; - private static readonly string[][] TradeDPPt = Util.GetLanguageStrings8(tradeDPPt); - #endregion - - internal static readonly EncounterStatic4[] StaticD = GetEncounters(Encounter_DPPt, D); - internal static readonly EncounterStatic4[] StaticP = GetEncounters(Encounter_DPPt, P); - internal static readonly EncounterStatic4[] StaticPt = GetEncounters(Encounter_DPPt, Pt); -} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters4HGSS.cs b/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters4HGSS.cs deleted file mode 100644 index 36266fa96..000000000 --- a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters4HGSS.cs +++ /dev/null @@ -1,144 +0,0 @@ -using static PKHeX.Core.EncounterUtil; -using static PKHeX.Core.GameVersion; -using static PKHeX.Core.GroundTileAllowed; -using static PKHeX.Core.AbilityPermission; - -namespace PKHeX.Core; - -/// -/// Generation 4 Encounters -/// -internal static class Encounters4HGSS -{ - internal static readonly EncounterArea4[] SlotsHG = EncounterArea4.GetAreas(Get("hg", "hg"), HG); - internal static readonly EncounterArea4[] SlotsSS = EncounterArea4.GetAreas(Get("ss", "ss"), SS); - - private static readonly EncounterStatic4Pokewalker[] Encounter_PokeWalker = EncounterStatic4Pokewalker.GetAll(Util.GetBinaryResource("encounter_walker4.pkl")); - - static Encounters4HGSS() => MarkEncounterTradeStrings(TradeGift_HGSS, TradeHGSS); - - #region Static Encounter/Gift Tables - private static readonly EncounterStatic4[] Encounter_HGSS = - { - // Starters - new(HGSS) { Gift = true, Species = 001, Level = 05, Location = 138, GroundTile = Max_Pt }, // Bulbasaur @ Pallet Town - new(HGSS) { Gift = true, Species = 004, Level = 05, Location = 138, GroundTile = Max_Pt }, // Charmander - new(HGSS) { Gift = true, Species = 007, Level = 05, Location = 138, GroundTile = Max_Pt }, // Squirtle - new(HGSS) { Gift = true, Species = 152, Level = 05, Location = 126, GroundTile = Max_DP }, // Chikorita @ New Bark Town - new(HGSS) { Gift = true, Species = 155, Level = 05, Location = 126, GroundTile = Max_DP }, // Cyndaquil - new(HGSS) { Gift = true, Species = 158, Level = 05, Location = 126, GroundTile = Max_DP }, // Totodile - new(HGSS) { Gift = true, Species = 252, Level = 05, Location = 148, GroundTile = Max_Pt }, // Treecko @ Saffron City - new(HGSS) { Gift = true, Species = 255, Level = 05, Location = 148, GroundTile = Max_Pt }, // Torchic - new(HGSS) { Gift = true, Species = 258, Level = 05, Location = 148, GroundTile = Max_Pt }, // Mudkip - - // Fossils @ Pewter City - new(HGSS) { Gift = true, Species = 138, Level = 20, Location = 140, GroundTile = Max_Pt }, // Omanyte - new(HGSS) { Gift = true, Species = 140, Level = 20, Location = 140, GroundTile = Max_Pt }, // Kabuto - new(HGSS) { Gift = true, Species = 142, Level = 20, Location = 140, GroundTile = Max_Pt }, // Aerodactyl - new(HGSS) { Gift = true, Species = 345, Level = 20, Location = 140, GroundTile = Max_Pt }, // Lileep - new(HGSS) { Gift = true, Species = 347, Level = 20, Location = 140, GroundTile = Max_Pt }, // Anorith - new(HGSS) { Gift = true, Species = 408, Level = 20, Location = 140, GroundTile = Max_Pt }, // Cranidos - new(HGSS) { Gift = true, Species = 410, Level = 20, Location = 140, GroundTile = Max_Pt }, // Shieldon - - // Gift - new(HGSS) { Gift = true, Species = 072, Level = 15, Location = 130, GroundTile = Max_Pt }, // Tentacool @ Cianwood City - new(HGSS) { Gift = true, Species = 133, Level = 05, Location = 131, GroundTile = Max_Pt }, // Eevee @ Goldenrod City - new(HGSS) { Gift = true, Species = 147, Level = 15, Location = 222, GroundTile = Max_Pt, Moves = new(245) }, // Dratini @ Dragon's Den (ExtremeSpeed) - new(HGSS) { Gift = true, Species = 236, Level = 10, Location = 216, GroundTile = Max_Pt }, // Tyrogue @ Mt. Mortar - new(HGSS) { Gift = true, Species = 175, Level = 01, EggLocation = 2013, Moves = new((int)Move.Growl, (int)Move.Charm, (int)Move.Extrasensory) }, // Togepi Egg from Mr. Pokemon (Extrasensory as Egg move) - new(HGSS) { Gift = true, Species = 179, Level = 01, EggLocation = 2014 }, // Mareep Egg from Primo - new(HGSS) { Gift = true, Species = 194, Level = 01, EggLocation = 2014 }, // Wooper Egg from Primo - new(HGSS) { Gift = true, Species = 218, Level = 01, EggLocation = 2014 }, // Slugma Egg from Primo - - // Celadon City Game Corner - new(HGSS) { Gift = true, Species = 122, Level = 15, Location = 144, GroundTile = Max_Pt }, // Mr. Mime - new(HGSS) { Gift = true, Species = 133, Level = 15, Location = 144, GroundTile = Max_Pt }, // Eevee - new(HGSS) { Gift = true, Species = 137, Level = 15, Location = 144, GroundTile = Max_Pt }, // Porygon - - // Goldenrod City Game Corner - new(HGSS) { Gift = true, Species = 063, Level = 15, Location = 131, GroundTile = Max_Pt }, // Abra - new(HG ) { Gift = true, Species = 023, Level = 15, Location = 131, GroundTile = Max_Pt }, // Ekans - new( SS) { Gift = true, Species = 027, Level = 15, Location = 131, GroundTile = Max_Pt }, // Sandshrew - new(HGSS) { Gift = true, Species = 147, Level = 15, Location = 131, GroundTile = Max_Pt }, // Dratini - - // Team Rocket HQ Trap Floor - new(HGSS) { Species = 100, Level = 23, Location = 213, GroundTile = Building }, // Voltorb - new(HGSS) { Species = 074, Level = 21, Location = 213, GroundTile = Building }, // Geodude - new(HGSS) { Species = 109, Level = 21, Location = 213, GroundTile = Building }, // Koffing - - // Stationary - new(HGSS) { Species = 130, Level = 30, Location = 135, GroundTile = Water, Shiny = Shiny.Always }, // Gyarados @ Lake of Rage - new(HGSS) { Species = 131, Level = 20, Location = 210, GroundTile = Water }, // Lapras @ Union Cave Friday Only - new(HGSS) { Species = 101, Level = 23, Location = 213, GroundTile = Building }, // Electrode @ Team Rocket HQ - new(HGSS) { Species = 143, Level = 50, Location = 159 }, // Snorlax @ Route 11 - new(HGSS) { Species = 143, Level = 50, Location = 160 }, // Snorlax @ Route 12 - new(HGSS) { Species = 185, Level = 20, Location = 184 }, // Sudowoodo @ Route 36, Encounter does not have type - - new(HGSS) // Spiky-Eared Pichu @ Ilex Forest - { - Species = 172, - Level = 30, - Gender = 1, - Form = 1, - Nature = Nature.Naughty, - Location = 214, - Moves = new(344, 270, 207, 220), - GroundTile = Max_Pt, - Shiny = Shiny.Never, - }, - - // Stationary Legendary - new(HGSS) { Species = 144, Level = 50, Location = 203, GroundTile = Cave }, // Articuno @ Seafoam Islands - new(HGSS) { Species = 145, Level = 50, Location = 158 }, // Zapdos @ Route 10 - new(HGSS) { Species = 146, Level = 50, Location = 219, GroundTile = Cave }, // Moltres @ Mt. Silver Cave - new(HGSS) { Species = 150, Level = 70, Location = 199, GroundTile = Cave }, // Mewtwo @ Cerulean Cave - new(HGSS) { Species = 245, Level = 40, Location = 173 }, // Suicune @ Route 25 - new(HGSS) { Species = 245, Level = 40, Location = 206, GroundTile = Cave }, // Suicune @ Burned Tower - new( SS) { Species = 249, Level = 45, Location = 218, GroundTile = Water }, // Lugia @ Whirl Islands - new(HG ) { Species = 249, Level = 70, Location = 218, GroundTile = Water }, // Lugia @ Whirl Islands - new(HG ) { Species = 250, Level = 45, Location = 205, GroundTile = Building }, // Ho-Oh @ Bell Tower - new( SS) { Species = 250, Level = 70, Location = 205, GroundTile = Building }, // Ho-Oh @ Bell Tower - new( SS) { Species = 380, Level = 40, Location = 140, GroundTile = Building }, // Latias @ Pewter City - new(HG ) { Species = 381, Level = 40, Location = 140, GroundTile = Building }, // Latios @ Pewter City - new(HG ) { Species = 382, Level = 50, Location = 232, GroundTile = Cave }, // Kyogre @ Embedded Tower - new( SS) { Species = 383, Level = 50, Location = 232, GroundTile = Cave }, // Groudon @ Embedded Tower - new(HGSS) { Species = 384, Level = 50, Location = 232, GroundTile = Cave }, // Rayquaza @ Embedded Tower - new(HGSS) { Species = 483, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt }, // Dialga @ Sinjoh Ruins - new(HGSS) { Species = 484, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt }, // Palkia @ Sinjoh Ruins - new(HGSS) { Species = 487, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt, Form = 1, HeldItem = 112 }, // Giratina @ Sinjoh Ruins - - // Johto Roamers - new(HGSS) { Roaming = true, Species = 243, Location = 177, Level = 40, GroundTile = Grass | Water }, // Raikou - new(HGSS) { Roaming = true, Species = 244, Location = 177, Level = 40, GroundTile = Grass | Water }, // Entei - - // Kanto Roamers - new(HG ) { Roaming = true, Species = 380, Location = 149, Level = 35, GroundTile = Grass | Water }, // Latias - new( SS) { Roaming = true, Species = 381, Location = 149, Level = 35, GroundTile = Grass | Water }, // Latios - }; - #endregion - #region Trade Tables - internal static readonly EncounterTrade4PID[] TradeGift_HGSS = - { - new(HGSS, 0x000025EF, 095, 01) { Ability = OnlySecond, TID16 = 48926, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(25,20,25,15,15,15) }, // Bellsprout -> Onix - new(HGSS, 0x00002310, 066, 01) { Ability = OnlyFirst, TID16 = 37460, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(15,25,20,20,15,15) }, // Drowzee -> Machop - new(HGSS, 0x000001DB, 100, 01) { Ability = OnlySecond, TID16 = 29189, SID16 = 00000, OTGender = 0, Gender = 2, IVs = new(15,20,15,25,25,15) }, // Krabby -> Voltorb - new(HGSS, 0x0001FC0A, 085, 15) { Ability = OnlyFirst, TID16 = 00283, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(20,20,20,15,15,15) }, // Dragonair (15 from DPPt) -> Dodrio - new(HGSS, 0x0000D136, 082, 19) { Ability = OnlyFirst, TID16 = 50082, SID16 = 00000, OTGender = 0, Gender = 2, IVs = new(15,20,15,20,20,20) }, // Dugtrio (19 from Diglett's Cave) -> Magneton - new(HGSS, 0x000034E4, 178, 16) { Ability = OnlyFirst, TID16 = 15616, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(15,20,15,20,20,20) }, // Haunter (16 from Old Chateau) -> Xatu - new(HGSS, 0x00485876, 025, 02) { Ability = OnlyFirst, TID16 = 33038, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(20,25,18,31,25,13) }, // Pikachu - new(HGSS, 0x0012B6D4, 374, 31) { Ability = OnlyFirst, TID16 = 23478, SID16 = 00000, OTGender = 0, Gender = 2, IVs = new(28,29,24,23,24,25) }, // Forretress -> Beldum - new(HGSS, 0x0012971C, 111, 01) { Ability = OnlyFirst, TID16 = 06845, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(22,31,13,00,22,09), Moves = new(422) }, // Bonsly -> Rhyhorn - new(HGSS, 0x00101596, 208, 01) { Ability = OnlyFirst, TID16 = 26491, SID16 = 00000, OTGender = 1, Gender = 0, IVs = new(08,30,28,06,18,20) }, // Any -> Steelix - - //Gift - new(HGSS, 0x00006B5E, 021, 20) { Ability = OnlyFirst, TID16 = 01001, SID16 = 00000, OTGender = 0, Gender = 1, IVs = new(15,20,15,20,20,20), MetLocation = 183, Moves = new(043,031,228,332) },// Webster's Spearow - new(HGSS, 0x000214D7, 213, 20) { Ability = OnlySecond, TID16 = 04336, SID16 = 00001, OTGender = 0, Gender = 0, IVs = new(15,20,15,20,20,20), MetLocation = 130, Moves = new(132,117,227,219) },// Kirk's Shuckle - }; - - private const string tradeHGSS = "tradehgss"; - private static readonly string[][] TradeHGSS = Util.GetLanguageStrings8(tradeHGSS); - #endregion - - internal static readonly EncounterStatic[] StaticHG = GetEncounters(ArrayUtil.ConcatAll(Encounter_HGSS, Encounter_PokeWalker), HG); - internal static readonly EncounterStatic[] StaticSS = GetEncounters(ArrayUtil.ConcatAll(Encounter_HGSS, Encounter_PokeWalker), SS); -} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5B2W2.cs b/PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5B2W2.cs similarity index 52% rename from PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5B2W2.cs rename to PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5B2W2.cs index 5485f2f84..3e1cc4e75 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5B2W2.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5B2W2.cs @@ -1,7 +1,6 @@ using static PKHeX.Core.EncounterUtil; using static PKHeX.Core.GameVersion; using static PKHeX.Core.AbilityPermission; -using static PKHeX.Core.Encounters5DR; namespace PKHeX.Core; @@ -13,10 +12,11 @@ public static class Encounters5B2W2 internal static readonly EncounterArea5[] SlotsB2 = EncounterArea5.GetAreas(Get("b2", "52"), B2); internal static readonly EncounterArea5[] SlotsW2 = EncounterArea5.GetAreas(Get("w2", "52"), W2); - static Encounters5B2W2() => MarkEncounterTradeStrings(TradeGift_B2W2_Regular, TradeB2W2); + private const string tradeB2W2 = "tradeb2w2"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings8(tradeB2W2); #region DreamWorld Encounter - public static readonly EncounterStatic5[] DreamWorld_B2W2 = DreamWorldEntry.GetArray(B2W2, stackalloc DreamWorldEntry[] + public static readonly EncounterStatic5Entree[] DreamWorld_B2W2 = DreamWorldEntry.GetArray(B2W2, stackalloc DreamWorldEntry[] { // Pleasant Forest new(535, 10, 496, 414, 352), // Tympole @@ -81,45 +81,40 @@ public static class Encounters5B2W2 }); #endregion #region Static Encounter/Gift Tables - private static readonly EncounterStatic5[] Encounter_B2W2_Regular = + + public static readonly EncounterStatic5[] Encounter_B2W2_Regular = { // Starters @ Aspertia City - new(B2W2) { Gift = true, Species = 495, Level = 05, Location = 117 }, // Snivy - new(B2W2) { Gift = true, Species = 498, Level = 05, Location = 117 }, // Tepig - new(B2W2) { Gift = true, Species = 501, Level = 05, Location = 117 }, // Oshawott + new(B2W2) { FixedBall = Ball.Poke, Species = 495, Level = 05, Location = 117 }, // Snivy + new(B2W2) { FixedBall = Ball.Poke, Species = 498, Level = 05, Location = 117 }, // Tepig + new(B2W2) { FixedBall = Ball.Poke, Species = 501, Level = 05, Location = 117 }, // Oshawott // Fossils @ Nacrene City - new(B2W2) { Gift = true, Species = 138, Level = 25, Location = 007 }, // Omanyte - new(B2W2) { Gift = true, Species = 140, Level = 25, Location = 007 }, // Kabuto - new(B2W2) { Gift = true, Species = 142, Level = 25, Location = 007 }, // Aerodactyl - new(B2W2) { Gift = true, Species = 345, Level = 25, Location = 007 }, // Lileep - new(B2W2) { Gift = true, Species = 347, Level = 25, Location = 007 }, // Anorith - new(B2W2) { Gift = true, Species = 408, Level = 25, Location = 007 }, // Cranidos - new(B2W2) { Gift = true, Species = 410, Level = 25, Location = 007 }, // Shieldon - new(B2W2) { Gift = true, Species = 564, Level = 25, Location = 007 }, // Tirtouga - new(B2W2) { Gift = true, Species = 566, Level = 25, Location = 007 }, // Archen + new(B2W2) { FixedBall = Ball.Poke, Species = 138, Level = 25, Location = 007 }, // Omanyte + new(B2W2) { FixedBall = Ball.Poke, Species = 140, Level = 25, Location = 007 }, // Kabuto + new(B2W2) { FixedBall = Ball.Poke, Species = 142, Level = 25, Location = 007 }, // Aerodactyl + new(B2W2) { FixedBall = Ball.Poke, Species = 345, Level = 25, Location = 007 }, // Lileep + new(B2W2) { FixedBall = Ball.Poke, Species = 347, Level = 25, Location = 007 }, // Anorith + new(B2W2) { FixedBall = Ball.Poke, Species = 408, Level = 25, Location = 007 }, // Cranidos + new(B2W2) { FixedBall = Ball.Poke, Species = 410, Level = 25, Location = 007 }, // Shieldon + new(B2W2) { FixedBall = Ball.Poke, Species = 564, Level = 25, Location = 007 }, // Tirtouga + new(B2W2) { FixedBall = Ball.Poke, Species = 566, Level = 25, Location = 007 }, // Archen // Gift - new(B2W2) { Gift = true, Species = 133, Level = 10, Location = 008, Ability = OnlyHidden }, // HA Eevee @ Castelia City - new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 0 }, // HA Deerling @ Route 6 - new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 1 }, // HA Deerling @ Route 6 - new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 2 }, // HA Deerling @ Route 6 - new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 3 }, // HA Deerling @ Route 6 - new(B2 ) { Gift = true, Species = 443, Level = 01, Location = 122, Shiny = Shiny.Always, Gender = 0 }, // Shiny Gible @ Floccesy Town - new( W2) { Gift = true, Species = 147, Level = 01, Location = 122, Shiny = Shiny.Always, Gender = 0 }, // Shiny Dratini @ Floccesy Town - new(B2W2) { Gift = true, Species = 129, Level = 05, Location = 068 } , // Magikarp @ Marvelous Bridge - new(B2W2) { Gift = true, Species = 440, Level = 01, EggLocation = 60003 }, // Happiny Egg from PKMN Breeder + new(B2W2) { FixedBall = Ball.Poke, Species = 133, Level = 10, Location = 008, Ability = OnlyHidden }, // HA Eevee @ Castelia City + new(B2W2) { FixedBall = Ball.Poke, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 0 }, // HA Deerling @ Route 6 + new(B2W2) { FixedBall = Ball.Poke, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 1 }, // HA Deerling @ Route 6 + new(B2W2) { FixedBall = Ball.Poke, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 2 }, // HA Deerling @ Route 6 + new(B2W2) { FixedBall = Ball.Poke, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 3 }, // HA Deerling @ Route 6 + new(B2W2) { FixedBall = Ball.Poke, Species = 129, Level = 05, Location = 068 } , // Magikarp @ Marvelous Bridge + new(B2W2) { FixedBall = Ball.Poke, Species = 440, Level = 01, EggLocation = 60003, Location = 0 }, // Happiny Egg from PKMN Breeder // Stationary new(B2W2) { Species = 590, Level = 29, Location = 019 }, // Foongus @ Route 6 new(B2W2) { Species = 591, Level = 43, Location = 024 }, // Amoonguss @ Route 11 new(B2W2) { Species = 591, Level = 47, Location = 127 }, // Amoonguss @ Route 22 new(B2W2) { Species = 591, Level = 56, Location = 128 }, // Amoonguss @ Route 23 - new(B2 ) { Species = 593, Level = 40, Location = 071, Ability = OnlyHidden, Gender = 0 }, // HA Jellicent @ Undella Bay Mon Only - new( W2) { Species = 593, Level = 40, Location = 071, Ability = OnlyHidden, Gender = 1 }, // HA Jellicent @ Undella Bay Thurs Only new(B2W2) { Species = 593, Level = 40, Location = 071 }, // HA Jellicent @ Undella Bay EncounterSlot collision - new( W2) { Species = 628, Level = 25, Location = 017, Ability = OnlyHidden, Gender = 0 }, // HA Braviary @ Route 4 Mon Only - new(B2 ) { Species = 630, Level = 25, Location = 017, Ability = OnlyHidden, Gender = 1 }, // HA Mandibuzz @ Route 4 Thurs Only new(B2W2) { Species = 637, Level = 35, Location = 035 }, // Volcarona @ Relic Castle new(B2W2) { Species = 637, Level = 65, Location = 035 }, // Volcarona @ Relic Castle new(B2W2) { Species = 558, Level = 42, Location = 141 }, // Crustle @ Seaside Cave @@ -129,8 +124,6 @@ public static class Encounters5B2W2 new(B2W2) { Species = 377, Level = 65, Location = 150 }, // Regirock @ Rock Peak Chamber new(B2W2) { Species = 378, Level = 65, Location = 151 }, // Regice @ Iceberg Chamber new(B2W2) { Species = 379, Level = 65, Location = 152 }, // Registeel @ Iron Chamber - new( W2) { Species = 380, Level = 68, Location = 032 }, // Latias @ Dreamyard - new(B2 ) { Species = 381, Level = 68, Location = 032 }, // Latios @ Dreamyard new(B2W2) { Species = 480, Level = 65, Location = 007 }, // Uxie @ Nacrene City new(B2W2) { Species = 481, Level = 65, Location = 056 }, // Mesprit @ Celestial Tower new(B2W2) { Species = 482, Level = 65, Location = 128 }, // Azelf @ Route 23 @@ -143,12 +136,28 @@ public static class Encounters5B2W2 new(B2W2) { Species = 639, Level = 65, Location = 127 }, // Terrakion @ Route 22 new(B2W2) { Species = 640, Level = 45, Location = 024 }, // Virizion @ Route 11 new(B2W2) { Species = 640, Level = 65, Location = 024 }, // Virizion @ Route 11 - new( W2) { Species = 643, Level = 70, Location = 039, Shiny = Shiny.Never }, // Reshiram @ Dragonspiral Tower - new(B2 ) { Species = 644, Level = 70, Location = 039, Shiny = Shiny.Never }, // Zekrom @ Dragonspiral Tower new(B2W2) { Species = 646, Level = 70, Location = 061, Form = 0 }, // Kyurem @ Giant Chasm }; - private static readonly EncounterStatic5N[] Encounter_B2W2_N = + public static readonly EncounterStatic5[] StaticB2 = + { + new(B2 ) { Species = 443, Level = 01, Location = 122, Shiny = Shiny.Always, Gender = 0, FixedBall = Ball.Poke }, // Shiny Gible @ Floccesy Town + new(B2 ) { Species = 381, Level = 68, Location = 032 }, // Latios @ Dreamyard + new(B2 ) { Species = 593, Level = 40, Location = 071, Ability = OnlyHidden, Gender = 0 }, // HA Jellicent @ Undella Bay Mon Only + new(B2 ) { Species = 630, Level = 25, Location = 017, Ability = OnlyHidden, Gender = 1 }, // HA Mandibuzz @ Route 4 Thurs Only + new(B2 ) { Species = 644, Level = 70, Location = 039, Shiny = Shiny.Never }, // Zekrom @ Dragonspiral Tower + }; + + public static readonly EncounterStatic5[] StaticW2 = + { + new( W2) { Species = 147, Level = 01, Location = 122, Shiny = Shiny.Always, Gender = 0, FixedBall = Ball.Poke }, // Shiny Dratini @ Floccesy Town + new( W2) { Species = 380, Level = 68, Location = 032 }, // Latias @ Dreamyard + new( W2) { Species = 593, Level = 40, Location = 071, Ability = OnlyHidden, Gender = 1 }, // HA Jellicent @ Undella Bay Thurs Only + new( W2) { Species = 628, Level = 25, Location = 017, Ability = OnlyHidden, Gender = 0 }, // HA Braviary @ Route 4 Mon Only + new( W2) { Species = 643, Level = 70, Location = 039, Shiny = Shiny.Never }, // Reshiram @ Dragonspiral Tower + }; + + public static readonly EncounterStatic5N[] Encounter_B2W2_N = { // N's Pokemon new(0xFF01007F) { Species = 509, Level = 07, Location = 015, Ability = OnlySecond, Nature = Nature.Timid }, // Purloin @ Route 2 @@ -165,66 +174,64 @@ public static class Encounters5B2W2 new(0xFF01007F) { Species = 595, Level = 28, Location = 037, Ability = OnlySecond, Nature = Nature.Docile }, // Joltik @ Chargestone Cave new(0xFF00007F) { Species = 597, Level = 28, Location = 037, Ability = OnlyFirst, Nature = Nature.Bashful }, // Ferroseed @ Chargestone Cave new(0xFF000000) { Species = 599, Level = 28, Location = 037, Ability = OnlyFirst, Nature = Nature.Rash }, // Klink @ Chargestone Cave - new(0xFF00001F) { Species = 570, Level = 25, Location = 010, Ability = OnlyFirst, Nature = Nature.Hasty, Gift = true }, // N's Zorua @ Driftveil City + new(0xFF00001F) { Species = 570, Level = 25, Location = 010, Ability = OnlyFirst, Nature = Nature.Hasty }, // N's Zorua @ Driftveil City }; - private static readonly EncounterStatic5[] Encounter_B2W2 = ArrayUtil.ConcatAll(Encounter_B2W2_Regular, Encounter_B2W2_N, Encounter_DreamRadar); #endregion #region Trade Tables - private static readonly EncounterTrade5[] TradeGift_B2W2_Regular = - { - new(B2 ) { Species = 548, Level = 20, Ability = OnlySecond, TID16 = 65217, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(20,20,20,20,31,20), Nature = Nature.Timid }, // Petilil - new( W2) { Species = 546, Level = 20, Ability = OnlyFirst, TID16 = 05720, SID16 = 00001, OTGender = 0, Gender = 0, IVs = new(20,20,20,20,31,20), Nature = Nature.Modest }, // Cottonee - new(B2W2) { Species = 526, Level = 35, Ability = OnlyFirst, TID16 = 11195, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(20,31,20,20,20,20), Nature = Nature.Adamant, IsNicknamed = false }, // Gigalith - new(B2W2) { Species = 465, Level = 45, Ability = OnlyFirst, TID16 = 27658, SID16 = 00001, OTGender = 0, Gender = 0, IVs = new(31,20,20,20,20,20), Nature = Nature.Hardy }, // Tangrowth - new(B2W2) { Species = 479, Level = 60, Ability = OnlyFirst, TID16 = 54673, SID16 = 00000, OTGender = 1, Gender = 2, IVs = new(20,20,20,20,20,31), Nature = Nature.Calm }, // Rotom - new(B2W2) { Species = 424, Level = 40, Ability = OnlySecond, TID16 = 17074, SID16 = 00001, OTGender = 1, Gender = 0, IVs = new(20,20,20,31,20,20), Nature = Nature.Jolly }, // Ambipom - new(B2W2) { Species = 065, Level = 40, Ability = OnlyFirst, TID16 = 17074, SID16 = 00001, OTGender = 1, Gender = 0, IVs = new(20,20,20,31,20,20), Nature = Nature.Timid }, // Alakazam - }; - - internal const int YancyTID = 10303; - internal const int CurtisTID = 54118; + private const ushort YancyTID = 10303; + private const ushort CurtisTID = 54118; private static readonly string[] TradeOT_B2W2_F = { string.Empty, "ルリ", "Yancy", "Brenda", "Lilì", "Sabine", string.Empty, "Belinda", "루리" }; private static readonly string[] TradeOT_B2W2_M = { string.Empty, "テツ", "Curtis", "Julien", "Dadi", "Markus", string.Empty, "Julián", "철권" }; - private static readonly EncounterTrade5[] TradeGift_B2W2_YancyCurtis = + public static readonly EncounterTrade5B2W2[] TradeGift_B2W2 = { + new(TradeNames, 00, B2 ) { Species = 548, Level = 20, Ability = OnlySecond, ID32 = 65217, OTGender = 1, Gender = 1, IVs = new(20,20,20,20,31,20), Nature = Nature.Timid }, // Petilil + new(TradeNames, 01, W2) { Species = 546, Level = 20, Ability = OnlyFirst, ID32 = 71256, OTGender = 0, Gender = 0, IVs = new(20,20,20,20,31,20), Nature = Nature.Modest }, // Cottonee + new(TradeNames, 02, B2W2) { Species = 526, Level = 35, Ability = OnlyFirst, ID32 = 11195, OTGender = 0, Gender = 0, IVs = new(20,31,20,20,20,20), Nature = Nature.Adamant, IsFixedNickname = false }, // Gigalith + new(TradeNames, 03, B2W2) { Species = 465, Level = 45, Ability = OnlyFirst, ID32 = 93194, OTGender = 0, Gender = 0, IVs = new(31,20,20,20,20,20), Nature = Nature.Hardy }, // Tangrowth + new(TradeNames, 04, B2W2) { Species = 479, Level = 60, Ability = OnlyFirst, ID32 = 54673, OTGender = 1, Gender = 2, IVs = new(20,20,20,20,20,31), Nature = Nature.Calm }, // Rotom + new(TradeNames, 05, B2W2) { Species = 424, Level = 40, Ability = OnlySecond, ID32 = 82610, OTGender = 1, Gender = 0, IVs = new(20,20,20,31,20,20), Nature = Nature.Jolly }, // Ambipom + new(TradeNames, 06, B2W2) { Species = 065, Level = 40, Ability = OnlyFirst, ID32 = 82610, OTGender = 1, Gender = 0, IVs = new(20,20,20,31,20,20), Nature = Nature.Timid }, // Alakazam + // Player is Male - new(B2W2) { Species = 052, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Meowth - new(B2W2) { Species = 202, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Wobbuffet - new(B2W2) { Species = 280, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Ralts - new(B2W2) { Species = 410, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Shieldon - new(B2W2) { Species = 111, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Rhyhorn - new(B2W2) { Species = 422, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F, Form = 0 }, // Shellos-West - new(B2W2) { Species = 303, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Mawile - new(B2W2) { Species = 442, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Spiritomb - new(B2W2) { Species = 143, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Snorlax - new(B2W2) { Species = 216, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Teddiursa - new(B2W2) { Species = 327, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Spinda - new(B2W2) { Species = 175, Level = 50, Ability = OnlyHidden, TID16 = 10303, SID16 = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Togepi + new(TradeOT_B2W2_F, B2W2) { Species = 052, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Meowth + new(TradeOT_B2W2_F, B2W2) { Species = 202, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Wobbuffet + new(TradeOT_B2W2_F, B2W2) { Species = 280, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Ralts + new(TradeOT_B2W2_F, B2W2) { Species = 410, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Shieldon + new(TradeOT_B2W2_F, B2W2) { Species = 111, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Rhyhorn + new(TradeOT_B2W2_F, B2W2) { Species = 422, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1, Form = 0 }, // Shellos-West + new(TradeOT_B2W2_F, B2W2) { Species = 303, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Mawile + new(TradeOT_B2W2_F, B2W2) { Species = 442, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Spiritomb + new(TradeOT_B2W2_F, B2W2) { Species = 143, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Snorlax + new(TradeOT_B2W2_F, B2W2) { Species = 216, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Teddiursa + new(TradeOT_B2W2_F, B2W2) { Species = 327, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Spinda + new(TradeOT_B2W2_F, B2W2) { Species = 175, Level = 50, Ability = OnlyHidden, ID32 = YancyTID, OTGender = 1 }, // Togepi // Player is Female - new(B2W2) { Species = 056, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Mankey - new(B2W2) { Species = 202, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Wobbuffet - new(B2W2) { Species = 280, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Ralts - new(B2W2) { Species = 408, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Cranidos - new(B2W2) { Species = 111, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Rhyhorn - new(B2W2) { Species = 422, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M, Form = 1 }, // Shellos-East - new(B2W2) { Species = 302, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Sableye - new(B2W2) { Species = 442, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Spiritomb - new(B2W2) { Species = 143, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Snorlax - new(B2W2) { Species = 231, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Phanpy - new(B2W2) { Species = 327, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Spinda - new(B2W2) { Species = 175, Level = 50, Ability = OnlyHidden, TID16 = 54118, SID16 = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Togepi + new(TradeOT_B2W2_M, B2W2) { Species = 056, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Mankey + new(TradeOT_B2W2_M, B2W2) { Species = 202, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Wobbuffet + new(TradeOT_B2W2_M, B2W2) { Species = 280, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Ralts + new(TradeOT_B2W2_M, B2W2) { Species = 408, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Cranidos + new(TradeOT_B2W2_M, B2W2) { Species = 111, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Rhyhorn + new(TradeOT_B2W2_M, B2W2) { Species = 422, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0, Form = 1 }, // Shellos-East + new(TradeOT_B2W2_M, B2W2) { Species = 302, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Sableye + new(TradeOT_B2W2_M, B2W2) { Species = 442, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Spiritomb + new(TradeOT_B2W2_M, B2W2) { Species = 143, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Snorlax + new(TradeOT_B2W2_M, B2W2) { Species = 231, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Phanpy + new(TradeOT_B2W2_M, B2W2) { Species = 327, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Spinda + new(TradeOT_B2W2_M, B2W2) { Species = 175, Level = 50, Ability = OnlyHidden, ID32 = CurtisTID, OTGender = 0 }, // Togepi }; - private const string tradeB2W2 = "tradeb2w2"; - private static readonly string[][] TradeB2W2 = Util.GetLanguageStrings8(tradeB2W2); + public static readonly EncounterTrade5B2W2[] TradeGift_W2 = + { + new(TradeNames, 01, W2) { Species = 546, Level = 20, Ability = OnlyFirst, ID32 = 71256, OTGender = 0, Gender = 0, IVs = new(20,20,20,20,31,20), Nature = Nature.Modest }, // Cottonee + }; - internal static readonly EncounterTrade5[] TradeGift_B2W2 = ArrayUtil.ConcatAll(TradeGift_B2W2_Regular, TradeGift_B2W2_YancyCurtis); + public static readonly EncounterTrade5B2W2[] TradeGift_B2 = + { + new(TradeNames, 00, B2 ) { Species = 548, Level = 20, Ability = OnlySecond, ID32 = 65217, OTGender = 1, Gender = 1, IVs = new(20,20,20,20,31,20), Nature = Nature.Timid }, // Petilil + }; #endregion - - internal static readonly EncounterStatic5[] StaticB2 = ArrayUtil.ConcatAll(GetEncounters(Encounter_B2W2, B2), DreamWorld_Common, DreamWorld_B2W2); - internal static readonly EncounterStatic5[] StaticW2 = ArrayUtil.ConcatAll(GetEncounters(Encounter_B2W2, W2), DreamWorld_Common, DreamWorld_B2W2); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5BW.cs b/PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5BW.cs similarity index 66% rename from PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5BW.cs rename to PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5BW.cs index ec1edcf97..9be0c99c7 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5BW.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5BW.cs @@ -1,7 +1,6 @@ using static PKHeX.Core.EncounterUtil; using static PKHeX.Core.GameVersion; using static PKHeX.Core.AbilityPermission; -using static PKHeX.Core.Encounters5DR; namespace PKHeX.Core; @@ -13,11 +12,12 @@ public static class Encounters5BW internal static readonly EncounterArea5[] SlotsB = EncounterArea5.GetAreas(Get("b", "51"), B); internal static readonly EncounterArea5[] SlotsW = EncounterArea5.GetAreas(Get("w", "51"), W); - static Encounters5BW() => MarkEncounterTradeStrings(TradeGift_BW, TradeBW); + private const string tradeBW = "tradebw"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings8(tradeBW); #region DreamWorld Encounter - public static readonly EncounterStatic5[] DreamWorld_BW = DreamWorldEntry.GetArray(BW, stackalloc DreamWorldEntry[] + public static readonly EncounterStatic5Entree[] DreamWorld_BW = DreamWorldEntry.GetArray(BW, stackalloc DreamWorldEntry[] { // Pleasant Forest new(029, 10, 010, 389, 162), // Nidoran♀ @@ -120,30 +120,30 @@ public static class Encounters5BW #endregion #region Static Encounter/Gift Tables - private static readonly EncounterStatic5[] Encounter_BW = + public static readonly EncounterStatic5[] Encounter_BW = { // Starters @ Nuvema Town - new(BW) { Gift = true, Species = 495, Level = 05, Location = 004 }, // Snivy - new(BW) { Gift = true, Species = 498, Level = 05, Location = 004 }, // Tepig - new(BW) { Gift = true, Species = 501, Level = 05, Location = 004 }, // Oshawott + new(BW) { FixedBall = Ball.Poke, Species = 495, Level = 05, Location = 004 }, // Snivy + new(BW) { FixedBall = Ball.Poke, Species = 498, Level = 05, Location = 004 }, // Tepig + new(BW) { FixedBall = Ball.Poke, Species = 501, Level = 05, Location = 004 }, // Oshawott // Fossils @ Nacrene City - new(BW) { Gift = true, Species = 138, Level = 25, Location = 007 }, // Omanyte - new(BW) { Gift = true, Species = 140, Level = 25, Location = 007 }, // Kabuto - new(BW) { Gift = true, Species = 142, Level = 25, Location = 007 }, // Aerodactyl - new(BW) { Gift = true, Species = 345, Level = 25, Location = 007 }, // Lileep - new(BW) { Gift = true, Species = 347, Level = 25, Location = 007 }, // Anorith - new(BW) { Gift = true, Species = 408, Level = 25, Location = 007 }, // Cranidos - new(BW) { Gift = true, Species = 410, Level = 25, Location = 007 }, // Shieldon - new(BW) { Gift = true, Species = 564, Level = 25, Location = 007 }, // Tirtouga - new(BW) { Gift = true, Species = 566, Level = 25, Location = 007 }, // Archen + new(BW) { FixedBall = Ball.Poke, Species = 138, Level = 25, Location = 007 }, // Omanyte + new(BW) { FixedBall = Ball.Poke, Species = 140, Level = 25, Location = 007 }, // Kabuto + new(BW) { FixedBall = Ball.Poke, Species = 142, Level = 25, Location = 007 }, // Aerodactyl + new(BW) { FixedBall = Ball.Poke, Species = 345, Level = 25, Location = 007 }, // Lileep + new(BW) { FixedBall = Ball.Poke, Species = 347, Level = 25, Location = 007 }, // Anorith + new(BW) { FixedBall = Ball.Poke, Species = 408, Level = 25, Location = 007 }, // Cranidos + new(BW) { FixedBall = Ball.Poke, Species = 410, Level = 25, Location = 007 }, // Shieldon + new(BW) { FixedBall = Ball.Poke, Species = 564, Level = 25, Location = 007 }, // Tirtouga + new(BW) { FixedBall = Ball.Poke, Species = 566, Level = 25, Location = 007 }, // Archen // Gift - new(BW) { Gift = true, Species = 511, Level = 10, Location = 032 }, // Pansage @ Dreamyard - new(BW) { Gift = true, Species = 513, Level = 10, Location = 032 }, // Pansear - new(BW) { Gift = true, Species = 515, Level = 10, Location = 032 }, // Panpour - new(BW) { Gift = true, Species = 129, Level = 05, Location = 068 }, // Magikarp @ Marvelous Bridge - new(BW) { Gift = true, Species = 636, Level = 01, EggLocation = 60003 }, // Larvesta Egg from Treasure Hunter + new(BW) { FixedBall = Ball.Poke, Species = 511, Level = 10, Location = 032 }, // Pansage @ Dreamyard + new(BW) { FixedBall = Ball.Poke, Species = 513, Level = 10, Location = 032 }, // Pansear + new(BW) { FixedBall = Ball.Poke, Species = 515, Level = 10, Location = 032 }, // Panpour + new(BW) { FixedBall = Ball.Poke, Species = 129, Level = 05, Location = 068 }, // Magikarp @ Marvelous Bridge + new(BW) { FixedBall = Ball.Poke, Species = 636, Level = 01, EggLocation = 60003, Location = 0 }, // Larvesta Egg from Treasure Hunter // Stationary new(BW) { Species = 518, Level = 50, Location = 032, Ability = OnlyHidden }, // Musharna @ Dreamyard Friday Only @@ -157,10 +157,6 @@ public static class Encounters5BW new(BW) { Species = 638, Level = 42, Location = 074 }, // Cobalion @ Guidance Chamber new(BW) { Species = 639, Level = 42, Location = 073 }, // Terrakion @ Trial Chamber new(BW) { Species = 640, Level = 42, Location = 055 }, // Virizion @ Rumination Field - new(B ) { Species = 643, Level = 50, Location = 045, Shiny = Shiny.Never }, // Reshiram @ N's Castle - new(B ) { Species = 643, Level = 50, Location = 039, Shiny = Shiny.Never }, // Reshiram @ Dragonspiral Tower - new( W) { Species = 644, Level = 50, Location = 045, Shiny = Shiny.Never }, // Zekrom @ N's Castle - new( W) { Species = 644, Level = 50, Location = 039, Shiny = Shiny.Never }, // Zekrom @ Dragonspiral Tower new(BW) { Species = 645, Level = 70, Location = 070 }, // Landorus @ Abundant Shrine new(BW) { Species = 646, Level = 75, Location = 061 }, // Kyurem @ Giant Chasm @@ -168,29 +164,40 @@ public static class Encounters5BW new(BW) { Species = 494, Level = 15, Location = 062, Shiny = Shiny.Never}, // Victini @ Liberty Garden new(BW) { Species = 570, Level = 10, Location = 008, Shiny = Shiny.Never, Gender = 0 }, // Zorua @ Castelia City new(BW) { Species = 571, Level = 25, Location = 072, Shiny = Shiny.Never, Gender = 1 }, // Zoroark @ Lostlorn Forest + }; - // Roamer - new(B ) { Roaming = true, Species = 641, Level = 40, Location = 25 }, // Tornadus + public static readonly EncounterStatic5[] StaticB = + { + new(B) { Species = 643, Level = 50, Location = 045, Shiny = Shiny.Never }, // Reshiram @ N's Castle + new(B) { Species = 643, Level = 50, Location = 039, Shiny = Shiny.Never }, // Reshiram @ Dragonspiral Tower + new(B) { Roaming = true, Species = 641, Level = 40, Location = 25 }, // Tornadus + }; + public static readonly EncounterStatic5[] StaticW = + { + new( W) { Species = 644, Level = 50, Location = 045, Shiny = Shiny.Never }, // Zekrom @ N's Castle + new( W) { Species = 644, Level = 50, Location = 039, Shiny = Shiny.Never }, // Zekrom @ Dragonspiral Tower new( W) { Roaming = true, Species = 642, Level = 40, Location = 25 }, // Thundurus }; #endregion #region Trade Tables - internal static readonly EncounterTrade5PID[] TradeGift_BW = + internal static readonly EncounterTrade5BW[] TradeGift_BW = { - new(B , 0x64000000) { Species = 548, Level = 15, Ability = OnlyFirst, TID16 = 39922, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(20,20,20,20,31,20), Nature = Nature.Modest }, // Petilil - new( W, 0x6400007E) { Species = 546, Level = 15, Ability = OnlyFirst, TID16 = 39922, SID16 = 00000, OTGender = 1, Gender = 1, IVs = new(20,20,20,20,31,20), Nature = Nature.Modest }, // Cottonee - new(B , 0x9400007F) { Species = 550, Level = 25, Ability = OnlyFirst, TID16 = 27646, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(20,31,20,20,20,20), Nature = Nature.Adamant, Form = 0 }, // Basculin-Red - new( W, 0x9400007F) { Species = 550, Level = 25, Ability = OnlyFirst, TID16 = 27646, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(20,31,20,20,20,20), Nature = Nature.Adamant, Form = 1 }, // Basculin-Blue - new(BW, 0xD400007F) { Species = 587, Level = 30, Ability = OnlyFirst, TID16 = 11195, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(20,20,31,20,20,20), Nature = Nature.Lax }, // Emolga - new(BW, 0x2A000000) { Species = 479, Level = 60, Ability = OnlyFirst, TID16 = 54673, SID16 = 00000, OTGender = 1, Gender = 2, IVs = new(20,20,20,20,20,31), Nature = Nature.Gentle }, // Rotom - new(BW, 0x6200001F) { Species = 446, Level = 60, Ability = OnlySecond, TID16 = 40217, SID16 = 00000, OTGender = 0, Gender = 0, IVs = new(31,20,20,20,20,20), Nature = Nature.Serious }, // Munchlax + new(TradeNames, 04, BW, 0xD400007F) { Species = 587, Level = 30, Ability = OnlyFirst, ID32 = 11195, OTGender = 0, Gender = 0, IVs = new(20,20,31,20,20,20), Nature = Nature.Lax }, // Emolga + new(TradeNames, 05, BW, 0x2A000000) { Species = 479, Level = 60, Ability = OnlyFirst, ID32 = 54673, OTGender = 1, Gender = 2, IVs = new(20,20,20,20,20,31), Nature = Nature.Gentle }, // Rotom + new(TradeNames, 06, BW, 0x6200001F) { Species = 446, Level = 60, Ability = OnlySecond, ID32 = 40217, OTGender = 0, Gender = 0, IVs = new(31,20,20,20,20,20), Nature = Nature.Serious }, // Munchlax }; - private const string tradeBW = "tradebw"; - private static readonly string[][] TradeBW = Util.GetLanguageStrings8(tradeBW); - #endregion + internal static readonly EncounterTrade5BW[] TradeGift_B = + { + new(TradeNames, 00, B , 0x64000000) { Species = 548, Level = 15, Ability = OnlyFirst, ID32 = 39922, OTGender = 1, Gender = 1, IVs = new(20,20,20,20,31,20), Nature = Nature.Modest }, // Petilil + new(TradeNames, 02, B , 0x9400007F) { Species = 550, Level = 25, Ability = OnlyFirst, ID32 = 27646, OTGender = 0, Gender = 0, IVs = new(20,31,20,20,20,20), Nature = Nature.Adamant, Form = 0 }, // Basculin-Red + }; - internal static readonly EncounterStatic5[] StaticB = ArrayUtil.ConcatAll(GetEncounters(Encounter_BW, B), DreamWorld_Common, DreamWorld_BW); - internal static readonly EncounterStatic5[] StaticW = ArrayUtil.ConcatAll(GetEncounters(Encounter_BW, W), DreamWorld_Common, DreamWorld_BW); + internal static readonly EncounterTrade5BW[] TradeGift_W = + { + new(TradeNames, 01, W, 0x6400007E) { Species = 546, Level = 15, Ability = OnlyFirst, ID32 = 39922, OTGender = 1, Gender = 1, IVs = new(20,20,20,20,31,20), Nature = Nature.Modest }, // Cottonee + new(TradeNames, 03, W, 0x9400007F) { Species = 550, Level = 25, Ability = OnlyFirst, ID32 = 27646, OTGender = 0, Gender = 0, IVs = new(20,31,20,20,20,20), Nature = Nature.Adamant, Form = 1 }, // Basculin-Blue + }; + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5DR.cs b/PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5DR.cs similarity index 97% rename from PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5DR.cs rename to PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5DR.cs index 3ac6382a1..762b17342 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen45/Encounters5DR.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen5/Encounters5DR.cs @@ -10,7 +10,7 @@ public static class Encounters5DR { #region Dream Radar Tables - internal static readonly EncounterStatic5DR[] Encounter_DreamRadar = + internal static readonly EncounterStatic5Radar[] Encounter_DreamRadar = { new(079, 0), // Slowpoke new(120, 0), // Staryu @@ -43,7 +43,7 @@ public static class Encounters5DR #endregion #region DreamWorld Encounter - public static readonly EncounterStatic5[] DreamWorld_Common = DreamWorldEntry.GetArray(Gen5, stackalloc DreamWorldEntry[] + public static readonly EncounterStatic5Entree[] DreamWorld_Common = DreamWorldEntry.GetArray(Gen5, stackalloc DreamWorldEntry[] { // Pleasant Forest new(019, 10, 098, 382, 231), // Rattata diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters6AO.cs b/PKHeX.Core/Legality/Encounters/Data/Gen6/Encounters6AO.cs similarity index 62% rename from PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters6AO.cs rename to PKHeX.Core/Legality/Encounters/Data/Gen6/Encounters6AO.cs index 9b75c92bf..b237dc269 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters6AO.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen6/Encounters6AO.cs @@ -12,13 +12,8 @@ internal static class Encounters6AO internal static readonly EncounterArea6AO[] SlotsA = EncounterArea6AO.GetAreas(Get("as", "ao"), AS); internal static readonly EncounterArea6AO[] SlotsO = EncounterArea6AO.GetAreas(Get("or", "ao"), OR); - static Encounters6AO() - { - MarkEncounterTradeStrings(TradeGift_AO, TradeAO); - } - private const string tradeAO = "tradeao"; - private static readonly string[][] TradeAO = Util.GetLanguageStrings8(tradeAO); + private static readonly string[][] TradeNames = Util.GetLanguageStrings8(tradeAO); #region Static Encounter/Gift Tables private static readonly EncounterStatic6 BaseCosplay = new(ORAS) @@ -35,73 +30,61 @@ internal static class Encounters6AO CNT_Cute = 70, CNT_Tough = 70, CNT_Smart = 70, - Gift = true, + FixedBall = Ball.Poke, Shiny = Shiny.Never, }; private static readonly EncounterStatic6[] Encounter_AO_Regular = { // Starters @ Route 101 - new(ORAS) { Gift = true, Species = 252, Level = 5, Location = 204 }, // Treeko - new(ORAS) { Gift = true, Species = 255, Level = 5, Location = 204 }, // Torchic - new(ORAS) { Gift = true, Species = 258, Level = 5, Location = 204 }, // Mudkip + new(ORAS) { FixedBall = Ball.Poke, Species = 252, Level = 5, Location = 204 }, // Treeko + new(ORAS) { FixedBall = Ball.Poke, Species = 255, Level = 5, Location = 204 }, // Torchic + new(ORAS) { FixedBall = Ball.Poke, Species = 258, Level = 5, Location = 204 }, // Mudkip - new(ORAS) { Gift = true, Species = 152, Level = 5, Location = 204 }, // Chikorita - new(ORAS) { Gift = true, Species = 155, Level = 5, Location = 204 }, // Cyndaquil - new(ORAS) { Gift = true, Species = 158, Level = 5, Location = 204 }, // Totodile + new(ORAS) { FixedBall = Ball.Poke, Species = 152, Level = 5, Location = 204 }, // Chikorita + new(ORAS) { FixedBall = Ball.Poke, Species = 155, Level = 5, Location = 204 }, // Cyndaquil + new(ORAS) { FixedBall = Ball.Poke, Species = 158, Level = 5, Location = 204 }, // Totodile - new(ORAS) { Gift = true, Species = 387, Level = 5, Location = 204 }, // Turtwig - new(ORAS) { Gift = true, Species = 390, Level = 5, Location = 204 }, // Chimchar - new(ORAS) { Gift = true, Species = 393, Level = 5, Location = 204 }, // Piplup + new(ORAS) { FixedBall = Ball.Poke, Species = 387, Level = 5, Location = 204 }, // Turtwig + new(ORAS) { FixedBall = Ball.Poke, Species = 390, Level = 5, Location = 204 }, // Chimchar + new(ORAS) { FixedBall = Ball.Poke, Species = 393, Level = 5, Location = 204 }, // Piplup - new(ORAS) { Gift = true, Species = 495, Level = 5, Location = 204 }, // Snivy - new(ORAS) { Gift = true, Species = 498, Level = 5, Location = 204 }, // Tepig - new(ORAS) { Gift = true, Species = 501, Level = 5, Location = 204 }, // Oshawott + new(ORAS) { FixedBall = Ball.Poke, Species = 495, Level = 5, Location = 204 }, // Snivy + new(ORAS) { FixedBall = Ball.Poke, Species = 498, Level = 5, Location = 204 }, // Tepig + new(ORAS) { FixedBall = Ball.Poke, Species = 501, Level = 5, Location = 204 }, // Oshawott // Fossils @ Rustboro City - new(ORAS) { Gift = true, Species = 138, Level = 20, Location = 190 }, // Omanyte - new(ORAS) { Gift = true, Species = 140, Level = 20, Location = 190 }, // Kabuto - new(ORAS) { Gift = true, Species = 142, Level = 20, Location = 190 }, // Aerodactyl - new(ORAS) { Gift = true, Species = 345, Level = 20, Location = 190 }, // Lileep - new(ORAS) { Gift = true, Species = 347, Level = 20, Location = 190 }, // Anorith - new(ORAS) { Gift = true, Species = 408, Level = 20, Location = 190 }, // Cranidos - new(ORAS) { Gift = true, Species = 410, Level = 20, Location = 190 }, // Shieldon - new(ORAS) { Gift = true, Species = 564, Level = 20, Location = 190 }, // Tirtouga - new(ORAS) { Gift = true, Species = 566, Level = 20, Location = 190 }, // Archen - new(ORAS) { Gift = true, Species = 696, Level = 20, Location = 190 }, // Tyrunt - new(ORAS) { Gift = true, Species = 698, Level = 20, Location = 190 }, // Amaura + new(ORAS) { FixedBall = Ball.Poke, Species = 138, Level = 20, Location = 190 }, // Omanyte + new(ORAS) { FixedBall = Ball.Poke, Species = 140, Level = 20, Location = 190 }, // Kabuto + new(ORAS) { FixedBall = Ball.Poke, Species = 142, Level = 20, Location = 190 }, // Aerodactyl + new(ORAS) { FixedBall = Ball.Poke, Species = 345, Level = 20, Location = 190 }, // Lileep + new(ORAS) { FixedBall = Ball.Poke, Species = 347, Level = 20, Location = 190 }, // Anorith + new(ORAS) { FixedBall = Ball.Poke, Species = 408, Level = 20, Location = 190 }, // Cranidos + new(ORAS) { FixedBall = Ball.Poke, Species = 410, Level = 20, Location = 190 }, // Shieldon + new(ORAS) { FixedBall = Ball.Poke, Species = 564, Level = 20, Location = 190 }, // Tirtouga + new(ORAS) { FixedBall = Ball.Poke, Species = 566, Level = 20, Location = 190 }, // Archen + new(ORAS) { FixedBall = Ball.Poke, Species = 696, Level = 20, Location = 190 }, // Tyrunt + new(ORAS) { FixedBall = Ball.Poke, Species = 698, Level = 20, Location = 190 }, // Amaura // Hot Springs Eggs - new(ORAS) { Gift = true, Species = 360, Level = 1, EggLocation = 60004, Ability = OnlyFirst, EggCycles = 70 }, // Wynaut - new(ORAS) { Gift = true, Species = 175, Level = 1, EggLocation = 60004, Ability = OnlyFirst, EggCycles = 70 }, // Togepi + new(ORAS) { FixedBall = Ball.Poke, Species = 360, Level = 1, Location = 0, EggLocation = 60004, Ability = OnlyFirst, EggCycles = 70 }, // Wynaut + new(ORAS) { FixedBall = Ball.Poke, Species = 175, Level = 1, Location = 0, EggLocation = 60004, Ability = OnlyFirst, EggCycles = 70 }, // Togepi // Gift - new(ORAS) { Species = 374, Level = 01, Location = 196, Ability = OnlyFirst, Gift = true, IVs = new(-1,-1,31,-1,-1,31) }, // Beldum - new(ORAS) { Species = 351, Level = 30, Location = 240, Ability = OnlyFirst, Gift = true, IVs = new(-1,-1,-1,-1,31,-1), CNT_Beauty = 100, Gender = 1, Nature = Nature.Lax }, // Castform - new(ORAS) { Species = 319, Level = 40, Location = 318, Ability = OnlyFirst, Gift = true, Gender = 1, Nature = Nature.Adamant }, // Sharpedo - new(ORAS) { Species = 323, Level = 40, Location = 318, Ability = OnlyFirst, Gift = true, Gender = 1, Nature = Nature.Quiet }, // Camerupt - new( AS) { Species = 380, Level = 30, Location = 320, Ability = OnlyFirst, Gift = true, FlawlessIVCount = 3 }, // Latias - new(OR ) { Species = 381, Level = 30, Location = 320, Ability = OnlyFirst, Gift = true, FlawlessIVCount = 3 }, // Latios + new(ORAS) { Species = 374, Level = 01, Location = 196, Ability = OnlyFirst, FixedBall = Ball.Poke, IVs = new(-1,-1,31,-1,-1,31) }, // Beldum + new(ORAS) { Species = 351, Level = 30, Location = 240, Ability = OnlyFirst, FixedBall = Ball.Poke, IVs = new(-1,-1,-1,-1,31,-1), CNT_Beauty = 100, Gender = 1, Nature = Nature.Lax }, // Castform + new(ORAS) { Species = 319, Level = 40, Location = 318, Ability = OnlyFirst, FixedBall = Ball.Poke, Gender = 1, Nature = Nature.Adamant }, // Sharpedo + new(ORAS) { Species = 323, Level = 40, Location = 318, Ability = OnlyFirst, FixedBall = Ball.Poke, Gender = 1, Nature = Nature.Quiet }, // Camerupt // Stationary Legendary new(ORAS) { Species = 377, Level = 40, Location = 278, FlawlessIVCount = 3 }, // Regirock new(ORAS) { Species = 378, Level = 40, Location = 306, FlawlessIVCount = 3 }, // Regice new(ORAS) { Species = 379, Level = 40, Location = 308, FlawlessIVCount = 3 }, // Registeel new(ORAS) { Species = 486, Level = 50, Location = 306, FlawlessIVCount = 3 }, // Regigigas - new( AS) { Species = 382, Level = 45, Location = 296, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Kyogre - new(OR ) { Species = 383, Level = 45, Location = 296, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Groudon new(ORAS) { Species = 384, Level = 70, Location = 316, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Rayquaza new(ORAS) { Species = 386, Level = 80, Location = 316, Shiny = Shiny.Never, FlawlessIVCount = 3, FatefulEncounter = true }, // Deoxys // Hoopa Rings - new( AS) { Species = 249, Level = 50, Location = 304, FlawlessIVCount = 3 }, // Lugia - new(OR ) { Species = 250, Level = 50, Location = 304, FlawlessIVCount = 3 }, // Ho-Oh - new( AS) { Species = 483, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Dialga - new(OR ) { Species = 484, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Palkia - new( AS) { Species = 644, Level = 50, Location = 340, FlawlessIVCount = 3 }, // Zekrom - new(OR ) { Species = 643, Level = 50, Location = 340, FlawlessIVCount = 3 }, // Reshiram - new( AS) { Species = 642, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Thundurus - new(OR ) { Species = 641, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Tornadus new(ORAS) { Species = 243, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Raikou new(ORAS) { Species = 244, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Entei new(ORAS) { Species = 245, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Suicune @@ -123,13 +106,6 @@ internal static class Encounters6AO new(ORAS) { Species = 352, Level = 40, Location = 176, Gender = 1 }, // Kecleon @ Lavaridge new(ORAS) { Species = 352, Level = 45, Location = 196, Ability = OnlyHidden }, // Kecleon @ Mossdeep City - // Eon Ticket Lati@s - new( AS) { Species = 381, Level = 30, Location = 320, FlawlessIVCount = 3 }, // Latios - new(OR ) { Species = 380, Level = 30, Location = 320, FlawlessIVCount = 3 }, // Latias - - // Stationary - new( AS) { Species = 101, Level = 40, Location = 292 }, // Electrode - new(OR ) { Species = 101, Level = 40, Location = 314 }, // Electrode new(ORAS) { Species = 100, Level = 20, Location = 302 }, // Voltorb @ Route 119 new(ORAS) { Species = 442, Level = 50, Location = 304 }, // Spiritomb @ Route 120 @@ -150,18 +126,39 @@ internal static class Encounters6AO BaseCosplay, // Cosplay, same 3 level up moves. }; - private static readonly EncounterStatic6[] Encounter_AO = Encounter_AO_Regular; + internal static readonly EncounterStatic6[] StaticA = + { + new( AS) { Species = 380, Level = 30, Location = 320, Ability = OnlyFirst, FixedBall = Ball.Poke, FlawlessIVCount = 3 }, // Latias + new( AS) { Species = 382, Level = 45, Location = 296, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Kyogre + new( AS) { Species = 249, Level = 50, Location = 304, FlawlessIVCount = 3 }, // Lugia + new( AS) { Species = 483, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Dialga + new( AS) { Species = 644, Level = 50, Location = 340, FlawlessIVCount = 3 }, // Zekrom + new( AS) { Species = 642, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Thundurus + new( AS) { Species = 381, Level = 30, Location = 320, FlawlessIVCount = 3 }, // Latios + new( AS) { Species = 101, Level = 40, Location = 292 }, // Electrode + }; + + internal static readonly EncounterStatic6[] StaticO = + { + new(OR ) { Species = 381, Level = 30, Location = 320, Ability = OnlyFirst, FixedBall = Ball.Poke, FlawlessIVCount = 3 }, // Latios + new(OR ) { Species = 383, Level = 45, Location = 296, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Groudon + new(OR ) { Species = 250, Level = 50, Location = 304, FlawlessIVCount = 3 }, // Ho-Oh + new(OR ) { Species = 484, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Palkia + new(OR ) { Species = 643, Level = 50, Location = 340, FlawlessIVCount = 3 }, // Reshiram + new(OR ) { Species = 641, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Tornadus + new(OR ) { Species = 380, Level = 30, Location = 320, FlawlessIVCount = 3 }, // Latias + new(OR ) { Species = 101, Level = 40, Location = 314 }, // Electrode + }; + + internal static readonly EncounterStatic6[] Encounter_AO = Encounter_AO_Regular; #endregion #region Trade Tables internal static readonly EncounterTrade6[] TradeGift_AO = { - new(ORAS, 01,3,05,040) { Species = 296, Level = 09, Ability = OnlySecond, TID16 = 30724, IVs = new(-1,31,-1,-1,-1,-1), Gender = 0, Nature = Nature.Brave }, // Makuhita - new(ORAS, 34,3,13,176) { Species = 300, Level = 30, Ability = OnlyFirst, TID16 = 03239, IVs = new(-1,-1,-1,31,-1,-1), Gender = 1, Nature = Nature.Naughty }, // Skitty - new(ORAS, 07,4,10,319) { Species = 222, Level = 50, Ability = OnlyHidden, TID16 = 00325, IVs = new(31,-1,-1,-1,-1,31), Gender = 1, Nature = Nature.Calm }, // Corsola + new(TradeNames, 00, ORAS, 01,3,05,040) { Species = 296, Level = 09, Ability = OnlySecond, ID32 = 30724, Gender = 0, OTGender = 0, IVs = new(-1,31,-1,-1,-1,-1), Nature = Nature.Brave }, // Makuhita + new(TradeNames, 01, ORAS, 34,3,13,176) { Species = 300, Level = 30, Ability = OnlyFirst, ID32 = 03239, Gender = 1, OTGender = 1, IVs = new(-1,-1,-1,31,-1,-1), Nature = Nature.Naughty }, // Skitty + new(TradeNames, 02, ORAS, 07,4,10,319) { Species = 222, Level = 50, Ability = OnlyHidden, ID32 = 00325, Gender = 1, OTGender = 1, IVs = new(31,-1,-1,-1,-1,31), Nature = Nature.Calm }, // Corsola }; #endregion - - internal static readonly EncounterStatic6[] StaticA = GetEncounters(Encounter_AO, AS); - internal static readonly EncounterStatic6[] StaticO = GetEncounters(Encounter_AO, OR); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen6/Encounters6XY.cs b/PKHeX.Core/Legality/Encounters/Data/Gen6/Encounters6XY.cs new file mode 100644 index 000000000..20ce6a1a8 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Data/Gen6/Encounters6XY.cs @@ -0,0 +1,106 @@ +using static PKHeX.Core.EncounterUtil; +using static PKHeX.Core.GameVersion; +using static PKHeX.Core.AbilityPermission; + +namespace PKHeX.Core; + +/// +/// Generation 6 Encounters +/// +internal static class Encounters6XY +{ + private static readonly EncounterArea6XY FriendSafari = new(); + internal static readonly EncounterArea6XY[] SlotsX = EncounterArea6XY.GetAreas(Get("x", "xy"), X, FriendSafari); + internal static readonly EncounterArea6XY[] SlotsY = EncounterArea6XY.GetAreas(Get("y", "xy"), Y, FriendSafari); + + private const string tradeXY = "tradexy"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings8(tradeXY); + + #region Static Encounter/Gift Tables + internal static readonly EncounterStatic6[] Encounter_XY = + { + // Kalos Starters @ Aquacorde Town + new(XY) { FixedBall = Ball.Poke, Species = 650, Level = 5, Location = 10 }, // Chespin + new(XY) { FixedBall = Ball.Poke, Species = 653, Level = 5, Location = 10 }, // Fennekin + new(XY) { FixedBall = Ball.Poke, Species = 656, Level = 5, Location = 10 }, // Froakie + + // Kanto Starters @ Lumiose City + new(XY) { FixedBall = Ball.Poke, Species = 001, Level = 10, Location = 22 }, // Bulbasaur + new(XY) { FixedBall = Ball.Poke, Species = 004, Level = 10, Location = 22 }, // Charmander + new(XY) { FixedBall = Ball.Poke, Species = 007, Level = 10, Location = 22 }, // Squirtle + + // Fossils @ Ambrette Town + new(XY) { FixedBall = Ball.Poke, Species = 138, Level = 20, Location = 44 }, // Omanyte + new(XY) { FixedBall = Ball.Poke, Species = 140, Level = 20, Location = 44 }, // Kabuto + new(XY) { FixedBall = Ball.Poke, Species = 142, Level = 20, Location = 44 }, // Aerodactyl + new(XY) { FixedBall = Ball.Poke, Species = 345, Level = 20, Location = 44 }, // Lileep + new(XY) { FixedBall = Ball.Poke, Species = 347, Level = 20, Location = 44 }, // Anorith + new(XY) { FixedBall = Ball.Poke, Species = 408, Level = 20, Location = 44 }, // Cranidos + new(XY) { FixedBall = Ball.Poke, Species = 410, Level = 20, Location = 44 }, // Shieldon + new(XY) { FixedBall = Ball.Poke, Species = 564, Level = 20, Location = 44 }, // Tirtouga + new(XY) { FixedBall = Ball.Poke, Species = 566, Level = 20, Location = 44 }, // Archen + new(XY) { FixedBall = Ball.Poke, Species = 696, Level = 20, Location = 44 }, // Tyrunt + new(XY) { FixedBall = Ball.Poke, Species = 698, Level = 20, Location = 44 }, // Amaura + + // Gift + new(XY) { FixedBall = Ball.Poke, Species = 448, Level = 32, Location = 60, Ability = OnlyFirst, IVs = new(06,25,16,31,25,19), Nature = Nature.Hasty, Gender = 0, Shiny = Shiny.Never }, // Lucario + new(XY) { FixedBall = Ball.Poke, Species = 131, Level = 30, Location = 62, Ability = OnlyFirst, IVs = new(31,20,20,20,20,20), Nature = Nature.Docile }, // Lapras + + // Stationary + new(XY) { Species = 143, Level = 15, Location = 038, Shiny = Shiny.Never }, // Snorlax + + // Shaking Trash Cans @ Lost Hotel + new(XY) { Species = 568, Level = 35, Location = 142 }, // Trubbish + new(XY) { Species = 569, Level = 36, Location = 142 }, // Garbodor + new(XY) { Species = 569, Level = 37, Location = 142 }, // Garbodor + new(XY) { Species = 569, Level = 38, Location = 142 }, // Garbodor + new(XY) { Species = 479, Level = 38, Location = 142 }, // Rotom + + // Shaking Trash Cans @ Pokemon Village + new(XY) { Species = 569, Level = 46, Location = 98 }, // Garbodor + new(XY) { Species = 569, Level = 47, Location = 98 }, // Garbodor + new(XY) { Species = 569, Level = 48, Location = 98 }, // Garbodor + new(XY) { Species = 569, Level = 49, Location = 98 }, // Garbodor + new(XY) { Species = 569, Level = 50, Location = 98 }, // Garbodor + new(XY) { Species = 354, Level = 46, Location = 98 }, // Banette + new(XY) { Species = 354, Level = 47, Location = 98 }, // Banette + new(XY) { Species = 354, Level = 48, Location = 98 }, // Banette + new(XY) { Species = 354, Level = 49, Location = 98 }, // Banette + new(XY) { Species = 354, Level = 50, Location = 98 }, // Banette + + // Stationary Legendary + new(XY) { Species = 718, Level = 70, Location = 140, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(XY) { Species = 150, Level = 70, Location = 168, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Mewtwo + new(XY) { Species = 144, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Articuno + new(XY) { Species = 145, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zapdos + new(XY) { Species = 146, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Moltres + }; + + internal static readonly EncounterStatic6[] StaticX = + { + new(X ) { Species = 716, Level = 50, Location = 138, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Xerneas + }; + + internal static readonly EncounterStatic6[] StaticY = + { + new( Y) { Species = 717, Level = 50, Location = 138, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Yveltal + }; + + #endregion + #region Trade Tables + internal static readonly EncounterTrade6[] TradeGift_XY = + { + new(TradeNames, 00, XY, 01,3,23,049) { Species = 129, Level = 05, Ability = OnlyFirst, ID32 = 44285, Gender = 0, OTGender = 0, IVs = new(-1,31,-1,-1,31,-1), Nature = Nature.Adamant }, // Magikarp + new(TradeNames, 01, XY, 10,3,00,000) { Species = 133, Level = 05, Ability = OnlyFirst, ID32 = 29294, Gender = 1, OTGender = 1, IVs = default, Nature = Nature.Docile }, // Eevee + + new(TradeNames, 02, XY, 15,4,13,017) { Species = 083, Level = 10, Ability = OnlyFirst, ID32 = 00185, Gender = 0, OTGender = 0, IVs = new(-1,-1,-1,31,-1,-1), Nature = Nature.Jolly }, // Farfetch'd + new(TradeNames, 03, XY, 17,5,08,025) { Species = 208, Level = 20, Ability = OnlyFirst, ID32 = 19250, Gender = 1, OTGender = 0, IVs = new(-1,-1,31,-1,-1,-1), Nature = Nature.Impish }, // Steelix + new(TradeNames, 04, XY, 18,7,20,709) { Species = 625, Level = 50, Ability = OnlyFirst, ID32 = 03447, Gender = 0, OTGender = 1, IVs = new(-1,31,-1,-1,-1,-1), Nature = Nature.Adamant }, // Bisharp + + new(TradeNames, 05, XY, 02,3,11,005) { Species = 656, Level = 05, Ability = OnlyFirst, ID32 = 00037, Gender = 0, OTGender = 1, IVs = new(20,20,20,31,20,20), Nature = Nature.Jolly }, // Froakie + new(TradeNames, 06, XY, 02,3,09,005) { Species = 650, Level = 05, Ability = OnlyFirst, ID32 = 00037, Gender = 0, OTGender = 1, IVs = new(20,31,20,20,20,20), Nature = Nature.Adamant }, // Chespin + new(TradeNames, 07, XY, 02,3,18,005) { Species = 653, Level = 05, Ability = OnlyFirst, ID32 = 00037, Gender = 0, OTGender = 1, IVs = new(20,20,20,20,31,20), Nature = Nature.Modest }, // Fennekin + new(TradeNames, 08, XY, 51,4,04,033) { Species = 280, Level = 05, Ability = OnlyFirst, ID32 = 37110, Gender = 1, OTGender = 1, IVs = new(20,20,20,31,31,20), Nature = Nature.Modest, IsFixedNickname = false }, // Ralts + }; + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters6XY.cs b/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters6XY.cs deleted file mode 100644 index 424130c63..000000000 --- a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters6XY.cs +++ /dev/null @@ -1,102 +0,0 @@ -using static PKHeX.Core.EncounterUtil; -using static PKHeX.Core.GameVersion; -using static PKHeX.Core.AbilityPermission; - -namespace PKHeX.Core; - -/// -/// Generation 6 Encounters -/// -internal static class Encounters6XY -{ - private static readonly EncounterArea6XY FriendSafari = new(); - internal static readonly EncounterArea6XY[] SlotsX = EncounterArea6XY.GetAreas(Get("x", "xy"), X, FriendSafari); - internal static readonly EncounterArea6XY[] SlotsY = EncounterArea6XY.GetAreas(Get("y", "xy"), Y, FriendSafari); - - static Encounters6XY() => MarkEncounterTradeStrings(TradeGift_XY, TradeXY); - - private const string tradeXY = "tradexy"; - private static readonly string[][] TradeXY = Util.GetLanguageStrings8(tradeXY); - - #region Static Encounter/Gift Tables - private static readonly EncounterStatic6[] Encounter_XY = - { - // Kalos Starters @ Aquacorde Town - new(XY) { Gift = true, Species = 650, Level = 5, Location = 10 }, // Chespin - new(XY) { Gift = true, Species = 653, Level = 5, Location = 10 }, // Fennekin - new(XY) { Gift = true, Species = 656, Level = 5, Location = 10 }, // Froakie - - // Kanto Starters @ Lumiose City - new(XY) { Gift = true, Species = 1, Level = 10, Location = 22 }, // Bulbasaur - new(XY) { Gift = true, Species = 4, Level = 10, Location = 22 }, // Charmander - new(XY) { Gift = true, Species = 7, Level = 10, Location = 22 }, // Squirtle - - // Fossils @ Ambrette Town - new(XY) { Gift = true, Species = 138, Level = 20, Location = 44 }, // Omanyte - new(XY) { Gift = true, Species = 140, Level = 20, Location = 44 }, // Kabuto - new(XY) { Gift = true, Species = 142, Level = 20, Location = 44 }, // Aerodactyl - new(XY) { Gift = true, Species = 345, Level = 20, Location = 44 }, // Lileep - new(XY) { Gift = true, Species = 347, Level = 20, Location = 44 }, // Anorith - new(XY) { Gift = true, Species = 408, Level = 20, Location = 44 }, // Cranidos - new(XY) { Gift = true, Species = 410, Level = 20, Location = 44 }, // Shieldon - new(XY) { Gift = true, Species = 564, Level = 20, Location = 44 }, // Tirtouga - new(XY) { Gift = true, Species = 566, Level = 20, Location = 44 }, // Archen - new(XY) { Gift = true, Species = 696, Level = 20, Location = 44 }, // Tyrunt - new(XY) { Gift = true, Species = 698, Level = 20, Location = 44 }, // Amaura - - // Gift - new(XY) { Gift = true, Species = 448, Level = 32, Location = 60, Ability = OnlyFirst, IVs = new(06,25,16,31,25,19), Nature = Nature.Hasty, Gender = 0, Shiny = Shiny.Never }, // Lucario - new(XY) { Gift = true, Species = 131, Level = 30, Location = 62, Ability = OnlyFirst, IVs = new(31,20,20,20,20,20), Nature = Nature.Docile }, // Lapras - - // Stationary - new(XY) { Species = 143, Level = 15, Location = 038, Shiny = Shiny.Never }, // Snorlax - - // Shaking Trash Cans @ Lost Hotel - new(XY) { Species = 568, Level = 35, Location = 142 }, // Trubbish - new(XY) { Species = 569, Level = 36, Location = 142 }, // Garbodor - new(XY) { Species = 569, Level = 37, Location = 142 }, // Garbodor - new(XY) { Species = 569, Level = 38, Location = 142 }, // Garbodor - new(XY) { Species = 479, Level = 38, Location = 142 }, // Rotom - - // Shaking Trash Cans @ Pokemon Village - new(XY) { Species = 569, Level = 46, Location = 98 }, // Garbodor - new(XY) { Species = 569, Level = 47, Location = 98 }, // Garbodor - new(XY) { Species = 569, Level = 48, Location = 98 }, // Garbodor - new(XY) { Species = 569, Level = 49, Location = 98 }, // Garbodor - new(XY) { Species = 569, Level = 50, Location = 98 }, // Garbodor - new(XY) { Species = 354, Level = 46, Location = 98 }, // Banette - new(XY) { Species = 354, Level = 47, Location = 98 }, // Banette - new(XY) { Species = 354, Level = 48, Location = 98 }, // Banette - new(XY) { Species = 354, Level = 49, Location = 98 }, // Banette - new(XY) { Species = 354, Level = 50, Location = 98 }, // Banette - - // Stationary Legendary - new(X ) { Species = 716, Level = 50, Location = 138, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Xerneas - new( Y) { Species = 717, Level = 50, Location = 138, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Yveltal - new(XY) { Species = 718, Level = 70, Location = 140, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(XY) { Species = 150, Level = 70, Location = 168, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Mewtwo - new(XY) { Species = 144, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Articuno - new(XY) { Species = 145, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zapdos - new(XY) { Species = 146, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Moltres - }; - #endregion - #region Trade Tables - internal static readonly EncounterTrade6[] TradeGift_XY = - { - new(XY, 01,3,23,049) { Species = 129, Level = 05, Ability = OnlyFirst, TID16 = 44285, IVs = new(-1,31,-1,-1,31,-1), Gender = 0, Nature = Nature.Adamant }, // Magikarp - new(XY, 10,3,00,000) { Species = 133, Level = 05, Ability = OnlyFirst, TID16 = 29294, Gender = 1, Nature = Nature.Docile }, // Eevee - - new(XY, 15,4,13,017) { Species = 083, Level = 10, Ability = OnlyFirst, TID16 = 00185, IVs = new(-1,-1,-1,31,-1,-1), Gender = 0, Nature = Nature.Jolly }, // Farfetch'd - new(XY, 17,5,08,025) { Species = 208, Level = 20, Ability = OnlyFirst, TID16 = 19250, IVs = new(-1,-1,31,-1,-1,-1), Gender = 1, Nature = Nature.Impish }, // Steelix - new(XY, 18,7,20,709) { Species = 625, Level = 50, Ability = OnlyFirst, TID16 = 03447, IVs = new(-1,31,-1,-1,-1,-1), Gender = 0, Nature = Nature.Adamant }, // Bisharp - - new(XY, 02,3,11,005) { Species = 656, Level = 05, Ability = OnlyFirst, TID16 = 00037, IVs = new(20,20,20,31,20,20), Gender = 0, Nature = Nature.Jolly }, // Froakie - new(XY, 02,3,09,005) { Species = 650, Level = 05, Ability = OnlyFirst, TID16 = 00037, IVs = new(20,31,20,20,20,20), Gender = 0, Nature = Nature.Adamant }, // Chespin - new(XY, 02,3,18,005) { Species = 653, Level = 05, Ability = OnlyFirst, TID16 = 00037, IVs = new(20,20,20,20,31,20), Gender = 0, Nature = Nature.Modest }, // Fennekin - new(XY, 51,4,04,033) { Species = 280, Level = 05, Ability = OnlyFirst, TID16 = 37110, IVs = new(20,20,20,31,31,20), Gender = 1, Nature = Nature.Modest, IsNicknamed = false }, // Ralts - }; - #endregion - - internal static readonly EncounterStatic6[] StaticX = GetEncounters(Encounter_XY, X); - internal static readonly EncounterStatic6[] StaticY = GetEncounters(Encounter_XY, Y); -} diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7GG.cs b/PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7GG.cs similarity index 55% rename from PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7GG.cs rename to PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7GG.cs index 5ed09ec77..84479d057 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7GG.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7GG.cs @@ -8,7 +8,7 @@ internal static class Encounters7GG internal static readonly EncounterArea7b[] SlotsGP = EncounterArea7b.GetAreas(Get("gp", "gg"), GP); internal static readonly EncounterArea7b[] SlotsGE = EncounterArea7b.GetAreas(Get("ge", "gg"), GE); - private static readonly EncounterStatic7b[] Encounter_GG = + internal static readonly EncounterStatic7b[] Encounter_GG = { // encounters new(GG) { Species = 144, Level = 50, Location = 44, FlawlessIVCount = 3 }, // Articuno @ Seafoam Islands @@ -21,24 +21,31 @@ internal static class Encounters7GG // collision new EncounterStatic7b { Species = 101, Level = 42, Location = 42, FlawlessIVCount = 3 }, // Electrode @ Power Plant // gifts - new(GP) { Species = 025, Level = 05, Location = 28, Gift = true, IVs = new(31,31,31,31,31,31), Shiny = Shiny.Never, Form = 8 }, // Pikachu @ Pallet Town - new(GE) { Species = 133, Level = 05, Location = 28, Gift = true, IVs = new(31,31,31,31,31,31), Shiny = Shiny.Never, Form = 1 }, // Eevee @ Pallet Town - - new(GG) { Species = 129, Level = 05, Location = 06, Gift = true, IVs = new(30,31,25,30,25,25) }, // Magikarp @ Route 4 + new(GG) { Species = 129, Level = 05, Location = 06, FixedBall = Ball.Poke, IVs = new(30,31,25,30,25,25) }, // Magikarp @ Route 4 // unused new EncounterStatic7b { Species = 133, Level = 30, Location = 34, Gift = true }, // Eevee @ Celadon City - new(GG) { Species = 131, Level = 34, Location = 52, Gift = true, IVs = new(31,25,25,25,30,30) }, // Lapras @ Saffron City (Silph Co. Employee, inside) - new(GG) { Species = 106, Level = 30, Location = 38, Gift = true, IVs = new(25,30,25,31,25,30) }, // Hitmonlee @ Saffron City (Karate Master) - new(GG) { Species = 107, Level = 30, Location = 38, Gift = true, IVs = new(25,31,30,25,25,30) }, // Hitmonchan @ Saffron City (Karate Master) - new(GG) { Species = 140, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Kabuto @ Cinnabar Island (Cinnabar Pokémon Lab) - new(GG) { Species = 138, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Omanyte @ Cinnabar Island (Cinnabar Pokémon Lab) - new(GG) { Species = 142, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Aerodactyl @ Cinnabar Island (Cinnabar Pokémon Lab) - new(GG) { Species = 001, Level = 12, Location = 31, Gift = true, IVs = new(31,25,30,25,25,30) }, // Bulbasaur @ Cerulean City - new(GG) { Species = 004, Level = 14, Location = 26, Gift = true, IVs = new(25,30,25,31,30,25) }, // Charmander @ Route 24 - new(GG) { Species = 007, Level = 16, Location = 33, Gift = true, IVs = new(25,25,30,25,31,30) }, // Squirtle @ Vermillion City - new(GG) { Species = 137, Level = 34, Location = 38, Gift = true, IVs = new(25,25,30,25,31,30) }, // Porygon @ Saffron City (Silph Co. Employee, outside) - new(GP) { Species = 053, Level = 16, Location = 33, Gift = true, IVs = new(30,30,25,31,25,25) }, // Persian @ Vermillion City (Outside Fan Club) - new(GE) { Species = 059, Level = 16, Location = 33, Gift = true, IVs = new(25,30,25,31,30,25) }, // Arcanine @ Vermillion City (Outside Fan Club) + new(GG) { Species = 131, Level = 34, Location = 52, FixedBall = Ball.Poke, IVs = new(31,25,25,25,30,30) }, // Lapras @ Saffron City (Silph Co. Employee, inside) + new(GG) { Species = 106, Level = 30, Location = 38, FixedBall = Ball.Poke, IVs = new(25,30,25,31,25,30) }, // Hitmonlee @ Saffron City (Karate Master) + new(GG) { Species = 107, Level = 30, Location = 38, FixedBall = Ball.Poke, IVs = new(25,31,30,25,25,30) }, // Hitmonchan @ Saffron City (Karate Master) + new(GG) { Species = 140, Level = 44, Location = 36, FixedBall = Ball.Poke, FlawlessIVCount = 3 }, // Kabuto @ Cinnabar Island (Cinnabar Pokémon Lab) + new(GG) { Species = 138, Level = 44, Location = 36, FixedBall = Ball.Poke, FlawlessIVCount = 3 }, // Omanyte @ Cinnabar Island (Cinnabar Pokémon Lab) + new(GG) { Species = 142, Level = 44, Location = 36, FixedBall = Ball.Poke, FlawlessIVCount = 3 }, // Aerodactyl @ Cinnabar Island (Cinnabar Pokémon Lab) + new(GG) { Species = 001, Level = 12, Location = 31, FixedBall = Ball.Poke, IVs = new(31,25,30,25,25,30) }, // Bulbasaur @ Cerulean City + new(GG) { Species = 004, Level = 14, Location = 26, FixedBall = Ball.Poke, IVs = new(25,30,25,31,30,25) }, // Charmander @ Route 24 + new(GG) { Species = 007, Level = 16, Location = 33, FixedBall = Ball.Poke, IVs = new(25,25,30,25,31,30) }, // Squirtle @ Vermillion City + new(GG) { Species = 137, Level = 34, Location = 38, FixedBall = Ball.Poke, IVs = new(25,25,30,25,31,30) }, // Porygon @ Saffron City (Silph Co. Employee, outside) + }; + + internal static readonly EncounterStatic7b[] StaticGP = + { + new(GP) { Species = 025, Level = 05, Location = 28, FixedBall = Ball.Poke, IVs = new(31,31,31,31,31,31), Shiny = Shiny.Never, Form = 8 }, // Pikachu @ Pallet Town + new(GP) { Species = 053, Level = 16, Location = 33, FixedBall = Ball.Poke, IVs = new(30,30,25,31,25,25) }, // Persian @ Vermillion City (Outside Fan Club) + }; + + internal static readonly EncounterStatic7b[] StaticGE = + { + new(GE) { Species = 133, Level = 05, Location = 28, FixedBall = Ball.Poke, IVs = new(31,31,31,31,31,31), Shiny = Shiny.Never, Form = 1 }, // Eevee @ Pallet Town + new(GE) { Species = 059, Level = 16, Location = 33, FixedBall = Ball.Poke, IVs = new(25,30,25,31,30,25) }, // Arcanine @ Vermillion City (Outside Fan Club) }; private static readonly string[] T1 = { string.Empty, "ミニコ", "Tatianna", "BarbaRatatta", "Addoloratta", "Barbaratt", string.Empty, "Tatiana", "미니꼬", "小幂妮", "小幂妮" }; @@ -53,18 +60,23 @@ internal static class Encounters7GG internal static readonly EncounterTrade7b[] TradeGift_GG = { // Random candy values! They can be zero so no impact on legality even though statistically rare. - new(GG) { Species = 019, Form = 1, Level = 12, TrainerNames = T1, TID7 = 121106, OTGender = 1, IVs = new(31,31,-1,-1,-1,-1) }, // Rattata @ Cerulean City, AV rand [0-5) - new(GP) { Species = 027, Form = 1, Level = 27, TrainerNames = T2, TID7 = 703019, OTGender = 0, IVs = new(-1,31,31,-1,-1,-1) }, // Sandshrew @ Celadon City, AV rand [0-5) - new(GE) { Species = 037, Form = 1, Level = 27, TrainerNames = T2, TID7 = 703019, OTGender = 0, IVs = new(-1,-1,-1,31,31,-1) }, // Vulpix @ Celadon City, AV rand [0-5) - new(GG) { Species = 050, Form = 1, Level = 25, TrainerNames = T3, TID7 = 520159, OTGender = 1, IVs = new(-1,31,-1,31,-1,-1) }, // Diglett @ Lavender Town, AV rand [0-5) - new(GE) { Species = 052, Form = 1, Level = 44, TrainerNames = T4, TID7 = 000219, OTGender = 0, IVs = new(31,-1,-1,31,-1,-1) }, // Meowth @ Cinnabar Island, AV rand [0-10) - new(GP) { Species = 088, Form = 1, Level = 44, TrainerNames = T4, TID7 = 000219, OTGender = 0, IVs = new(31,31,-1,-1,-1,-1) }, // Grimer @ Cinnabar Island, AV rand [0-10) - new(GG) { Species = 026, Form = 1, Level = 30, TrainerNames = T5, TID7 = 940711, OTGender = 1, IVs = new(-1,-1,-1,31,31,-1) }, // Raichu @ Saffron City, AV rand [0-10) - new(GG) { Species = 105, Form = 1, Level = 38, TrainerNames = T6, TID7 = 102595, OTGender = 0, IVs = new(-1,31,31,-1,-1,-1) }, // Marowak @ Fuchsia City, AV rand [0-10) - new(GG) { Species = 103, Form = 1, Level = 46, TrainerNames = T7, TID7 = 060310, OTGender = 0, IVs = new(-1,31,-1,-1,31,-1) }, // Exeggutor @ Indigo Plateau, AV rand [0-15) - new(GG) { Species = 074, Form = 1, Level = 16, TrainerNames = T8, TID7 = 551873, OTGender = 0, IVs = new(31,31,-1,-1,-1,-1) }, // Geodude @ Vermilion City, AV rand [0-5) + new(GG) { Species = 019, Form = 1, Level = 12, TrainerNames = T1, ID32 = 121106, OTGender = 1, IVs = new(31,31,-1,-1,-1,-1) }, // Rattata @ Cerulean City, AV rand [0-5) + new(GG) { Species = 050, Form = 1, Level = 25, TrainerNames = T3, ID32 = 520159, OTGender = 1, IVs = new(-1,31,-1,31,-1,-1) }, // Diglett @ Lavender Town, AV rand [0-5) + new(GG) { Species = 026, Form = 1, Level = 30, TrainerNames = T5, ID32 = 940711, OTGender = 1, IVs = new(-1,-1,-1,31,31,-1) }, // Raichu @ Saffron City, AV rand [0-10) + new(GG) { Species = 105, Form = 1, Level = 38, TrainerNames = T6, ID32 = 102595, OTGender = 0, IVs = new(-1,31,31,-1,-1,-1) }, // Marowak @ Fuchsia City, AV rand [0-10) + new(GG) { Species = 103, Form = 1, Level = 46, TrainerNames = T7, ID32 = 060310, OTGender = 0, IVs = new(-1,31,-1,-1,31,-1) }, // Exeggutor @ Indigo Plateau, AV rand [0-15) + new(GG) { Species = 074, Form = 1, Level = 16, TrainerNames = T8, ID32 = 551873, OTGender = 0, IVs = new(31,31,-1,-1,-1,-1) }, // Geodude @ Vermilion City, AV rand [0-5) }; - internal static readonly EncounterStatic7b[] StaticGP = GetEncounters(Encounter_GG, GP); - internal static readonly EncounterStatic7b[] StaticGE = GetEncounters(Encounter_GG, GE); + internal static readonly EncounterTrade7b[] TradeGift_GP = + { + new(GP) { Species = 027, Form = 1, Level = 27, TrainerNames = T2, ID32 = 703019, OTGender = 0, IVs = new(-1,31,31,-1,-1,-1) }, // Sandshrew @ Celadon City, AV rand [0-5) + new(GP) { Species = 088, Form = 1, Level = 44, TrainerNames = T4, ID32 = 000219, OTGender = 0, IVs = new(31,31,-1,-1,-1,-1) }, // Grimer @ Cinnabar Island, AV rand [0-10) + }; + + internal static readonly EncounterTrade7b[] TradeGift_GE = + { + new(GE) { Species = 037, Form = 1, Level = 27, TrainerNames = T2, ID32 = 703019, OTGender = 0, IVs = new(-1,-1,-1,31,31,-1) }, // Vulpix @ Celadon City, AV rand [0-5) + new(GE) { Species = 052, Form = 1, Level = 44, TrainerNames = T4, ID32 = 000219, OTGender = 0, IVs = new(31,-1,-1,31,-1,-1) }, // Meowth @ Cinnabar Island, AV rand [0-10) + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7SM.cs b/PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7SM.cs similarity index 58% rename from PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7SM.cs rename to PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7SM.cs index 31b38ae7d..6d37bcc91 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7SM.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7SM.cs @@ -12,55 +12,53 @@ internal static class Encounters7SM internal static readonly EncounterArea7[] SlotsSN = EncounterArea7.GetAreas(Get("sn", "sm"), SN); internal static readonly EncounterArea7[] SlotsMN = EncounterArea7.GetAreas(Get("mn", "sm"), MN); - static Encounters7SM() => MarkEncounterTradeStrings(TradeGift_SM, TradeSM); + private const string tradeSM = "tradesm"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings10(tradeSM); - private static readonly EncounterStatic7[] Encounter_SM = // @ a\1\5\5 + public static readonly EncounterStatic7[] StaticSM = // @ a\1\5\5 { // Gifts - 0.bin - new(SM) { Gift = true, Species = 722, Level = 5, Location = 024 }, // Rowlet - new(SM) { Gift = true, Species = 725, Level = 5, Location = 024 }, // Litten - new(SM) { Gift = true, Species = 728, Level = 5, Location = 024 }, // Popplio - new(SM) { Gift = true, Species = 138, Level = 15, Location = 058 }, // Omanyte - new(SM) { Gift = true, Species = 140, Level = 15, Location = 058 }, // Kabuto - // new(SM) { Gift = true, Species = 142, Level = 15, Location = 058 }, // Aerodactyl - new(SM) { Gift = true, Species = 345, Level = 15, Location = 058 }, // Lileep - new(SM) { Gift = true, Species = 347, Level = 15, Location = 058 }, // Anorith - new(SM) { Gift = true, Species = 408, Level = 15, Location = 058 }, // Cranidos - new(SM) { Gift = true, Species = 410, Level = 15, Location = 058 }, // Shieldon - new(SM) { Gift = true, Species = 564, Level = 15, Location = 058 }, // Tirtouga - new(SM) { Gift = true, Species = 566, Level = 15, Location = 058 }, // Archen - new(SM) { Gift = true, Species = 696, Level = 15, Location = 058 }, // Tyrunt - new(SM) { Gift = true, Species = 698, Level = 15, Location = 058 }, // Amaura - new(SM) { Gift = true, Species = 137, Level = 30, Location = 116 }, // Porygon @ Route 15 - new(SM) { Gift = true, Species = 133, Level = 1, EggLocation = 60002 }, // Eevee @ Nursery helpers - new(SM) { Gift = true, Species = 772, Level = 40, Location = 188, FlawlessIVCount = 3 }, // Type: Null - new(SN) { Gift = true, Species = 789, Level = 5, Location = 142, Shiny = Shiny.Never, Ability = OnlySecond, FlawlessIVCount = 3 }, // Cosmog - new(MN) { Gift = true, Species = 789, Level = 5, Location = 144, Shiny = Shiny.Never, Ability = OnlySecond, FlawlessIVCount = 3 }, // Cosmog - new(SM) { Gift = true, Species = 142, Level = 40, Location = 172 }, // Aerodactyl @ Seafolk Village + new(SM) { FixedBall = Ball.Poke, Species = 722, Level = 5, Location = 024 }, // Rowlet + new(SM) { FixedBall = Ball.Poke, Species = 725, Level = 5, Location = 024 }, // Litten + new(SM) { FixedBall = Ball.Poke, Species = 728, Level = 5, Location = 024 }, // Popplio + new(SM) { FixedBall = Ball.Poke, Species = 138, Level = 15, Location = 058 }, // Omanyte + new(SM) { FixedBall = Ball.Poke, Species = 140, Level = 15, Location = 058 }, // Kabuto + // new(SM) { FixedBall = Ball.Poke, Species = 142, Level = 15, Location = 058 }, // Aerodactyl + new(SM) { FixedBall = Ball.Poke, Species = 345, Level = 15, Location = 058 }, // Lileep + new(SM) { FixedBall = Ball.Poke, Species = 347, Level = 15, Location = 058 }, // Anorith + new(SM) { FixedBall = Ball.Poke, Species = 408, Level = 15, Location = 058 }, // Cranidos + new(SM) { FixedBall = Ball.Poke, Species = 410, Level = 15, Location = 058 }, // Shieldon + new(SM) { FixedBall = Ball.Poke, Species = 564, Level = 15, Location = 058 }, // Tirtouga + new(SM) { FixedBall = Ball.Poke, Species = 566, Level = 15, Location = 058 }, // Archen + new(SM) { FixedBall = Ball.Poke, Species = 696, Level = 15, Location = 058 }, // Tyrunt + new(SM) { FixedBall = Ball.Poke, Species = 698, Level = 15, Location = 058 }, // Amaura + new(SM) { FixedBall = Ball.Poke, Species = 137, Level = 30, Location = 116 }, // Porygon @ Route 15 + new(SM) { FixedBall = Ball.Poke, Species = 133, Level = 1, EggLocation = 60002 }, // Eevee @ Nursery helpers + new(SM) { FixedBall = Ball.Poke, Species = 772, Level = 40, Location = 188, FlawlessIVCount = 3 }, // Type: Null + new(SN) { FixedBall = Ball.Poke, Species = 789, Level = 5, Location = 142, Shiny = Shiny.Never, Ability = OnlySecond, FlawlessIVCount = 3 }, // Cosmog + new(MN) { FixedBall = Ball.Poke, Species = 789, Level = 5, Location = 144, Shiny = Shiny.Never, Ability = OnlySecond, FlawlessIVCount = 3 }, // Cosmog + new(SM) { FixedBall = Ball.Poke, Species = 142, Level = 40, Location = 172 }, // Aerodactyl @ Seafolk Village - new(SM) { Gift = true, Species = 718, Form = 0, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 1, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 2, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 3, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { FixedBall = Ball.Poke, Species = 718, Form = 0, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { FixedBall = Ball.Poke, Species = 718, Form = 1, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { FixedBall = Ball.Poke, Species = 718, Form = 2, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { FixedBall = Ball.Poke, Species = 718, Form = 3, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 0, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 1, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 2, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 3, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { FixedBall = Ball.Poke, Species = 718, Form = 0, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { FixedBall = Ball.Poke, Species = 718, Form = 1, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { FixedBall = Ball.Poke, Species = 718, Form = 2, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { FixedBall = Ball.Poke, Species = 718, Form = 3, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde new(SM) // Magearna (Bottle Cap) 00 FF { - Gift = true, Species = 801, Level = 50, Location = 40001, Shiny = Shiny.Never, FlawlessIVCount = 3, HeldItem = 795, Ability = OnlySecond, - FatefulEncounter = true, Relearn = new(705, 430, 381, 270), Ball = 0x10, // Cherish + FixedBall = Ball.Cherish, Species = 801, Level = 50, Location = 40001, Shiny = Shiny.Never, FlawlessIVCount = 3, Ability = OnlySecond, + FatefulEncounter = true, Relearn = new(705, 430, 381, 270), // Cherish }, // Static Encounters - 1.bin new(SM) { Species = 746, Level = 17, Location = 086, Shiny = Shiny.Never, Ability = OnlyFirst }, // Wishiwashi new(SM) { Species = 746, Level = 18, Location = 086, Shiny = Shiny.Never, Ability = OnlyFirst }, // Wishiwashi - new(SN) { Species = 791, Level = 55, Location = 176, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(713, 322, 242, 428) }, // Solgaleo - new(MN) { Species = 792, Level = 55, Location = 178, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(714, 322, 539, 247) }, // Lunala - new(SM) { Species = 785, Level = 60, Location = 030, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Koko new(SM) { Species = 786, Level = 60, Location = 092, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Lele new(SM) { Species = 787, Level = 60, Location = 140, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Bulu @@ -68,14 +66,8 @@ internal static class Encounters7SM new(SM) { Species = 793, Level = 55, Location = 082, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Nihilego @ Wela Volcano Park new(SM) { Species = 793, Level = 55, Location = 100, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Nihilego @ Diglett’s Tunnel - new(SN) { Species = 794, Level = 65, Location = 040, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Buzzwole @ Melemele Meadow - new(MN) { Species = 795, Level = 60, Location = 046, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Pheromosa @ Verdant Cavern (Trial Site) new(SM) { Species = 796, Level = 65, Location = 090, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Lush Jungle new(SM) { Species = 796, Level = 65, Location = 076, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Memorial Hill - new(SN) { Species = 798, Level = 60, Location = 134, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Malie Garden - new(SN) { Species = 798, Level = 60, Location = 120, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Route 17 - new(MN) { Species = 797, Level = 65, Location = 124, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Haina Desert - new(MN) { Species = 797, Level = 65, Location = 134, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Malie Garden new(SM) { Species = 799, Level = 70, Location = 182, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Guzzlord @ Resolution Cave new(SM) { Species = 800, Level = 75, Location = 036, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Necrozma @ Ten Carat Hill (Farthest Hollow) @@ -117,21 +109,31 @@ internal static class Encounters7SM new(SM) { Species = 500, Level = 43, Location = 160, Relearn = new(276, 053, 372, 535) }, // Emboar @ Ancient Poni Path }; + public static readonly EncounterStatic7[] StaticSN = + { + new(SN) { Species = 791, Level = 55, Location = 176, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(713, 322, 242, 428) }, // Solgaleo + new(SN) { Species = 794, Level = 65, Location = 040, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Buzzwole @ Melemele Meadow + new(SN) { Species = 798, Level = 60, Location = 134, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Malie Garden + new(SN) { Species = 798, Level = 60, Location = 120, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Route 17 + }; + + public static readonly EncounterStatic7[] StaticMN = + { + new(MN) { Species = 792, Level = 55, Location = 178, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(714, 322, 539, 247) }, // Lunala + new(MN) { Species = 795, Level = 60, Location = 046, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Pheromosa @ Verdant Cavern (Trial Site) + new(MN) { Species = 797, Level = 65, Location = 124, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Haina Desert + new(MN) { Species = 797, Level = 65, Location = 134, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Malie Garden + }; + internal static readonly EncounterTrade7[] TradeGift_SM = // @ a\1\5\5 { // Trades - 4.bin - new(SM) { Species = 066, Form = 0, Level = 09, Ability = OnlySecond, TID16 = 00410, SID16 = 00000, IVs = new(-1,31,-1,-1,-1,-1), OTGender = 1, Gender = 0, Nature = Nature.Brave }, // Machop - new(SM) { Species = 761, Form = 0, Level = 16, Ability = OnlyFirst, TID16 = 20683, SID16 = 00009, IVs = new(-1,31,-1,-1,-1,-1), OTGender = 0, Gender = 1, Nature = Nature.Adamant }, // Bounsweet - new(SM) { Species = 061, Form = 0, Level = 22, Ability = OnlySecond, TID16 = 01092, SID16 = 00009, IVs = new(31,-1,-1,-1,-1,-1), OTGender = 1, Gender = 1, Nature = Nature.Naughty }, // Poliwhirl - new(SM) { Species = 440, Form = 0, Level = 27, Ability = OnlySecond, TID16 = 10913, SID16 = 00000, IVs = new(-1,-1,-1,-1,31,-1), OTGender = 1, Gender = 1, Nature = Nature.Calm }, // Happiny - new(SM) { Species = 075, Form = 1, Level = 32, Ability = OnlyFirst, TID16 = 20778, SID16 = 00009, IVs = new(-1,-1,31,-1,-1,-1), OTGender = 0, Gender = 0, Nature = Nature.Impish, EvolveOnTrade = true }, // Graveler-1 - new(SM) { Species = 762, Form = 0, Level = 43, Ability = OnlyFirst, TID16 = 20679, SID16 = 00009, IVs = new(-1,-1,-1,-1,-1,31), OTGender = 1, Gender = 1, Nature = Nature.Careful }, // Steenee - new(SM) { Species = 663, Form = 0, Level = 59, Ability = OnlyHidden, TID16 = 56734, SID16 = 00008, IVs = new(-1,-1,-1,31,-1,-1), OTGender = 0, Gender = 0, Nature = Nature.Jolly }, // Talonflame + new(TradeNames, 00, SM) { Species = 066, Form = 0, Level = 09, Ability = OnlySecond, ID32 = 000410, IVs = new(-1,31,-1,-1,-1,-1), OTGender = 1, Gender = 0, Nature = Nature.Brave }, // Machop + new(TradeNames, 01, SM) { Species = 761, Form = 0, Level = 16, Ability = OnlyFirst, ID32 = 610507, IVs = new(-1,31,-1,-1,-1,-1), OTGender = 0, Gender = 1, Nature = Nature.Adamant }, // Bounsweet + new(TradeNames, 02, SM) { Species = 061, Form = 0, Level = 22, Ability = OnlySecond, ID32 = 590916, IVs = new(31,-1,-1,-1,-1,-1), OTGender = 1, Gender = 1, Nature = Nature.Naughty }, // Poliwhirl + new(TradeNames, 03, SM) { Species = 440, Form = 0, Level = 27, Ability = OnlySecond, ID32 = 010913, IVs = new(-1,-1,-1,-1,31,-1), OTGender = 1, Gender = 1, Nature = Nature.Calm }, // Happiny + new(TradeNames, 04, SM) { Species = 075, Form = 1, Level = 32, Ability = OnlyFirst, ID32 = 610602, IVs = new(-1,-1,31,-1,-1,-1), OTGender = 0, Gender = 0, Nature = Nature.Impish, EvolveOnTrade = true }, // Graveler-1 + new(TradeNames, 05, SM) { Species = 762, Form = 0, Level = 43, Ability = OnlyFirst, ID32 = 610503, IVs = new(-1,-1,-1,-1,-1,31), OTGender = 1, Gender = 1, Nature = Nature.Careful }, // Steenee + new(TradeNames, 06, SM) { Species = 663, Form = 0, Level = 59, Ability = OnlyHidden, ID32 = 581022, IVs = new(-1,-1,-1,31,-1,-1), OTGender = 0, Gender = 0, Nature = Nature.Jolly }, // Talonflame }; - - private const string tradeSM = "tradesm"; - private static readonly string[][] TradeSM = Util.GetLanguageStrings10(tradeSM); - - internal static readonly EncounterStatic7[] StaticSN = GetEncounters(Encounter_SM, SN); - internal static readonly EncounterStatic7[] StaticMN = GetEncounters(Encounter_SM, MN); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7USUM.cs b/PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7USUM.cs similarity index 69% rename from PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7USUM.cs rename to PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7USUM.cs index c34f59366..25168b326 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen67/Encounters7USUM.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen7/Encounters7USUM.cs @@ -12,63 +12,47 @@ internal static class Encounters7USUM internal static readonly EncounterArea7[] SlotsUS = EncounterArea7.GetAreas(Get("us", "uu"), US); internal static readonly EncounterArea7[] SlotsUM = EncounterArea7.GetAreas(Get("um", "uu"), UM); - static Encounters7USUM() => MarkEncounterTradeStrings(TradeGift_USUM, TradeUSUM); + private const string tradeUSUM = "tradeusum"; + private static readonly string[][] TradeNames = Util.GetLanguageStrings10(tradeUSUM); - private static readonly EncounterStatic7[] Encounter_USUM = + public static readonly EncounterStatic7[] StaticUSUM = { - new(USUM) { Gift = true, Species = 722, Level = 05, Location = 008 }, // Rowlet - new(USUM) { Gift = true, Species = 725, Level = 05, Location = 008 }, // Litten - new(USUM) { Gift = true, Species = 728, Level = 05, Location = 008 }, // Popplio - new(USUM) { Gift = true, Species = 138, Level = 15, Location = 058 }, // Omanyte - new(USUM) { Gift = true, Species = 140, Level = 15, Location = 058 }, // Kabuto - // new(USUM) { Gift = true, Species = 142, Level = 15, Location = 058 }, // Aerodactyl - new(USUM) { Gift = true, Species = 345, Level = 15, Location = 058 }, // Lileep - new(USUM) { Gift = true, Species = 347, Level = 15, Location = 058 }, // Anorith - new(USUM) { Gift = true, Species = 408, Level = 15, Location = 058 }, // Cranidos - new(USUM) { Gift = true, Species = 410, Level = 15, Location = 058 }, // Shieldon - new(USUM) { Gift = true, Species = 564, Level = 15, Location = 058 }, // Tirtouga - new(USUM) { Gift = true, Species = 566, Level = 15, Location = 058 }, // Archen - new(USUM) { Gift = true, Species = 696, Level = 15, Location = 058 }, // Tyrunt - new(USUM) { Gift = true, Species = 698, Level = 15, Location = 058 }, // Amaura - new(USUM) { Gift = true, Species = 133, Level = 01, EggLocation = 60002 }, // Eevee @ Nursery helpers - new(USUM) { Gift = true, Species = 137, Level = 30, Location = 116 }, // Porygon @ Route 15 - new(USUM) { Gift = true, Species = 772, Level = 60, Location = 188, FlawlessIVCount = 3 }, // Type: Null @ Aether Paradise - new(USUM) { Gift = true, Species = 772, Level = 60, Location = 160, FlawlessIVCount = 3 }, // Type: Null @ Ancient Poni Path - new(US ) { Gift = true, Species = 789, Level = 05, Location = 142, FlawlessIVCount = 3, Shiny = Shiny.Never, Ability = OnlySecond }, // Cosmog @ Lake of the Sunne - new( UM) { Gift = true, Species = 789, Level = 05, Location = 144, FlawlessIVCount = 3, Shiny = Shiny.Never, Ability = OnlySecond }, // Cosmog @ Lake of the Moone - new(USUM) { Gift = true, Species = 142, Level = 40, Location = 172 }, // Aerodactyl @ Seafolk Village - new(USUM) { Gift = true, Species = 025, Level = 40, Location = 070, FlawlessIVCount = 3, HeldItem = 796, Relearn = new(57) }, // Pikachu @ Heahea City - new(USUM) { Gift = true, Species = 803, Level = 40, Location = 208, FlawlessIVCount = 3 }, // Poipole @ Megalo Tower - new(USUM) { Gift = true, Species = 803, Level = 40, Location = 206, FlawlessIVCount = 3 }, // Poipole @ Ultra Megalopolis + new(USUM) { FixedBall = Ball.Poke, Species = 722, Level = 05, Location = 008 }, // Rowlet + new(USUM) { FixedBall = Ball.Poke, Species = 725, Level = 05, Location = 008 }, // Litten + new(USUM) { FixedBall = Ball.Poke, Species = 728, Level = 05, Location = 008 }, // Popplio + new(USUM) { FixedBall = Ball.Poke, Species = 138, Level = 15, Location = 058 }, // Omanyte + new(USUM) { FixedBall = Ball.Poke, Species = 140, Level = 15, Location = 058 }, // Kabuto + // new(USUM) { FixedBall = Ball.Poke, Species = 142, Level = 15, Location = 058 }, // Aerodactyl + new(USUM) { FixedBall = Ball.Poke, Species = 345, Level = 15, Location = 058 }, // Lileep + new(USUM) { FixedBall = Ball.Poke, Species = 347, Level = 15, Location = 058 }, // Anorith + new(USUM) { FixedBall = Ball.Poke, Species = 408, Level = 15, Location = 058 }, // Cranidos + new(USUM) { FixedBall = Ball.Poke, Species = 410, Level = 15, Location = 058 }, // Shieldon + new(USUM) { FixedBall = Ball.Poke, Species = 564, Level = 15, Location = 058 }, // Tirtouga + new(USUM) { FixedBall = Ball.Poke, Species = 566, Level = 15, Location = 058 }, // Archen + new(USUM) { FixedBall = Ball.Poke, Species = 696, Level = 15, Location = 058 }, // Tyrunt + new(USUM) { FixedBall = Ball.Poke, Species = 698, Level = 15, Location = 058 }, // Amaura + new(USUM) { FixedBall = Ball.Poke, Species = 133, Level = 01, EggLocation = 60002 }, // Eevee @ Nursery helpers + new(USUM) { FixedBall = Ball.Poke, Species = 137, Level = 30, Location = 116 }, // Porygon @ Route 15 + new(USUM) { FixedBall = Ball.Poke, Species = 772, Level = 60, Location = 188, FlawlessIVCount = 3 }, // Type: Null @ Aether Paradise + new(USUM) { FixedBall = Ball.Poke, Species = 772, Level = 60, Location = 160, FlawlessIVCount = 3 }, // Type: Null @ Ancient Poni Path + new(USUM) { FixedBall = Ball.Poke, Species = 142, Level = 40, Location = 172 }, // Aerodactyl @ Seafolk Village + new(USUM) { FixedBall = Ball.Poke, Species = 025, Level = 40, Location = 070, FlawlessIVCount = 3, Relearn = new(57) }, // Pikachu @ Heahea City + new(USUM) { FixedBall = Ball.Poke, Species = 803, Level = 40, Location = 208, FlawlessIVCount = 3 }, // Poipole @ Megalo Tower + new(USUM) { FixedBall = Ball.Poke, Species = 803, Level = 40, Location = 206, FlawlessIVCount = 3 }, // Poipole @ Ultra Megalopolis - // Totem-Sized Gifts @ Heahea Beach - new(US ) { Gift = true, Species = 735, Level = 20, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Gumshoos - new( UM) { Gift = true, Species = 020, Level = 20, Ability = OnlyHidden, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Raticate - new(US ) { Gift = true, Species = 105, Level = 25, Ability = OnlyHidden, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Marowak - new( UM) { Gift = true, Species = 752, Level = 25, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Araquanid - new(US ) { Gift = true, Species = 754, Level = 30, Ability = OnlySecond, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Lurantis - new( UM) { Gift = true, Species = 758, Level = 30, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Salazzle - new(US ) { Gift = true, Species = 738, Level = 35, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Vikavolt - new( UM) { Gift = true, Species = 777, Level = 35, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Togedemaru - new(USUM) { Gift = true, Species = 778, Level = 40, Ability = OnlyFirst, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Mimikyu - new(US ) { Gift = true, Species = 743, Level = 50, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Ribombee - new( UM) { Gift = true, Species = 784, Level = 50, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Kommo-o - - new(USUM) { Gift = true, Species = 718, Level = 63, Ability = OnlyFirst, Location = 118, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde (10%) @ Route 16 + new(USUM) { FixedBall = Ball.Poke, Species = 778, Level = 40, Ability = OnlyFirst, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Mimikyu + new(USUM) { FixedBall = Ball.Poke, Species = 718, Level = 63, Ability = OnlyFirst, Location = 118, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde (10%) @ Route 16 new(USUM) // Magearna (Bottle Cap) { - Gift = true, Species = 801, Level = 50, Location = 40001, Shiny = Shiny.Never, FlawlessIVCount = 3, HeldItem = 795, Ability = OnlySecond, - FatefulEncounter = true, Relearn = new(705, 430, 381, 270), Ball = 0x10, // Cherish + FixedBall = Ball.Cherish, Species = 801, Level = 50, Location = 40001, Shiny = Shiny.Never, FlawlessIVCount = 3, Ability = OnlySecond, + FatefulEncounter = true, Relearn = new(705, 430, 381, 270), // Cherish }, - new(USUM) { Gift = true, Species = 718, Form = 0, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (50%) - new(USUM) { Gift = true, Species = 718, Form = 1, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (10%) - new(USUM) { Gift = true, Species = 718, Form = 2, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (10%-C) - new(USUM) { Gift = true, Species = 718, Form = 3, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (50%-C) - - new(US ) { Species = 791, Level = 60, Location = 028, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new(713,322,242,428) }, // Solgaleo @ Mahalo Trail (Plank Bridge) - new( UM) { Species = 792, Level = 60, Location = 028, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new(714,322,539,585) }, // Lunala @ Mahalo Trail (Plank Bridge) + new(USUM) { FixedBall = Ball.Poke, Species = 718, Form = 0, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (50%) + new(USUM) { FixedBall = Ball.Poke, Species = 718, Form = 1, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (10%) + new(USUM) { FixedBall = Ball.Poke, Species = 718, Form = 2, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (10%-C) + new(USUM) { FixedBall = Ball.Poke, Species = 718, Form = 3, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (50%-C) // QR Scan: Su/M/Tu/W/Th/F/Sa // Melemele Island @@ -116,46 +100,28 @@ internal static class Encounters7USUM new(USUM) { Species = 127, Level = 42, Location = 184, Shiny = Shiny.Never }, // Pinsir @ Exeggutor Island new(USUM) { Species = 127, Level = 43, Location = 184, Shiny = Shiny.Never }, // Pinsir @ Exeggutor Island - new(USUM) { Species = 800, Level = 65, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new(722,334,408,400), HeldItem = 923 }, // Necrozma @ Mount Lanakila + new(USUM) { Species = 800, Level = 65, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new(722,334,408,400) }, // Necrozma @ Mount Lanakila // Legendaries new(USUM) { Species = 144, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(246,573,115,258) }, // Articuno new(USUM) { Species = 145, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(246,435,365,240) }, // Zapdos new(USUM) { Species = 146, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(246,053,403,241) }, // Moltres new(USUM) { Species = 150, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(094,105,129,427) }, // Mewtwo - new(US ) { Species = 243, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Raikou - new( UM) { Species = 244, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(023,044,207,436) }, // Entei new(USUM) { Species = 245, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(061,062,054,240) }, // Suicune - new( UM) { Species = 249, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(285,177,326,246) }, // Lugia - new(US ) { Species = 250, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(682,221,326,246), HeldItem = 044 }, // Ho-Oh new(USUM) { Species = 377, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Regirock new(USUM) { Species = 378, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Regice new(USUM) { Species = 379, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Registeel - new( UM) { Species = 380, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(296,406,375,273), Gender = 1 }, // Latias - new(US ) { Species = 381, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(295,406,375,225), Gender = 0 }, // Latios - new( UM) { Species = 382, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(058,618,347,330) }, // Kyogre - new(US ) { Species = 383, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(089,619,339,076) }, // Groudon new(USUM) { Species = 384, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Rayquaza new(USUM) { Species = 480, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(326,281,133,129) }, // Uxie new(USUM) { Species = 481, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(326,204,248,129) }, // Mesprit new(USUM) { Species = 482, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(326,417,253,129) }, // Azelf - new(US ) { Species = 483, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Dialga - new( UM) { Species = 484, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Palkia - new(US ) { Species = 485, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Heatran - new( UM) { Species = 486, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(428,279,146,109) }, // Regigigas new(USUM) { Species = 487, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(467,396,414,337) }, // Giratina new(USUM) { Species = 488, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 1 }, // Cresselia new(USUM) { Species = 638, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(533,014,098,442) }, // Cobalion new(USUM) { Species = 639, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(533,014,157,444) }, // Terrakion new(USUM) { Species = 640, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(533,014,202,348) }, // Virizion - new(US ) { Species = 641, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Tornadus - new( UM) { Species = 642, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Thundurus - new(US ) { Species = 643, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Reshiram - new( UM) { Species = 644, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Zekrom new(USUM) { Species = 645, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Landorus new(USUM) { Species = 646, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kyurem - new(US ) { Species = 716, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(601,532,400,585) }, // Xerneas - new( UM) { Species = 717, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(613,399,566,094) }, // Yveltal new(USUM) { Species = 718, Level = 60, Location = 182, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new(616,137,219,225) }, // Zygarde @ Resolution Cave // Ultra Space Wilds @@ -182,14 +148,8 @@ internal static class Encounters7USUM // Ultra Beasts new(USUM) { Species = 793, Level = 60, Location = 190, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(408,491,446,243) }, // Nihilego @ Ultra Deep Sea - new(US ) { Species = 794, Level = 60, Location = 218, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Buzzwole @ Ultra Jungle - new( UM) { Species = 795, Level = 60, Location = 214, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Pheromosa @ Ultra Desert new(USUM) { Species = 796, Level = 60, Location = 210, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Ultra Plant - new( UM) { Species = 797, Level = 60, Location = 212, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Ultra Crater - new(US ) { Species = 798, Level = 60, Location = 216, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Ultra Forest new(USUM) { Species = 799, Level = 60, Location = 220, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Guzzlord @ Ultra Ruin - new( UM) { Species = 805, Level = 60, Location = 164, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Stakataka @ Poni Grove - new(US ) { Species = 806, Level = 60, Location = 164, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Blacephalon @ Poni Grove // Ditto Five new(USUM) { Species = 132, Level = 29, Location = 060, IVs = new(-1,-1,31,00,30,-1), Nature = Nature.Bold }, // Ditto @ Route 9 @@ -205,7 +165,6 @@ internal static class Encounters7USUM new(USUM) { Species = 097, Level = 29, Location = 020, Shiny = Shiny.Never, Relearn = new(093,050,001,096) }, // Hypno @ Hau'oli City Police Station new(USUM) { Species = 092, Level = 19, Location = 230, Shiny = Shiny.Never, Relearn = new(174,109,122,101) }, // Gastly @ Route 1 (Trainers’ School) new(USUM) { Species = 425, Level = 19, Location = 230, Shiny = Shiny.Never, Relearn = new(310,132,016,371) }, // Drifloon @ Route 1 (Trainers’ School) - new( UM) { Species = 769, Level = 30, Location = 116, Shiny = Shiny.Never, Relearn = new(310,523,072,328) }, // Sandygast @ Route 15 new(USUM) { Species = 592, Level = 34, Location = 126, Shiny = Shiny.Never, Gender = 1 }, // Frillish @ Route 14 new(USUM) { Species = 101, Level = 60, Location = 224, Ability = OnlyFirst, Shiny = Shiny.Never }, // Electrode @ Team Rocket's Castle @@ -216,21 +175,71 @@ internal static class Encounters7USUM new(USUM) { Species = 739, Level = 32, Location = 120 }, // Route 17 }; + public static readonly EncounterStatic7[] StaticUS = + { + new(US ) { FixedBall = Ball.Poke, Species = 789, Level = 05, Location = 142, FlawlessIVCount = 3, Shiny = Shiny.Never, Ability = OnlySecond }, // Cosmog @ Lake of the Sunne + + // Totem-Sized Gifts @ Heahea Beach + new(US ) { FixedBall = Ball.Poke, Species = 735, Level = 20, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Gumshoos + new(US ) { FixedBall = Ball.Poke, Species = 105, Level = 25, Ability = OnlyHidden, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Marowak + new(US ) { FixedBall = Ball.Poke, Species = 754, Level = 30, Ability = OnlySecond, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Lurantis + new(US ) { FixedBall = Ball.Poke, Species = 738, Level = 35, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Vikavolt + new(US ) { FixedBall = Ball.Poke, Species = 743, Level = 50, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Ribombee + + new(US ) { Species = 791, Level = 60, Location = 028, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new(713,322,242,428) }, // Solgaleo @ Mahalo Trail (Plank Bridge) + new(US ) { Species = 243, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Raikou + new(US ) { Species = 250, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(682,221,326,246) }, // Ho-Oh + new(US ) { Species = 381, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(295,406,375,225), Gender = 0 }, // Latios + new(US ) { Species = 383, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(089,619,339,076) }, // Groudon + new(US ) { Species = 483, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Dialga + new(US ) { Species = 485, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Heatran + new(US ) { Species = 641, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Tornadus + new(US ) { Species = 643, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Reshiram + new(US ) { Species = 716, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(601,532,400,585) }, // Xerneas + + new(US ) { Species = 794, Level = 60, Location = 218, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Buzzwole @ Ultra Jungle + new(US ) { Species = 798, Level = 60, Location = 216, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Ultra Forest + new(US ) { Species = 806, Level = 60, Location = 164, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Blacephalon @ Poni Grove + }; + + public static readonly EncounterStatic7[] StaticUM = + { + new( UM) { FixedBall = Ball.Poke, Species = 789, Level = 05, Location = 144, FlawlessIVCount = 3, Shiny = Shiny.Never, Ability = OnlySecond }, // Cosmog @ Lake of the Moone + + // Totem-Sized Gifts @ Heahea Beach + new( UM) { FixedBall = Ball.Poke, Species = 020, Level = 20, Ability = OnlyHidden, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Raticate + new( UM) { FixedBall = Ball.Poke, Species = 752, Level = 25, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Araquanid + new( UM) { FixedBall = Ball.Poke, Species = 758, Level = 30, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Salazzle + new( UM) { FixedBall = Ball.Poke, Species = 777, Level = 35, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Togedemaru + new( UM) { FixedBall = Ball.Poke, Species = 784, Level = 50, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Kommo-o + + new( UM) { Species = 792, Level = 60, Location = 028, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new(714,322,539,585) }, // Lunala @ Mahalo Trail (Plank Bridge) + new( UM) { Species = 244, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(023,044,207,436) }, // Entei + new( UM) { Species = 249, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(285,177,326,246) }, // Lugia + new( UM) { Species = 380, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(296,406,375,273), Gender = 1 }, // Latias + new( UM) { Species = 382, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(058,618,347,330) }, // Kyogre + new( UM) { Species = 484, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Palkia + new( UM) { Species = 486, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(428,279,146,109) }, // Regigigas + new( UM) { Species = 642, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Thundurus + new( UM) { Species = 644, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Zekrom + new( UM) { Species = 717, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new(613,399,566,094) }, // Yveltal + + new( UM) { Species = 795, Level = 60, Location = 214, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Pheromosa @ Ultra Desert + new( UM) { Species = 797, Level = 60, Location = 212, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Ultra Crater + new( UM) { Species = 805, Level = 60, Location = 164, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Stakataka @ Poni Grove + + new( UM) { Species = 769, Level = 30, Location = 116, Shiny = Shiny.Never, Relearn = new(310,523,072,328) }, // Sandygast @ Route 15 + }; + internal static readonly EncounterTrade7[] TradeGift_USUM = { // Trades - 4.bin - new(USUM) { Species = 701, Form = 0, Level = 08, Ability = OnlySecond, TID16 = 00410, SID16 = 00000, IVs = new(-1,31,-1,-1,-1,-1), OTGender = 1, Gender = 0, Nature = Nature.Brave }, // Hawlucha - new(USUM) { Species = 714, Form = 0, Level = 19, Ability = OnlyFirst, TID16 = 20683, SID16 = 00009, IVs = new(-1,-1,-1,-1,31,-1), OTGender = 0, Gender = 0, Nature = Nature.Modest }, // Noibat - new(USUM) { Species = 339, Form = 0, Level = 21, Ability = OnlySecond, TID16 = 01092, SID16 = 00009, IVs = new(31,-1,-1,-1,-1,-1), OTGender = 0, Gender = 1, Nature = Nature.Naughty }, // Barboach - new(USUM) { Species = 024, Form = 0, Level = 22, Ability = OnlyFirst, TID16 = 10913, SID16 = 00000, IVs = new(-1,-1,31,-1,-1,-1), OTGender = 1, Gender = 1, Nature = Nature.Impish }, // Arbok - new(USUM) { Species = 708, Form = 0, Level = 33, Ability = OnlyFirst, TID16 = 20778, SID16 = 00009, IVs = new(-1,-1,-1,-1,-1,31), OTGender = 0, Gender = 0, Nature = Nature.Calm, EvolveOnTrade = true }, // Phantump - new(USUM) { Species = 422, Form = 0, Level = 44, Ability = OnlySecond, TID16 = 20679, SID16 = 00009, IVs = new(-1,-1,31,-1,-1,-1), OTGender = 1, Gender = 1, Nature = Nature.Quiet }, // Shellos - new(USUM) { Species = 128, Form = 0, Level = 59, Ability = OnlyFirst, TID16 = 56734, SID16 = 00008, IVs = new(-1,-1,-1,31,-1,-1), OTGender = 0, Gender = 0, Nature = Nature.Jolly }, // Tauros + new(TradeNames, 00, USUM) { Species = 701, Form = 0, Level = 08, Ability = OnlySecond, ID32 = 000410, IVs = new(-1,31,-1,-1,-1,-1), OTGender = 1, Gender = 0, Nature = Nature.Brave }, // Hawlucha + new(TradeNames, 01, USUM) { Species = 714, Form = 0, Level = 19, Ability = OnlyFirst, ID32 = 610507, IVs = new(-1,-1,-1,-1,31,-1), OTGender = 0, Gender = 0, Nature = Nature.Modest }, // Noibat + new(TradeNames, 02, USUM) { Species = 339, Form = 0, Level = 21, Ability = OnlySecond, ID32 = 590916, IVs = new(31,-1,-1,-1,-1,-1), OTGender = 0, Gender = 1, Nature = Nature.Naughty }, // Barboach + new(TradeNames, 03, USUM) { Species = 024, Form = 0, Level = 22, Ability = OnlyFirst, ID32 = 010913, IVs = new(-1,-1,31,-1,-1,-1), OTGender = 1, Gender = 1, Nature = Nature.Impish }, // Arbok + new(TradeNames, 04, USUM) { Species = 708, Form = 0, Level = 33, Ability = OnlyFirst, ID32 = 610602, IVs = new(-1,-1,-1,-1,-1,31), OTGender = 0, Gender = 0, Nature = Nature.Calm, EvolveOnTrade = true }, // Phantump + new(TradeNames, 05, USUM) { Species = 422, Form = 0, Level = 44, Ability = OnlySecond, ID32 = 610503, IVs = new(-1,-1,31,-1,-1,-1), OTGender = 1, Gender = 1, Nature = Nature.Quiet }, // Shellos + new(TradeNames, 06, USUM) { Species = 128, Form = 0, Level = 59, Ability = OnlyFirst, ID32 = 581022, IVs = new(-1,-1,-1,31,-1,-1), OTGender = 0, Gender = 0, Nature = Nature.Jolly }, // Tauros }; - - private const string tradeUSUM = "tradeusum"; - private static readonly string[][] TradeUSUM = Util.GetLanguageStrings10(tradeUSUM); - - internal static readonly EncounterStatic7[] StaticUS = GetEncounters(Encounter_USUM, US); - internal static readonly EncounterStatic7[] StaticUM = GetEncounters(Encounter_USUM, UM); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8.cs b/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8.cs index 3a793992a..a910d5f30 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8.cs @@ -4,8 +4,6 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.AreaWeather8; using static PKHeX.Core.AbilityPermission; -using static PKHeX.Core.Encounters8Nest; - namespace PKHeX.Core; /// @@ -13,61 +11,50 @@ namespace PKHeX.Core; /// internal static class Encounters8 { - private static readonly EncounterArea8[] SlotsSW_Symbol = EncounterArea8.GetAreas(Get("sw_symbol", "sw"), SW, true); - private static readonly EncounterArea8[] SlotsSH_Symbol = EncounterArea8.GetAreas(Get("sh_symbol", "sh"), SH, true); - private static readonly EncounterArea8[] SlotsSW_Hidden = EncounterArea8.GetAreas(Get("sw_hidden", "sw"), SW); - private static readonly EncounterArea8[] SlotsSH_Hidden = EncounterArea8.GetAreas(Get("sh_hidden", "sh"), SH); + public static readonly EncounterArea8[] SlotsSW_Symbol = EncounterArea8.GetAreas(Get("sw_symbol", "sw"), SW, true); + public static readonly EncounterArea8[] SlotsSH_Symbol = EncounterArea8.GetAreas(Get("sh_symbol", "sh"), SH, true); + public static readonly EncounterArea8[] SlotsSW_Hidden = EncounterArea8.GetAreas(Get("sw_hidden", "sw"), SW); + public static readonly EncounterArea8[] SlotsSH_Hidden = EncounterArea8.GetAreas(Get("sh_hidden", "sh"), SH); - internal static readonly EncounterArea8[] SlotsSW = ArrayUtil.ConcatAll(SlotsSW_Symbol, SlotsSW_Hidden); - internal static readonly EncounterArea8[] SlotsSH = ArrayUtil.ConcatAll(SlotsSH_Symbol, SlotsSH_Hidden); - - static Encounters8() - { - foreach (var t in TradeGift_R1) - t.TrainerNames = TradeOT_R1; - - MarkEncounterTradeStrings(TradeGift_SWSH, TradeSWSH); - } - - private static readonly EncounterStatic8[] Encounter_SWSH_0 = + public static readonly EncounterStatic8[] StaticSWSH = { // gifts - new() { Gift = true, Species = 810, Shiny = Never, Level = 05, Location = 006 }, // Grookey - new() { Gift = true, Species = 813, Shiny = Never, Level = 05, Location = 006 }, // Scorbunny - new() { Gift = true, Species = 816, Shiny = Never, Level = 05, Location = 006 }, // Sobble + new() { FixedBall = Ball.Poke, Species = 810, Shiny = Never, Level = 05, Location = 006 }, // Grookey + new() { FixedBall = Ball.Poke, Species = 813, Shiny = Never, Level = 05, Location = 006 }, // Scorbunny + new() { FixedBall = Ball.Poke, Species = 816, Shiny = Never, Level = 05, Location = 006 }, // Sobble - new() { Gift = true, Species = 772, Shiny = Never, Level = 50, Location = 158, FlawlessIVCount = 3 }, // Type: Null - new() { Gift = true, Species = 848, Shiny = Never, Level = 01, Location = 040, IVs = new(-1,31,-1,-1,31,-1), Ball = 11 }, // Toxel, Attack flawless + new() { FixedBall = Ball.Poke, Species = 772, Shiny = Never, Level = 50, Location = 158, FlawlessIVCount = 3 }, // Type: Null + new() { FixedBall = Ball.Luxury, Species = 848, Shiny = Never, Level = 01, Location = 040, IVs = new(-1,31,-1,-1,31,-1) }, // Toxel, Attack flawless - new() { Gift = true, Species = 880, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Dracozolt @ Route 6 - new() { Gift = true, Species = 881, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Arctozolt @ Route 6 - new() { Gift = true, Species = 882, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Dracovish @ Route 6 - new() { Gift = true, Species = 883, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Arctovish @ Route 6 + new() { FixedBall = Ball.Poke, Species = 880, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Dracozolt @ Route 6 + new() { FixedBall = Ball.Poke, Species = 881, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Arctozolt @ Route 6 + new() { FixedBall = Ball.Poke, Species = 882, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Dracovish @ Route 6 + new() { FixedBall = Ball.Poke, Species = 883, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Arctovish @ Route 6 - new() { Gift = true, Species = 004, Shiny = Never, Level = 05, Location = 006, FlawlessIVCount = 3, CanGigantamax = true, Ability = OnlyFirst }, // Charmander - new() { Gift = true, Species = 025, Shiny = Never, Level = 10, Location = 156, FlawlessIVCount = 6, CanGigantamax = true }, // Pikachu - new() { Gift = true, Species = 133, Shiny = Never, Level = 10, Location = 156, FlawlessIVCount = 6, CanGigantamax = true }, // Eevee + new() { FixedBall = Ball.Poke, Species = 004, Shiny = Never, Level = 05, Location = 006, FlawlessIVCount = 3, CanGigantamax = true, Ability = OnlyFirst }, // Charmander + new() { FixedBall = Ball.Poke, Species = 025, Shiny = Never, Level = 10, Location = 156, FlawlessIVCount = 6, CanGigantamax = true }, // Pikachu + new() { FixedBall = Ball.Poke, Species = 133, Shiny = Never, Level = 10, Location = 156, FlawlessIVCount = 6, CanGigantamax = true }, // Eevee // DLC gifts - new() { Gift = true, Species = 001, Level = 05, Location = 196, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, CanGigantamax = true }, // Bulbasaur - new() { Gift = true, Species = 007, Level = 05, Location = 196, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, CanGigantamax = true }, // Squirtle - new() { Gift = true, Species = 137, Level = 25, Location = 196, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Porygon - new() { Gift = true, Species = 891, Level = 10, Location = 196, Shiny = Never, FlawlessIVCount = 3 }, // Kubfu + new() { FixedBall = Ball.Poke, Species = 001, Level = 05, Location = 196, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, CanGigantamax = true }, // Bulbasaur + new() { FixedBall = Ball.Poke, Species = 007, Level = 05, Location = 196, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, CanGigantamax = true }, // Squirtle + new() { FixedBall = Ball.Poke, Species = 137, Level = 25, Location = 196, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Porygon + new() { FixedBall = Ball.Poke, Species = 891, Level = 10, Location = 196, Shiny = Never, FlawlessIVCount = 3 }, // Kubfu - new() { Gift = true, Species = 079, Level = 10, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Slowpoke - new() { Gift = true, Species = 722, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Rowlet - new() { Gift = true, Species = 725, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Litten - new() { Gift = true, Species = 728, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Popplio - new() { Gift = true, Species = 026, Level = 30, Location = 164, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, Form = 01 }, // Raichu-1 - new() { Gift = true, Species = 027, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Sandshrew-1 - new() { Gift = true, Species = 037, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Vulpix-1 - new() { Gift = true, Species = 052, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Meowth-1 - new() { Gift = true, Species = 103, Level = 30, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Exeggutor-1 - new() { Gift = true, Species = 105, Level = 30, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Marowak-1 - new() { Gift = true, Species = 050, Level = 20, Location = 164, Shiny = Never, Ability = OnlyHidden, Gender = 0, Nature = Nature.Jolly, FlawlessIVCount = 6, Form = 01 }, // Diglett-1 + new() { FixedBall = Ball.Poke, Species = 079, Level = 10, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Slowpoke + new() { FixedBall = Ball.Poke, Species = 722, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Rowlet + new() { FixedBall = Ball.Poke, Species = 725, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Litten + new() { FixedBall = Ball.Poke, Species = 728, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Popplio + new() { FixedBall = Ball.Poke, Species = 026, Level = 30, Location = 164, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, Form = 01 }, // Raichu-1 + new() { FixedBall = Ball.Poke, Species = 027, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Sandshrew-1 + new() { FixedBall = Ball.Poke, Species = 037, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Vulpix-1 + new() { FixedBall = Ball.Poke, Species = 052, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Meowth-1 + new() { FixedBall = Ball.Poke, Species = 103, Level = 30, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Exeggutor-1 + new() { FixedBall = Ball.Poke, Species = 105, Level = 30, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Marowak-1 + new() { FixedBall = Ball.Poke, Species = 050, Level = 20, Location = 164, Shiny = Never, Ability = OnlyHidden, Gender = 0, Nature = Nature.Jolly, FlawlessIVCount = 6, Form = 01 }, // Diglett-1 - new() { Gift = true, Species = 789, Level = 05, Location = 206, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Cosmog - new() { Gift = true, Species = 803, Level = 20, Location = 244, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Ball = 26 }, // Poipole + new() { FixedBall = Ball.Poke, Species = 789, Level = 05, Location = 206, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Cosmog + new() { FixedBall = Ball.Beast,Species = 803, Level = 20, Location = 244, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Poipole // Technically a gift, but copies ball from Calyrex. new() { Species = 896, Level = 75, Location = 220, ScriptedNoMarks = true, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Relearn = new(556) }, // Glastrier @@ -110,10 +97,9 @@ internal static class Encounters8 new() { Species = 460, Level = 55, Location = 106, Moves = new(008,059,452,275), Weather = Snowstorm }, // Abomasnow on Route 10 new() { Species = 342, Level = 50, Location = 034, Moves = new(242,014,534,400), FlawlessIVCount = 3 }, // Crawdaunt in the town of Turffield #endregion - }; - private static readonly EncounterStatic8[] Encounter_SWSH_Strong0 = - { + // Strong-0 + // Some of these may be crossover cases. For now, just log the locations they can show up in and re-categorize later. new() { Species = 095, Level = 26, Location = 122, Weather = All }, // Onix in the Rolling Fields new() { Species = 291, Level = 15, Location = 122, Weather = All }, // Ninjask in the Rolling Fields @@ -137,8 +123,6 @@ internal static class Encounters8 new() { Species = 760, Level = 34, Location = 124, Weather = All }, // Bewear in the Dappled Grove new() { Species = 826, Level = 65, Location = 124, Weather = All }, // Orbeetle in the Dappled Grove new() { Species = 045, Level = 36, Location = 124, Weather = Normal | Overcast | Heavy_Fog }, // Vileplume in the Dappled Grove - new(SW ) { Species = 275, Level = 34, Location = 124, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Shiftry in the Dappled Grove - new( SH) { Species = 272, Level = 34, Location = 124, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Ludicolo in the Dappled Grove new() { Species = 675, Level = 32, Location = 124, Weather = Intense_Sun | Icy | Sandstorm }, // Pangoro in the Dappled Grove new() { Species = 537, Level = 36, Location = 124, Weather = Stormy }, // Seismitoad in the Dappled Grove new() { Species = 583, Level = 36, Location = 124, Weather = Icy }, // Vanillish in the Dappled Grove @@ -272,8 +256,6 @@ internal static class Encounters8 new() { Species = 750, Level = 41, Location = 146, Weather = Normal | Intense_Sun | Sandstorm }, // Mudsdale in Dusty Bowl new() { Species = 185, Level = 41, Location = 146, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Sudowoodo in Dusty Bowl new() { Species = 437, Level = 41, Location = 146, Weather = Normal | Stormy | Icy | Heavy_Fog }, // Bronzong in Dusty Bowl - new(SW ) { Species = 784, Level = 60, Location = 146, Ability = OnlyFirst, Weather = Normal | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Kommo-o in Dusty Bowl - new( SH) { Species = 248, Level = 60, Location = 146, Weather = Normal | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Tyranitar in Dusty Bowl new() { Species = 213, Level = 34, Location = 146, Weather = All }, // Shuckle in Dusty Bowl new() { Species = 330, Level = 51, Location = 146, Weather = Normal | Sandstorm }, // Flygon in Dusty Bowl new() { Species = 526, Level = 51, Location = 146, Weather = Normal | Intense_Sun }, // Gigalith in Dusty Bowl @@ -335,10 +317,9 @@ internal static class Encounters8 new() { Species = 136, Level = 56, Location = 154, Weather = Intense_Sun }, // Flareon at the Lake of Outrage new() { Species = 197, Level = 56, Location = 154, Weather = Sandstorm }, // Umbreon at the Lake of Outrage new() { Species = 700, Level = 56, Location = 154, Weather = Heavy_Fog }, // Sylveon at the Lake of Outrage - }; - private static readonly EncounterStatic8[] Encounter_SWSH_Strong1 = - { + // Strong 1 + new() { Species = 079, Level = 12, Location = 016, ScriptedNoMarks = true, Form = 01, Shiny = Never }, // Slowpoke-1 at Wedgehurst Station new() { Species = 321, Level = 80, Location = 186, Weather = All_IoA }, // Wailord in the Workout Sea @@ -370,8 +351,6 @@ internal static class Encounters8 new() { Species = 186, Level = 32, Location = 166, Weather = Stormy }, // Politoed in the Soothing Wetlands new() { Species = 061, Level = 20, Location = 166, Weather = Stormy | Heavy_Fog }, // Poliwhirl in the Soothing Wetlands new() { Species = 549, Level = 22, Location = 166, Weather = Intense_Sun }, // Lilligant in the Soothing Wetlands - new(SW ) { Species = 559, Level = 20, Location = 166, Weather = Overcast }, // Scraggy in the Soothing Wetlands - new( SH) { Species = 453, Level = 20, Location = 166, Weather = Overcast }, // Croagunk in the Soothing Wetlands new() { Species = 663, Level = 32, Location = 166, Weather = Intense_Sun }, // Talonflame in the Soothing Wetlands new() { Species = 026, Level = 26, Location = 166, Crossover = new(168), Weather = Thunderstorm }, // Raichu in the Soothing Wetlands, in the Forest of Focus new() { Species = 184, Level = 21, Location = 166, Crossover = new(168), Weather = Heavy_Fog }, // Azumarill in the Soothing Wetlands, in the Forest of Focus @@ -380,20 +359,14 @@ internal static class Encounters8 //new() { Species = 834, Level = 21, Location = -1 }, // Drednaw //new() { Species = 768, Level = 26, Location = -1 }, // Golisopod new() { Species = 025, Level = 22, Location = 168, Weather = Normal | Overcast | Stormy }, // Pikachu in the Forest of Focus - new(SW ) { Species = 766, Level = 26, Location = 168 }, // Passimian in the Forest of Focus - new( SH) { Species = 765, Level = 26, Location = 168 }, // Oranguru in the Forest of Focus new() { Species = 342, Level = 26, Location = 168, Weather = Overcast | Stormy }, // Crawdaunt in the Forest of Focus new() { Species = 040, Level = 26, Location = 168, Weather = Heavy_Fog }, // Wigglytuff in the Forest of Focus new() { Species = 028, Level = 26, Location = 168, Weather = Sandstorm }, // Sandslash in the Forest of Focus new() { Species = 589, Level = 32, Location = 168, Weather = Sandstorm }, // Escavalier in the Forest of Focus new() { Species = 104, Level = 20, Location = 168, Weather = Sandstorm }, // Cubone in the Forest of Focus new() { Species = 545, Level = 32, Location = 168, Weather = Overcast }, // Scolipede in the Forest of Focus - new(SW ) { Species = 127, Level = 26, Location = 168, Weather = Intense_Sun }, // Pinsir in the Forest of Focus - new( SH) { Species = 214, Level = 26, Location = 168, Weather = Intense_Sun }, // Heracross in the Forest of Focus new() { Species = 636, Level = 15, Location = 168, Weather = Intense_Sun }, // Larvesta in the Forest of Focus new() { Species = 465, Level = 36, Location = 168, Weather = Intense_Sun }, // Tangrowth in the Forest of Focus - new(SW ) { Species = 616, Level = 20, Location = 168, Weather = Stormy }, // Shelmet in the Forest of Focus - new( SH) { Species = 704, Level = 20, Location = 168, Weather = Stormy }, // Goomy in the Forest of Focus new() { Species = 172, Level = 20, Location = 168, Weather = Thunderstorm }, // Pichu in the Forest of Focus new() { Species = 845, Level = 20, Location = 168, Weather = Normal | Raining | Intense_Sun | Heavy_Fog }, // Cramorant in the Forest of Focus new() { Species = 617, Level = 32, Location = 168, Weather = Raining }, // Accelgor in the Forest of Focus @@ -452,10 +425,6 @@ internal static class Encounters8 new() { Species = 621, Level = 36, Location = 172 }, // Druddigon in Brawlers’ Cave new() { Species = 055, Level = 26, Location = 172 }, // Golduck in Brawlers’ Cave new() { Species = 526, Level = 42, Location = 172 }, // Gigalith in Brawlers’ Cave - new(SW ) { Species = 744, Level = 22, Location = 172, Crossover = new(174), Weather = Normal | Overcast }, // Rockruff on Challenge Road, Brawlers' Cave (c) - new( SH) { Species = 744, Level = 22, Location = 172, Crossover = new(174), Weather = Normal | Overcast | Intense_Sun | Heavy_Fog }, // Rockruff on Challenge Road, Brawlers' Cave (c) - new(SW ) { Species = 560, Level = 26, Location = 172, Crossover = new(174, 180), Weather = Stormy }, // Scrafty on Challenge Road, Brawlers’ Cave (c), Training Lowlands - new( SH) { Species = 454, Level = 26, Location = 172, Crossover = new(174, 180), Weather = Stormy }, // Toxicroak on Challenge Road, Brawlers’ Cave (c), Training Lowlands new() { Species = 558, Level = 26, Location = 172, Crossover = new(174, 180), Weather = Sandstorm }, // Crustle on Challenge Road, Brawlers’ Cave (c), Training Lowlands new() { Species = 340, Level = 42, Location = 172, Crossover = new(176) }, // Whiscash in Courageous Cavern, Brawlers' Cave new() { Species = 620, Level = 28, Location = 174 }, // Mienshao on Challenge Road @@ -465,13 +434,7 @@ internal static class Encounters8 new() { Species = 745, Level = 32, Location = 174 }, // Lycanroc on Challenge Road new() { Species = 745, Level = 32, Location = 174, Form = 01, Weather = Overcast }, // Lycanroc-1 on Challenge Road new() { Species = 212, Level = 40, Location = 174, Weather = Sandstorm }, // Scizor on Challenge Road - new(SW ) { Species = 127, Level = 26, Location = 174, Weather = Intense_Sun }, // Pinsir on Challenge Road - new( SH) { Species = 214, Level = 26, Location = 174, Weather = Intense_Sun }, // Heracross on Challenge Road - new(SW ) { Species = 782, Level = 22, Location = 174, Weather = Intense_Sun | Sandstorm | Heavy_Fog }, // Jangmo-o on Challenge Road - new() { Species = 227, Level = 26, Location = 174, Weather = Normal | Raining | Intense_Sun | Sandstorm }, // Skarmory on Challenge Road new() { Species = 426, Level = 26, Location = 174, Weather = Heavy_Fog }, // Drifblim on Challenge Road - new(SW ) { Species = 628, Level = 26, Location = 174, Weather = Overcast }, // Braviary on Challenge Road - new( SH) { Species = 630, Level = 26, Location = 174, Weather = Overcast }, // Mandibuzz on Challenge Road new() { Species = 082, Level = 26, Location = 174, Weather = Thunderstorm }, // Magneton on Challenge Road new() { Species = 507, Level = 28, Location = 174, Crossover = new(180), Weather = Normal | Heavy_Fog }, // Herdier on Challenge Road, Training Lowlands new() { Species = 558, Level = 28, Location = 176 }, // Crustle in Courageous Cavern @@ -514,14 +477,12 @@ internal static class Encounters8 new() { Species = 764, Level = 28, Location = 180, Weather = Heavy_Fog }, // Comfey in the Training Lowlands new() { Species = 452, Level = 28, Location = 180, Weather = Overcast | Intense_Sun }, // Drapion in the Training Lowlands new() { Species = 279, Level = 28, Location = 180, Weather = Raining }, // Pelipper in the Training Lowlands - new(SW ) { Species = 127, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Pinsir in the Training Lowlands - new( SH) { Species = 214, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Heracross in the Training Lowlands + new() { Species = 227, Level = 26, Location = 174, Weather = Normal | Raining | Intense_Sun | Sandstorm }, // Skarmory on Challenge Road new() { Species = 528, Level = 28, Location = 180, Weather = Overcast }, // Swoobat in the Training Lowlands new() { Species = 241, Level = 28, Location = 180, Weather = Normal | Overcast | Intense_Sun }, // Miltank in the Training Lowlands new() { Species = 082, Level = 28, Location = 180, Weather = Thunderstorm }, // Magneton in the Training Lowlands new() { Species = 662, Level = 28, Location = 180, Weather = Intense_Sun }, // Fletchinder in the Training Lowlands new() { Species = 227, Level = 26, Location = 180, Weather = Sandstorm }, // Skarmory in the Training Lowlands - new(SW ) { Species = 782, Level = 22, Location = 180, Weather = Sandstorm }, // Jangmo-o in Training Lowlands new() { Species = 128, Level = 28, Location = 180, Weather = All_IoA }, // Tauros in the Training Lowlands new() { Species = 687, Level = 28, Location = 180, Weather = Overcast | Raining }, // Malamar in the Training Lowlands new() { Species = 549, Level = 28, Location = 180, Weather = Intense_Sun }, // Lilligant in the Training Lowlands @@ -543,8 +504,6 @@ internal static class Encounters8 new() { Species = 844, Level = 42, Location = 184, Weather = Normal | Intense_Sun | Heavy_Fog }, // Sandaconda in the Potbottom Desert new() { Species = 637, Level = 50, Location = 184, Weather = Intense_Sun }, // Volcarona in the Potbottom Desert new() { Species = 028, Level = 42, Location = 184, Weather = Sandstorm }, // Sandslash in the Potbottom Desert - new(SW ) { Species = 628, Level = 42, Location = 184, Weather = Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Braviary in the Potbottom Desert - new( SH) { Species = 630, Level = 42, Location = 184, Weather = Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Mandibuzz in the Potbottom Desert new() { Species = 479, Level = 50, Location = 186, FlawlessIVCount = 3, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom in the Workout Sea new() { Species = 479, Level = 50, Location = 186, Form = 01, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-1 in the Workout Sea new() { Species = 479, Level = 50, Location = 186, Form = 02, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-2 in the Workout Sea @@ -564,10 +523,9 @@ internal static class Encounters8 new() { Species = 117, Level = 45, Location = 192, Weather = Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Seadra in the Honeycalm Sea new() { Species = 549, Level = 45, Location = 194, Weather = Normal | Intense_Sun }, // Lilligant on Honeycalm Island new() { Species = 415, Level = 40, Location = 194, Weather = Overcast | Stormy }, // Combee on Honeycalm Island - }; - private static readonly EncounterStatic8[] Encounter_SWSH_Strong2 = - { + // Strong 2 + new() { Species = 144, Level = 70, Location = 208, Crossover = new(210, 212, 214), Moves = new(821,542,427, 375), FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All_CT }, // Articuno-1 in the Crown Tundra new() { Species = 145, Level = 70, Location = 122, Crossover = new(124, 126, 128, 130), Moves = new(823,065,179,116), FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All }, // Zapdos-1 in a Wild Area new() { Species = 146, Level = 70, Location = 164, Crossover = new(166, 170, 178, 186, 188, 190, 192), Moves = new(822,542,389,417), FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All_IoA }, // Moltres-1 on the Isle of Armor @@ -603,8 +561,6 @@ internal static class Encounters8 new() { Species = 143, Level = 65, Location = 204, Weather = Normal | Intense_Sun }, // Snorlax on Slippery Slope new() { Species = 872, Level = 60, Location = 204, Weather = Normal | Heavy_Fog }, // Snom on Slippery Slope new() { Species = 832, Level = 63, Location = 204, Crossover = new(208), Weather = Normal | Intense_Sun }, // Dubwool on Slippery Slope, Frostpoint Field - new(SW ) { Species = 576, Level = 65, Location = 204, Crossover = new(208), Weather = Heavy_Fog }, // Gothitelle on Slippery Slope, Frostpoint Field - new( SH) { Species = 579, Level = 65, Location = 204, Crossover = new(208), Weather = Heavy_Fog }, // Reuniclus on Slippery Slope, Frostpoint Field new() { Species = 461, Level = 63, Location = 204, Crossover = new(208), Weather = Overcast }, // Weavile on Slippery Slope, Frostpoint Field new() { Species = 531, Level = 62, Location = 204, Crossover = new(208), Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Audino on Slippery Slope, Frostpoint Field new() { Species = 615, Level = 62, Location = 204, Crossover = new(208, 210), Weather = Icy }, // Cryogonal on Slippery Slope, Frostpoint Field, Giant’s Bed @@ -628,8 +584,6 @@ internal static class Encounters8 new() { Species = 437, Level = 65, Location = 208, Crossover = new(222), Weather = Normal | Overcast }, // Bronzong in Frostpoint Field (c), Giant’s Foot new() { Species = 029, Level = 60, Location = 210, Weather = Normal | Stormy | Intense_Sun }, // Nidoran♀ in the Giant’s Bed new() { Species = 832, Level = 63, Location = 210 }, // Dubwool in the Giant’s Bed - new(SW ) { Species = 874, Level = 63, Location = 210, Weather = All_CT }, // Stonjourner in the Giant’s Bed - new( SH) { Species = 143, Level = 65, Location = 210, Weather = All_CT }, // Snorlax in the Giant’s Bed new() { Species = 142, Level = 65, Location = 210, Weather = All_CT }, // Aerodactyl in the Giant’s Bed new() { Species = 133, Level = 60, Location = 210 }, // Eevee in the Giant’s Bed new() { Species = 470, Level = 63, Location = 210 }, // Leafeon in the Giant’s Bed @@ -666,7 +620,6 @@ internal static class Encounters8 new() { Species = 531, Level = 62, Location = 210, Crossover = new(222, 230), Weather = All_CT }, // Audino in the Giant’s Bed, Giant’s Foot new() { Species = 130, Level = 67, Location = 210, Crossover = new(230), Weather = Normal | Overcast | Stormy | Intense_Sun | Icy }, // Gyarados in the Giant’s Bed, Ballimere Lake new() { Species = 350, Level = 67, Location = 210, Crossover = new(230), Weather = Heavy_Fog }, // Milotic in the Giant’s Bed, Ballimere Lake - new( SH) { Species = 078, Level = 67, Location = 212, Form = 01, Weather = Heavy_Fog }, // Rapidash-1 in the Old Cemetery new() { Species = 872, Level = 62, Location = 214, Weather = Normal | Overcast }, // Snom on Snowslide Slope new() { Species = 698, Level = 62, Location = 214, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Amaura on Snowslide Slope new() { Species = 621, Level = 65, Location = 214, Weather = Normal | Intense_Sun }, // Druddigon on Snowslide Slope @@ -690,33 +643,21 @@ internal static class Encounters8 new() { Species = 036, Level = 65, Location = 216, Weather = Overcast }, // Clefable in the Tunnel to the Top new() { Species = 621, Level = 65, Location = 216, Weather = Overcast }, // Druddigon in the Tunnel to the Top new() { Species = 478, Level = 65, Location = 216, Weather = Overcast }, // Froslass in the Tunnel to the Top - new(SW ) { Species = 371, Level = 65, Location = 216, Weather = Overcast }, // Bagon in the Tunnel to the Top - new( SH) { Species = 443, Level = 65, Location = 216, Weather = Overcast }, // Gible in the Tunnel to the Top - new(SW ) { Species = 373, Level = 68, Location = 216, Weather = Overcast }, // Salamence in the Tunnel to the Top - new( SH) { Species = 445, Level = 68, Location = 216, Weather = Overcast }, // Garchomp in the Tunnel to the Top new() { Species = 703, Level = 65, Location = 216, Weather = Overcast }, // Carbink in the Tunnel to the Top new() { Species = 041, Level = 63, Location = 216, Crossover = new(224), Weather = Overcast }, // Zubat in the Tunnel to the Top, Roaring-Sea Caves new() { Species = 042, Level = 65, Location = 216, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Golbat in the Tunnel to the Top new() { Species = 873, Level = 65, Location = 218, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Frosmoth on the Path to the Peak - new(SW ) { Species = 373, Level = 68, Location = 218, Weather = Intense_Sun }, // Salamence on the Path to the Peak - new( SH) { Species = 445, Level = 68, Location = 218, Weather = Intense_Sun }, // Garchomp on the Path to the Peak new() { Species = 621, Level = 65, Location = 218 }, // Druddigon on the Path to the Peak new() { Species = 851, Level = 67, Location = 222, Weather = Normal | Intense_Sun }, // Centiskorch at the Giant’s Foot new() { Species = 879, Level = 67, Location = 222, Weather = Overcast | Stormy | Icy | Heavy_Fog }, // Copperajah at the Giant’s Foot new() { Species = 534, Level = 67, Location = 222 }, // Conkeldurr at the Giant’s Foot - new(SW ) { Species = 138, Level = 63, Location = 222, Weather = All_CT }, // Omanyte at the Giant’s Foot - new( SH) { Species = 140, Level = 63, Location = 222, Weather = All_CT }, // Kabuto at the Giant’s Foot new() { Species = 566, Level = 63, Location = 222, Weather = All_CT }, // Archen at the Giant’s Foot new() { Species = 126, Level = 65, Location = 222, Weather = Intense_Sun }, // Magmar at the Giant’s Foot new() { Species = 752, Level = 67, Location = 222, Crossover = new(230), Weather = Raining }, // Araquanid at Ballimere Lake, Giant’s Foot new() { Species = 125, Level = 65, Location = 222, Crossover = new(230), Weather = Thunderstorm }, // Electabuzz at the Giant’s Foot, Ballimere Lake //new() { Species = 567, Level = 67, Location = -1 }, // Archeops - new(SW ) { Species = 635, Level = 68, Location = 224, Weather = No_Sun_Sand }, // Hydreigon in Roaring-Sea Caves, weather from Frigid Sea - new( SH) { Species = 248, Level = 68, Location = 224, Weather = No_Sun_Sand }, // Tyranitar in Roaring-Sea Caves, weather from Frigid Sea new() { Species = 448, Level = 67, Location = 224, Weather = Overcast }, // Lucario in Roaring-Sea Caves new() { Species = 042, Level = 65, Location = 224, Weather = Overcast }, // Golbat in the Roaring-Sea Caves - new( SH) { Species = 141, Level = 68, Location = 224, Weather = Overcast }, // Kabutops in Roaring-Sea Caves - new(SW ) { Species = 139, Level = 68, Location = 224, Weather = Overcast }, // Omastar in Roaring-Sea Caves new() { Species = 363, Level = 63, Location = 226, Weather = No_Sun_Sand }, // Spheal at the Frigid Sea new() { Species = 364, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Sealeo at the Frigid Sea new() { Species = 564, Level = 63, Location = 226, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Tirtouga at the Frigid Sea @@ -724,7 +665,6 @@ internal static class Encounters8 new() { Species = 365, Level = 68, Location = 226, Weather = Normal | Overcast | Icy | Heavy_Fog }, // Walrein at the Frigid Sea new() { Species = 565, Level = 67, Location = 226, Weather = Normal | Stormy | Intense_Sun }, // Carracosta at the Frigid Sea new() { Species = 871, Level = 65, Location = 226, Weather = Thunderstorm }, // Pincurchin at the Frigid Sea - new( SH) { Species = 875, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Eiscue at the Frigid Sea new() { Species = 623, Level = 65, Location = 226, Crossover = new(228), Weather = All_CT }, // Golurk at the Frigid Sea (c), Three-Point Pass new() { Species = 467, Level = 68, Location = 226, Crossover = new(230), Weather = Intense_Sun }, // Magmortar at Frigid Sea (c), Ballimere Lake new() { Species = 466, Level = 68, Location = 226, Crossover = new(228, 230), Weather = Thunderstorm }, // Electivire at the Frigid Sea, Three-Point Pass, Ballimere Lake @@ -738,8 +678,6 @@ internal static class Encounters8 new() { Species = 547, Level = 65, Location = 230, Weather = Normal | Raining }, // Whimsicott at Ballimere Lake new() { Species = 836, Level = 67, Location = 230, Weather = Normal | Stormy | Snowing | Heavy_Fog }, // Boltund at Ballimere Lake new() { Species = 830, Level = 65, Location = 230, Weather = Raining | Intense_Sun }, // Eldegoss at Ballimere Lake - new(SW ) { Species = 876, Level = 65, Location = 230, Weather = Normal | Heavy_Fog }, // Indeedee at Ballimere Lake - new( SH) { Species = 876, Level = 65, Location = 230, Form = 01, Weather = Normal | Heavy_Fog }, // Indeedee-1 at Ballimere Lake new() { Species = 696, Level = 63, Location = 230, Weather = All_CT }, // Tyrunt at Ballimere Lake new() { Species = 213, Level = 65, Location = 230, Weather = Normal | Intense_Sun }, // Shuckle at Ballimere Lake new() { Species = 820, Level = 68, Location = 230, Weather = All_Ballimere }, // Greedent at Ballimere Lake @@ -764,46 +702,97 @@ internal static class Encounters8 new() { Species = 820, Level = 68, Location = 234, Weather = All_Ballimere }, // Greedent at Dyna Tree Hill }; + public static readonly EncounterStatic8[] StaticSW = + { + new(SW ) { Species = 275, Level = 34, Location = 124, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Shiftry in the Dappled Grove + new(SW ) { Species = 784, Level = 60, Location = 146, Ability = OnlyFirst, Weather = Normal | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Kommo-o in Dusty Bowl + new(SW ) { Species = 559, Level = 20, Location = 166, Weather = Overcast }, // Scraggy in the Soothing Wetlands + new(SW ) { Species = 766, Level = 26, Location = 168 }, // Passimian in the Forest of Focus + new(SW ) { Species = 127, Level = 26, Location = 168, Weather = Intense_Sun }, // Pinsir in the Forest of Focus + new(SW ) { Species = 616, Level = 20, Location = 168, Weather = Stormy }, // Shelmet in the Forest of Focus + new(SW ) { Species = 744, Level = 22, Location = 172, Crossover = new(174), Weather = Normal | Overcast }, // Rockruff on Challenge Road, Brawlers' Cave (c) + new(SW ) { Species = 560, Level = 26, Location = 172, Crossover = new(174, 180), Weather = Stormy }, // Scrafty on Challenge Road, Brawlers’ Cave (c), Training Lowlands + new(SW ) { Species = 127, Level = 26, Location = 174, Weather = Intense_Sun }, // Pinsir on Challenge Road + new(SW ) { Species = 782, Level = 22, Location = 174, Weather = Intense_Sun | Sandstorm | Heavy_Fog }, // Jangmo-o on Challenge Road + new(SW ) { Species = 628, Level = 26, Location = 174, Weather = Overcast }, // Braviary on Challenge Road + new(SW ) { Species = 127, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Pinsir in the Training Lowlands + new(SW ) { Species = 782, Level = 22, Location = 180, Weather = Sandstorm }, // Jangmo-o in Training Lowlands + new(SW ) { Species = 628, Level = 42, Location = 184, Weather = Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Braviary in the Potbottom Desert + new(SW ) { Species = 576, Level = 65, Location = 204, Crossover = new(208), Weather = Heavy_Fog }, // Gothitelle on Slippery Slope, Frostpoint Field + new(SW ) { Species = 874, Level = 63, Location = 210, Weather = All_CT }, // Stonjourner in the Giant’s Bed + new(SW ) { Species = 371, Level = 65, Location = 216, Weather = Overcast }, // Bagon in the Tunnel to the Top + new(SW ) { Species = 373, Level = 68, Location = 216, Weather = Overcast }, // Salamence in the Tunnel to the Top + new(SW ) { Species = 373, Level = 68, Location = 218, Weather = Intense_Sun }, // Salamence on the Path to the Peak + new(SW ) { Species = 138, Level = 63, Location = 222, Weather = All_CT }, // Omanyte at the Giant’s Foot + new(SW ) { Species = 635, Level = 68, Location = 224, Weather = No_Sun_Sand }, // Hydreigon in Roaring-Sea Caves, weather from Frigid Sea + new(SW ) { Species = 139, Level = 68, Location = 224, Weather = Overcast }, // Omastar in Roaring-Sea Caves + new(SW ) { Species = 876, Level = 65, Location = 230, Weather = Normal | Heavy_Fog }, // Indeedee at Ballimere Lake + }; + + public static readonly EncounterStatic8[] StaticSH = + { + new( SH) { Species = 272, Level = 34, Location = 124, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Ludicolo in the Dappled Grove + new( SH) { Species = 248, Level = 60, Location = 146, Weather = Normal | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Tyranitar in Dusty Bowl + new( SH) { Species = 453, Level = 20, Location = 166, Weather = Overcast }, // Croagunk in the Soothing Wetlands + new( SH) { Species = 765, Level = 26, Location = 168 }, // Oranguru in the Forest of Focus + new( SH) { Species = 214, Level = 26, Location = 168, Weather = Intense_Sun }, // Heracross in the Forest of Focus + new( SH) { Species = 704, Level = 20, Location = 168, Weather = Stormy }, // Goomy in the Forest of Focus + new( SH) { Species = 744, Level = 22, Location = 172, Crossover = new(174), Weather = Normal | Overcast | Intense_Sun | Heavy_Fog }, // Rockruff on Challenge Road, Brawlers' Cave (c) + new( SH) { Species = 454, Level = 26, Location = 172, Crossover = new(174, 180), Weather = Stormy }, // Toxicroak on Challenge Road, Brawlers’ Cave (c), Training Lowlands + new( SH) { Species = 214, Level = 26, Location = 174, Weather = Intense_Sun }, // Heracross on Challenge Road + new( SH) { Species = 630, Level = 26, Location = 174, Weather = Overcast }, // Mandibuzz on Challenge Road + new( SH) { Species = 214, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Heracross in the Training Lowlands + new( SH) { Species = 630, Level = 42, Location = 184, Weather = Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Mandibuzz in the Potbottom Desert + new( SH) { Species = 579, Level = 65, Location = 204, Crossover = new(208), Weather = Heavy_Fog }, // Reuniclus on Slippery Slope, Frostpoint Field + new( SH) { Species = 143, Level = 65, Location = 210, Weather = All_CT }, // Snorlax in the Giant’s Bed + new( SH) { Species = 078, Level = 67, Location = 212, Form = 01, Weather = Heavy_Fog }, // Rapidash-1 in the Old Cemetery + new( SH) { Species = 443, Level = 65, Location = 216, Weather = Overcast }, // Gible in the Tunnel to the Top + new( SH) { Species = 445, Level = 68, Location = 216, Weather = Overcast }, // Garchomp in the Tunnel to the Top + new( SH) { Species = 445, Level = 68, Location = 218, Weather = Intense_Sun }, // Garchomp on the Path to the Peak + new( SH) { Species = 140, Level = 63, Location = 222, Weather = All_CT }, // Kabuto at the Giant’s Foot + new( SH) { Species = 248, Level = 68, Location = 224, Weather = No_Sun_Sand }, // Tyranitar in Roaring-Sea Caves, weather from Frigid Sea + new( SH) { Species = 141, Level = 68, Location = 224, Weather = Overcast }, // Kabutops in Roaring-Sea Caves + new( SH) { Species = 875, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Eiscue at the Frigid Sea + new( SH) { Species = 876, Level = 65, Location = 230, Form = 01, Weather = Normal | Heavy_Fog }, // Indeedee-1 at Ballimere Lake + }; + private const string tradeSWSH = "tradeswsh"; - private static readonly string[][] TradeSWSH = Util.GetLanguageStrings10(tradeSWSH, "zh2"); + private static readonly string[][] TradeNames = Util.GetLanguageStrings10(tradeSWSH, "zh2"); private static readonly string[] TradeOT_R1 = { string.Empty, "チホコ", "Regina", "Régiona", "Regionalia", "Regine", string.Empty, "Tatiana", "지민", "易蒂", "易蒂" }; private static readonly IndividualValueSet TradeIVs = new(15, 15, 15, 15, 15, 15); - private static readonly EncounterTrade8[] TradeGift_Regular = + public static readonly EncounterTrade8[] TradeSWSH = { - new(SWSH, 052,18,08,000,04,5) { Ability = OnlySecond, TID7 = 263455, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Timid, Relearn = new(387) }, // Meowth - new(SWSH, 819,10,01,044,01,2) { Ability = OnlyFirst, TID7 = 648753, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 0, Nature = Nature.Mild }, // Skwovet - new(SWSH, 546,23,11,000,09,5) { Ability = OnlyFirst, TID7 = 101154, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 1, Nature = Nature.Modest }, // Cottonee - new(SWSH, 175,25,02,010,10,6) { Ability = OnlySecond, TID7 = 109591, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 0, Nature = Nature.Timid, Relearn = new(791) }, // Togepi - new(SW , 856,30,09,859,08,3) { Ability = OnlySecond, TID7 = 101101, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 1, Nature = Nature.Quiet }, // Hatenna - new( SH, 859,30,43,000,07,6) { Ability = OnlyFirst, TID7 = 256081, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Brave, Relearn = new(252) }, // Impidimp - new(SWSH, 562,35,16,310,15,5) { Ability = OnlyFirst, TID7 = 102534, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 1, Gender = 0, Nature = Nature.Bold, Relearn = new(261) }, // Yamask - new(SW , 538,37,17,129,20,7) { Ability = OnlySecond, TID7 = 768945, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 0, Gender = 0, Nature = Nature.Adamant }, // Throh - new( SH, 539,37,17,129,14,6) { Ability = OnlyFirst, TID7 = 881426, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 0, Gender = 0, Nature = Nature.Adamant }, // Sawk - new(SWSH, 122,40,56,000,12,4) { Ability = OnlyFirst, TID7 = 891846, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Calm }, // Mr. Mime - new(SWSH, 884,50,15,038,06,2) { Ability = OnlySecond, TID7 = 101141, IVs = TradeIVs, DynamaxLevel = 3, OTGender = 0, Gender = 0, Nature = Nature.Adamant, Relearn = new(400) }, // Duraludon + new(TradeNames, 00, SWSH, 052,18,08,000,04,5) { Ability = OnlySecond, ID32 = 263455, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Timid, Relearn = new(387) }, // Meowth + new(TradeNames, 01, SWSH, 819,10,01,044,01,2) { Ability = OnlyFirst, ID32 = 648753, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 0, Nature = Nature.Mild }, // Skwovet + new(TradeNames, 02, SWSH, 546,23,11,000,09,5) { Ability = OnlyFirst, ID32 = 101154, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 1, Nature = Nature.Modest }, // Cottonee + new(TradeNames, 03, SWSH, 175,25,02,010,10,6) { Ability = OnlySecond, ID32 = 109591, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 0, Nature = Nature.Timid, Relearn = new(791) }, // Togepi + new(TradeNames, 06, SWSH, 562,35,16,310,15,5) { Ability = OnlyFirst, ID32 = 102534, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 1, Gender = 0, Nature = Nature.Bold, Relearn = new(261) }, // Yamask + new(TradeNames, 09, SWSH, 122,40,56,000,12,4) { Ability = OnlyFirst, ID32 = 891846, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Calm }, // Mr. Mime + new(TradeNames, 10, SWSH, 884,50,15,038,06,2) { Ability = OnlySecond, ID32 = 101141, IVs = TradeIVs, DynamaxLevel = 3, OTGender = 0, Gender = 0, Nature = Nature.Adamant, Relearn = new(400) }, // Duraludon + + new(TradeOT_R1, SWSH, 052,15,01,033,04,2) { Ability = OnlyHidden, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(387) }, // Meowth + new(TradeOT_R1, SWSH, 122,15,01,005,04,2) { Ability = OnlyHidden, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(252) }, // Mr. Mime + new(TradeOT_R1, SWSH, 263,15,01,045,04,2) { Ability = OnlyHidden, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(245) }, // Zigzagoon + new(TradeOT_R1, SWSH, 618,15,01,050,05,2) { Ability = OnlyHidden, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(281) }, // Stunfisk + new(TradeOT_R1, SWSH, 110,15,01,040,12,2) { Ability = Any12H, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(220) }, // Weezing + new(TradeOT_R1, SWSH, 103,15,01,038,06,2) { Ability = Any12, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(246), Form = 1 }, // Exeggutor-1 + new(TradeOT_R1, SWSH, 105,15,01,038,06,2) { Ability = Any12, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(174), Form = 1 }, // Marowak-1 }; - private static readonly EncounterTrade8[] TradeGift_R1 = + internal static readonly EncounterTrade8[] TradeSW = { - new(SWSH, 052,15,01,033,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(387) }, // Meowth - new(SW , 083,15,01,013,10,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(098) }, // Farfetch’d - new( SH, 222,15,01,069,12,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(457) }, // Corsola - new( SH, 077,15,01,047,06,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(234) }, // Ponyta - new(SWSH, 122,15,01,005,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(252) }, // Mr. Mime - new(SW , 554,15,01,040,12,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(326) }, // Darumaka - new(SWSH, 263,15,01,045,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(245) }, // Zigzagoon - new(SWSH, 618,15,01,050,05,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(281) }, // Stunfisk - new(SWSH, 110,15,01,040,12,2, Random) { Ability = Any12H, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(220) }, // Weezing - new(SWSH, 103,15,01,038,06,2, Random) { TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(246), Form = 1 }, // Exeggutor-1 - new(SWSH, 105,15,01,038,06,2, Random) { TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new(174), Form = 1 }, // Marowak-1 + new(TradeNames, 04, SW , 856,30,09,859,08,3) { Ability = OnlySecond, ID32 = 101101, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 1, Nature = Nature.Quiet }, // Hatenna + new(TradeNames, 07, SW , 538,37,17,129,20,7) { Ability = OnlySecond, ID32 = 768945, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 0, Gender = 0, Nature = Nature.Adamant }, // Throh + new(TradeOT_R1, SW , 083,15,01,013,10,2) { Ability = OnlyHidden, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(098) }, // Farfetch’d + new(TradeOT_R1, SW , 554,15,01,040,12,2) { Ability = OnlyHidden, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(326) }, // Darumaka }; - internal static readonly EncounterTrade8[] TradeGift_SWSH = ArrayUtil.ConcatAll(TradeGift_Regular, TradeGift_R1); - - internal static readonly EncounterStatic[] StaticSW = ArrayUtil.ConcatAll(Nest_SW, Nest_SH, Dist_SW, Dist_SH, DynAdv_SWSH, Crystal_SWSH, - GetEncounters(new EncounterStatic[][] { Encounter_SWSH_0, Encounter_SWSH_Strong0, Encounter_SWSH_Strong1, Encounter_SWSH_Strong2 }, SH)); - - internal static readonly EncounterStatic[] StaticSH = ArrayUtil.ConcatAll(Nest_SW, Nest_SH, Dist_SW, Dist_SH, DynAdv_SWSH, Crystal_SWSH, - GetEncounters(new EncounterStatic[][] { Encounter_SWSH_0, Encounter_SWSH_Strong0, Encounter_SWSH_Strong1, Encounter_SWSH_Strong2 }, SW)); + internal static readonly EncounterTrade8[] TradeSH = + { + new(TradeNames, 05, SH, 859,30,43,000,07,6) { Ability = OnlyFirst, ID32 = 256081, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Brave, Relearn = new(252) }, // Impidimp + new(TradeNames, 08, SH, 539,37,17,129,14,6) { Ability = OnlyFirst, ID32 = 881426, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 0, Gender = 0, Nature = Nature.Adamant }, // Sawk + new(TradeOT_R1, SH, 222,15,01,069,12,2) { Ability = OnlyHidden, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(457) }, // Corsola + new(TradeOT_R1, SH, 077,15,01,047,06,2) { Ability = OnlyHidden, ID32 = 101141, FlawlessIVCount = 3, IVs = default, DynamaxLevel = 5, OTGender = 1, Relearn = new(234) }, // Ponyta + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8Nest.cs b/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8Nest.cs index af4169b8a..daa0fbcfb 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8Nest.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8Nest.cs @@ -269,19 +269,19 @@ internal static class Encounters8Nest internal static readonly EncounterStatic8NC[] Crystal_SWSH = { - new(SWSH) { Species = 782, Level = 16, Ability = A3, Location = 126, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 2, Moves = new(033,029,525,043) }, // ★And458 Jangmo-o - new(SWSH) { Species = 246, Level = 16, Ability = A3, Location = 126, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 2, Moves = new(033,157,371,044) }, // ★And15 Larvitar - new(SWSH) { Species = 823, Level = 50, Ability = A2, Location = 126, IVs = new(31,31,31,-1,-1,31), DynamaxLevel = 5, Moves = new(065,442,034,796), CanGigantamax = true }, // ★And337 Gigantamax Corviknight - new(SWSH) { Species = 875, Level = 15, Ability = A3, Location = 126, IVs = new(31,31,-1,31,-1,-1), DynamaxLevel = 2, Moves = new(181,311,054,556) }, // ★And603 Eiscue - new(SWSH) { Species = 874, Level = 15, Ability = A3, Location = 126, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 2, Moves = new(397,317,335,157) }, // ★And390 Stonjourner - new(SWSH) { Species = 879, Level = 35, Ability = A3, Location = 126, IVs = new(31,31,-1, 0,31,-1), DynamaxLevel = 4, Moves = new(484,174,776,583), CanGigantamax = true }, // ★Sgr6879 Gigantamax Copperajah - new(SWSH) { Species = 851, Level = 35, Ability = A2, Location = 126, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 5, Moves = new(680,679,489,438), CanGigantamax = true }, // ★Sgr6859 Gigantamax Centiskorch - new(SW ) { Species = 842, Level = 40, Ability = A0, Location = 126, IVs = new(31,-1,31,-1,31,-1), DynamaxLevel = 5, Moves = new(787,412,406,076), CanGigantamax = true }, // ★Sgr6913 Gigantamax Appletun - new( SH) { Species = 841, Level = 40, Ability = A0, Location = 126, IVs = new(31,31,-1,31,-1,-1), DynamaxLevel = 5, Moves = new(788,491,412,406), CanGigantamax = true }, // ★Sgr6913 Gigantamax Flapple - new(SWSH) { Species = 844, Level = 40, Ability = A0, Location = 126, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 5, Moves = new(523,776,489,157), CanGigantamax = true }, // ★Sgr7348 Gigantamax Sandaconda - new(SWSH) { Species = 884, Level = 40, Ability = A2, Location = 126, IVs = new(31,-1,-1,31,31,-1), DynamaxLevel = 5, Moves = new(796,063,784,319), CanGigantamax = true }, // ★Sgr7121 Gigantamax Duraludon - new(SWSH) { Species = 025, Level = 25, Ability = A2, Location = 126, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 5, Moves = new(606,273,104,085), CanGigantamax = true }, // ★Sgr6746 Gigantamax Pikachu - new(SWSH) { Species = 133, Level = 25, Ability = A2, Location = 126, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 5, Moves = new(606,273,038,129), CanGigantamax = true }, // ★Sgr7194 Gigantamax Eevee + new(SWSH) { Species = 782, Level = 16, Ability = A3, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 2, Moves = new(033,029,525,043) }, // ★And458 Jangmo-o + new(SWSH) { Species = 246, Level = 16, Ability = A3, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 2, Moves = new(033,157,371,044) }, // ★And15 Larvitar + new(SWSH) { Species = 823, Level = 50, Ability = A2, IVs = new(31,31,31,-1,-1,31), DynamaxLevel = 5, Moves = new(065,442,034,796), CanGigantamax = true }, // ★And337 Gigantamax Corviknight + new(SWSH) { Species = 875, Level = 15, Ability = A3, IVs = new(31,31,-1,31,-1,-1), DynamaxLevel = 2, Moves = new(181,311,054,556) }, // ★And603 Eiscue + new(SWSH) { Species = 874, Level = 15, Ability = A3, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 2, Moves = new(397,317,335,157) }, // ★And390 Stonjourner + new(SWSH) { Species = 879, Level = 35, Ability = A3, IVs = new(31,31,-1, 0,31,-1), DynamaxLevel = 4, Moves = new(484,174,776,583), CanGigantamax = true }, // ★Sgr6879 Gigantamax Copperajah + new(SWSH) { Species = 851, Level = 35, Ability = A2, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 5, Moves = new(680,679,489,438), CanGigantamax = true }, // ★Sgr6859 Gigantamax Centiskorch + new(SW ) { Species = 842, Level = 40, Ability = A0, IVs = new(31,-1,31,-1,31,-1), DynamaxLevel = 5, Moves = new(787,412,406,076), CanGigantamax = true }, // ★Sgr6913 Gigantamax Appletun + new( SH) { Species = 841, Level = 40, Ability = A0, IVs = new(31,31,-1,31,-1,-1), DynamaxLevel = 5, Moves = new(788,491,412,406), CanGigantamax = true }, // ★Sgr6913 Gigantamax Flapple + new(SWSH) { Species = 844, Level = 40, Ability = A0, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 5, Moves = new(523,776,489,157), CanGigantamax = true }, // ★Sgr7348 Gigantamax Sandaconda + new(SWSH) { Species = 884, Level = 40, Ability = A2, IVs = new(31,-1,-1,31,31,-1), DynamaxLevel = 5, Moves = new(796,063,784,319), CanGigantamax = true }, // ★Sgr7121 Gigantamax Duraludon + new(SWSH) { Species = 025, Level = 25, Ability = A2, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 5, Moves = new(606,273,104,085), CanGigantamax = true }, // ★Sgr6746 Gigantamax Pikachu + new(SWSH) { Species = 133, Level = 25, Ability = A2, IVs = new(31,31,31,-1,-1,-1), DynamaxLevel = 5, Moves = new(606,273,038,129), CanGigantamax = true }, // ★Sgr7194 Gigantamax Eevee }; private static EncounterStatic8N[] GetBase(string name, GameVersion game) diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8a.cs b/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8a.cs index f38a8a0f3..adef86e49 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8a.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8a.cs @@ -1,13 +1,12 @@ using static PKHeX.Core.EncounterUtil; using static PKHeX.Core.Shiny; -using static PKHeX.Core.GameVersion; using static PKHeX.Core.EncounterStatic8aCorrelation; namespace PKHeX.Core; internal static class Encounters8a { - internal static readonly EncounterArea8a[] SlotsLA = EncounterArea8a.GetAreas(Get("la", "la"), PLA); + internal static readonly EncounterArea8a[] SlotsLA = EncounterArea8a.GetAreas(Get("la", "la")); private const byte M = 127; // Middle Height/Weight private const byte A = 255; // Max Height/Weight for Alphas @@ -16,13 +15,13 @@ internal static class Encounters8a internal static readonly EncounterStatic8a[] StaticLA = { // Gifts - new(722,000,05,M,M) { Location = 006, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke }, // Rowlet - new(155,000,05,M,M) { Location = 006, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke }, // Cyndaquil - new(501,000,05,M,M) { Location = 006, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke }, // Oshawott - new(037,001,40,M,M) { Location = 088, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke }, // Vulpix-1 - new(483,000,65,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Method = Fixed, Ball = (int)Ball.LAOrigin }, // Dialga - new(484,000,65,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Method = Fixed, Ball = (int)Ball.LAOrigin }, // Palkia - new(493,000,75,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke, FatefulEncounter = true }, // Arceus + new(722,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed, }, // Rowlet + new(155,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed, }, // Cyndaquil + new(501,000,05,M,M) { Location = 006, FixedBall = Ball.LAPoke, Method = Fixed, }, // Oshawott + new(037,001,40,M,M) { Location = 088, FixedBall = Ball.LAPoke, Method = Fixed, }, // Vulpix-1 + new(483,000,65,M,M) { Location = 109, FlawlessIVCount = 3, FixedBall = Ball.LAOrigin, Method = Fixed, }, // Dialga + new(484,000,65,M,M) { Location = 109, FlawlessIVCount = 3, FixedBall = Ball.LAOrigin, Method = Fixed, }, // Palkia + new(493,000,75,M,M) { Location = 109, FlawlessIVCount = 3, FixedBall = Ball.LAPoke, Method = Fixed, FatefulEncounter = true }, // Arceus // Static Encounters - Scripted Table Slots new(480,000,70,M,M) { Location = 111, FlawlessIVCount = 3, Moves = new(129,326,832,095) }, // Uxie @@ -38,14 +37,9 @@ internal static class Encounters8a new(077,000,15 ) { Location = 014, Shiny = Always}, // Ponyta* new(442,000,60,M,M) { Location = 043, FlawlessIVCount = 3 }, // Spiritomb - new(570,001,27 ) { Location = 027 }, // Zorua - new(570,001,28 ) { Location = 027 }, // Zorua - new(570,001,29 ) { Location = 027 }, // Zorua + new(570,001,27 ) { Location = 027, LevelMax = 29 }, // Zorua - new(489,000,33 ) { Location = 064, FatefulEncounter = true, Moves = new(145,352,151,428) }, // Phione - new(489,000,34 ) { Location = 064, FatefulEncounter = true, Moves = new(145,352,151,428) }, // Phione - new(489,000,35 ) { Location = 064, FatefulEncounter = true, Moves = new(145,352,151,428) }, // Phione - new(489,000,36 ) { Location = 064, FatefulEncounter = true, Moves = new(145,352,151,428) }, // Phione + new(489,000,33 ) { Location = 064, FatefulEncounter = true, LevelMax = 36, Moves = new(145,352,151,428) }, // Phione new(490,000,50,M,M) { Location = 064, FlawlessIVCount = 3, FatefulEncounter = true, Moves = new(352,428,585,145) }, // Manaphy new(491,000,70,M,M) { Location = 010, FlawlessIVCount = 3, FatefulEncounter = true, Moves = new(506,399,094,464) }, // Darkrai new(492,000,70,M,M) { Location = 026, FlawlessIVCount = 3, FatefulEncounter = true, Moves = new(403,412,414,465) }, // Shaymin diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8b.cs b/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8b.cs index c05d49d05..a06644e7d 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8b.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen8/Encounters8b.cs @@ -15,30 +15,28 @@ internal static class Encounters8b internal static readonly EncounterArea8b[] SlotsBD = ArrayUtil.ConcatAll(SlotsBD_OW, SlotsBD_UG); internal static readonly EncounterArea8b[] SlotsSP = ArrayUtil.ConcatAll(SlotsSP_OW, SlotsSP_UG); - static Encounters8b() => MarkEncounterTradeStrings(TradeGift_BDSP, TradeBDSP); - - private static readonly EncounterStatic8b[] Encounter_BDSP = + internal static readonly EncounterStatic8b[] Encounter_BDSP = { // Gifts - new(BDSP) { Gift = true, Species = 387, Level = 05, Location = 323 }, // Turtwig - new(BDSP) { Gift = true, Species = 390, Level = 05, Location = 323 }, // Chimchar - new(BDSP) { Gift = true, Species = 393, Level = 05, Location = 323 }, // Piplup - new(BDSP) { Gift = true, Species = 133, Level = 05, Location = 104 }, // Eevee - new(BDSP) { Gift = true, Species = 440, Level = 01, EggLocation = 60007, EggCycles = 40 }, // Happiny Egg from Traveling Man - new(BDSP) { Gift = true, Species = 447, Level = 01, EggLocation = 60005, EggCycles = 25 }, // Riolu Egg from Riley + new(BDSP) { FixedBall = Ball.Poke, Species = 387, Level = 05, Location = 323 }, // Turtwig + new(BDSP) { FixedBall = Ball.Poke, Species = 390, Level = 05, Location = 323 }, // Chimchar + new(BDSP) { FixedBall = Ball.Poke, Species = 393, Level = 05, Location = 323 }, // Piplup + new(BDSP) { FixedBall = Ball.Poke, Species = 133, Level = 05, Location = 104 }, // Eevee + new(BDSP) { FixedBall = Ball.Poke, Species = 440, Level = 01, EggLocation = 60007, Location = Locations.Default8bNone }, // Happiny Egg from Traveling Man + new(BDSP) { FixedBall = Ball.Poke, Species = 447, Level = 01, EggLocation = 60005, Location = Locations.Default8bNone }, // Riolu Egg from Riley // Fossils - new(BDSP) { Gift = true, Species = 138, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Omanyte - new(BDSP) { Gift = true, Species = 140, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Kabuto - new(BDSP) { Gift = true, Species = 142, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Aerodactyl - new(BDSP) { Gift = true, Species = 345, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Lileep - new(BDSP) { Gift = true, Species = 347, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Anorith - new(BDSP) { Gift = true, Species = 408, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Cranidos - new(BDSP) { Gift = true, Species = 410, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Shieldon + new(BDSP) { FixedBall = Ball.Poke, Species = 138, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Omanyte + new(BDSP) { FixedBall = Ball.Poke, Species = 140, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Kabuto + new(BDSP) { FixedBall = Ball.Poke, Species = 142, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Aerodactyl + new(BDSP) { FixedBall = Ball.Poke, Species = 345, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Lileep + new(BDSP) { FixedBall = Ball.Poke, Species = 347, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Anorith + new(BDSP) { FixedBall = Ball.Poke, Species = 408, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Cranidos + new(BDSP) { FixedBall = Ball.Poke, Species = 410, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Shieldon // Game-specific gifts - new(BDSP) { Gift = true, Species = 151, Level = 01, Ability = OnlySecond, Location = 438, Shiny = Never, FlawlessIVCount = 3, FatefulEncounter = true }, // Mew - new(BDSP) { Gift = true, Species = 385, Level = 05, Ability = OnlySecond, Location = 438, Shiny = Never, FlawlessIVCount = 3, FatefulEncounter = true }, // Jirachi + new(BDSP) { FixedBall = Ball.Poke, Species = 151, Level = 01, Ability = OnlySecond, Location = 438, Shiny = Never, FlawlessIVCount = 3, FatefulEncounter = true }, // Mew + new(BDSP) { FixedBall = Ball.Poke, Species = 385, Level = 05, Ability = OnlySecond, Location = 438, Shiny = Never, FlawlessIVCount = 3, FatefulEncounter = true }, // Jirachi // Stationary new(BDSP) { Species = 425, Level = 22, Location = 197, FlawlessIVCount = 3 }, // Drifloon @@ -46,14 +44,12 @@ internal static class Encounters8b new(BDSP) { Species = 479, Level = 15, Location = 311 }, // Rotom // Roamers - new(BDSP) { Species = 481, Level = 50, FlawlessIVCount = 3, Roaming = true }, // Mesprit - new(BDSP) { Species = 488, Level = 50, FlawlessIVCount = 3, Roaming = true }, // Cresselia + new(BDSP) { Species = 481, Level = 50, Location = 197, FlawlessIVCount = 3, Roaming = true }, // Mesprit + new(BDSP) { Species = 488, Level = 50, Location = 197, FlawlessIVCount = 3, Roaming = true }, // Cresselia // Legendary new(BDSP) { Species = 480, Level = 50, Location = 331, FlawlessIVCount = 3 }, // Uxie new(BDSP) { Species = 482, Level = 50, Location = 328, FlawlessIVCount = 3 }, // Azelf - new(BD ) { Species = 483, Level = 47, Location = 216, FlawlessIVCount = 3 }, // Dialga - new( SP) { Species = 484, Level = 47, Location = 217, FlawlessIVCount = 3 }, // Palkia new(BDSP) { Species = 485, Level = 70, Location = 262, FlawlessIVCount = 3 }, // Heatran new(BDSP) { Species = 486, Level = 70, Location = 291, FlawlessIVCount = 3 }, // Regigigas new(BDSP) { Species = 487, Level = 70, Location = 266, FlawlessIVCount = 3 }, // Giratina @@ -61,16 +57,8 @@ internal static class Encounters8b // Mythical new(BDSP) { Species = 491, Level = 50, Location = 333, FlawlessIVCount = 3, FatefulEncounter = true }, // Darkrai new(BDSP) { Species = 492, Level = 30, Location = 285, FlawlessIVCount = 3, FatefulEncounter = true }, // Shaymin - new(BD ) { Species = 493, Level = 80, Location = 218, FlawlessIVCount = 3, FatefulEncounter = true }, // Arceus (Brilliant Diamond) - new( SP) { Species = 493, Level = 80, Location = 618, FlawlessIVCount = 3, FatefulEncounter = true }, // Arceus (Shining Pearl) // Ramanas Park (Pure Space) - new( SP) { Species = 144, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Articuno - new( SP) { Species = 145, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Zapdos - new( SP) { Species = 146, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Moltres - new(BD ) { Species = 243, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Raikou - new(BD ) { Species = 244, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Entei - new(BD ) { Species = 245, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Suicune new(BDSP) { Species = 377, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Regirock new(BDSP) { Species = 378, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Regice new(BDSP) { Species = 379, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Registeel @@ -79,24 +67,39 @@ internal static class Encounters8b // Ramanas Park (Strange Space) new(BDSP) { Species = 150, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Mewtwo - new( SP) { Species = 249, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Lugia - new(BD ) { Species = 250, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Ho-Oh new(BDSP) { Species = 382, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Kyogre new(BDSP) { Species = 383, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Groudon new(BDSP) { Species = 384, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Rayquaza }; - internal static readonly EncounterStatic8b[] StaticBD = GetEncounters(Encounter_BDSP, BD); - internal static readonly EncounterStatic8b[] StaticSP = GetEncounters(Encounter_BDSP, SP); + internal static readonly EncounterStatic8b[] StaticBD = + { + new(BD ) { Species = 483, Level = 47, Location = 216, FlawlessIVCount = 3 }, // Dialga + new(BD ) { Species = 493, Level = 80, Location = 218, FlawlessIVCount = 3, FatefulEncounter = true }, // Arceus (Brilliant Diamond) + new(BD ) { Species = 243, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Raikou + new(BD ) { Species = 244, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Entei + new(BD ) { Species = 245, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Suicune + new(BD ) { Species = 250, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Ho-Oh + }; + + internal static readonly EncounterStatic8b[] StaticSP = + { + new( SP) { Species = 484, Level = 47, Location = 217, FlawlessIVCount = 3 }, // Palkia + new( SP) { Species = 493, Level = 80, Location = 618, FlawlessIVCount = 3, FatefulEncounter = true }, // Arceus (Shining Pearl) + new( SP) { Species = 144, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Articuno + new( SP) { Species = 145, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Zapdos + new( SP) { Species = 146, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Moltres + new( SP) { Species = 249, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Lugia + }; private const string tradeBDSP = "tradebdsp"; - private static readonly string[][] TradeBDSP = Util.GetLanguageStrings10(tradeBDSP, "zh2"); + private static readonly string[][] TradeNames = Util.GetLanguageStrings10(tradeBDSP, "zh2"); internal static readonly EncounterTrade8b[] TradeGift_BDSP = { - new(BDSP) { Species = 063, EncryptionConstant = 0x0000008E, PID = 0xFF50A8F5, Level = 09, Ability = OnlyFirst, Gender = 0, OTGender = 0, TID16 = 25643, IVs = new(28,10,09,31,11,03), Moves = new(100,000,000,000), HeightScalar = 029, WeightScalar = 202, Nature = Nature.Quiet }, // Abra - new(BDSP) { Species = 441, EncryptionConstant = 0x00000867, PID = 0x17DAAB19, Level = 15, Ability = OnlySecond, Gender = 1, OTGender = 0, TID16 = 44142, IVs = new(17,08,29,25,17,23), Moves = new(448,047,064,045), HeightScalar = 088, WeightScalar = 091, Nature = Nature.Lonely }, // Chatot - new(BDSP) { Species = 093, EncryptionConstant = 0x00000088, PID = 0xF60AB5BB, Level = 33, Ability = OnlyFirst, Gender = 0, OTGender = 0, TID16 = 19248, IVs = new(18,24,28,02,22,30), Moves = new(247,371,389,109), HeightScalar = 096, WeightScalar = 208, Nature = Nature.Hasty }, // Haunter - new(BDSP) { Species = 129, EncryptionConstant = 0x0000045C, PID = 0xFCE82F88, Level = 45, Ability = OnlyFirst, Gender = 1, OTGender = 0, TID16 = 53277, IVs = new(03,03,31,02,11,03), Moves = new(150,000,000,000), HeightScalar = 169, WeightScalar = 068, Nature = Nature.Mild }, // Magikarp + new(TradeNames, 00, BDSP) { Species = 063, EncryptionConstant = 0x0000008E, PID = 0xFF50A8F5, Level = 09, Ability = OnlyFirst, Gender = 0, OTGender = 0, ID32 = 25643, IVs = new(28,10,09,31,11,03), Moves = new(100,000,000,000), HeightScalar = 029, WeightScalar = 202, Nature = Nature.Quiet }, // Abra + new(TradeNames, 01, BDSP) { Species = 441, EncryptionConstant = 0x00000867, PID = 0x17DAAB19, Level = 15, Ability = OnlySecond, Gender = 1, OTGender = 0, ID32 = 44142, IVs = new(17,08,29,25,17,23), Moves = new(448,047,064,045), HeightScalar = 088, WeightScalar = 091, Nature = Nature.Lonely }, // Chatot + new(TradeNames, 02, BDSP) { Species = 093, EncryptionConstant = 0x00000088, PID = 0xF60AB5BB, Level = 33, Ability = OnlyFirst, Gender = 0, OTGender = 0, ID32 = 19248, IVs = new(18,24,28,02,22,30), Moves = new(247,371,389,109), HeightScalar = 096, WeightScalar = 208, Nature = Nature.Hasty }, // Haunter + new(TradeNames, 03, BDSP) { Species = 129, EncryptionConstant = 0x0000045C, PID = 0xFCE82F88, Level = 45, Ability = OnlyFirst, Gender = 1, OTGender = 0, ID32 = 53277, IVs = new(03,03,31,02,11,03), Moves = new(150,000,000,000), HeightScalar = 169, WeightScalar = 068, Nature = Nature.Mild }, // Magikarp }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters9.cs b/PKHeX.Core/Legality/Encounters/Data/Gen9/Encounters9.cs similarity index 52% rename from PKHeX.Core/Legality/Encounters/Data/Encounters9.cs rename to PKHeX.Core/Legality/Encounters/Data/Gen9/Encounters9.cs index e359e2cb9..d0bd833d4 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters9.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen9/Encounters9.cs @@ -12,32 +12,15 @@ internal static class Encounters9 { internal static readonly EncounterArea9[] Slots = EncounterArea9.GetAreas(Get("wild_paldea", "sv"), SV); - static Encounters9() - { - MarkEncounterTradeStrings(TradeGift_SV, TradeSV); - } - - private static readonly EncounterStatic9[] Encounter_SV = + internal static readonly EncounterStatic9[] Encounter_SV = { // Starters - new(SV) { Gift = true, Species = 906, Shiny = Never, Level = 05, Location = 080, Ability = OnlyFirst, Size = 128 }, // Sprigatito - new(SV) { Gift = true, Species = 909, Shiny = Never, Level = 05, Location = 080, Ability = OnlyFirst, Size = 128 }, // Fuecoco - new(SV) { Gift = true, Species = 912, Shiny = Never, Level = 05, Location = 080, Ability = OnlyFirst, Size = 128 }, // Quaxly - - // Galarian Meowth from Salvatore (Specific Met Location depending on game, inside Academy's Staff Quarters) - new(SL) { Gift = true, Species = 052, Shiny = Never, Level = 05, Location = 130, Form = 2, FlawlessIVCount = 3 }, // Meowth-2 - new(VL) { Gift = true, Species = 052, Shiny = Never, Level = 05, Location = 131, Form = 2, FlawlessIVCount = 3 }, // Meowth-2 + new(SV) { FixedBall = Ball.Poke, Species = 906, Shiny = Never, Level = 05, Location = 080, Ability = OnlyFirst, Size = 128 }, // Sprigatito + new(SV) { FixedBall = Ball.Poke, Species = 909, Shiny = Never, Level = 05, Location = 080, Ability = OnlyFirst, Size = 128 }, // Fuecoco + new(SV) { FixedBall = Ball.Poke, Species = 912, Shiny = Never, Level = 05, Location = 080, Ability = OnlyFirst, Size = 128 }, // Quaxly // Scripted - new (SV) { Gift = true, Species = 734, Level = 02, Location = 064 }, // Yungoos level 2, no Marks in Inlet Grotto. Only Poké Ball available in early game. - - // Box Legendary (Battle Form) - new(SL) { Species = 1007, Shiny = Never, Level = 72, Location = 124, Size = 128, Ability = OnlyFirst, Nature = Nature.Adamant, TeraType = GemType.Fighting, IVs = new(25,31,25,31,31,25), Moves = new(416,339,878,053) }, // Koraidon - new(VL) { Species = 1008, Shiny = Never, Level = 72, Location = 124, Size = 128, Ability = OnlyFirst, Nature = Nature.Modest, TeraType = GemType.Electric, IVs = new(25,31,25,31,31,25), Moves = new(063,268,879,408) }, // Miraidon - - // Box Legendary (Ride Form) - new(SL) { Gift = true, Species = 1007, Shiny = Never, Level = 68, Location = 070, Ability = OnlyFirst, Size = 128, Nature = Nature.Quirky, TeraType = GemType.Dragon, IVs = new(31,31,28,31,31,28), Moves = new(053,878,203,851) }, // Koraidon - new(VL) { Gift = true, Species = 1008, Shiny = Never, Level = 68, Location = 070, Ability = OnlyFirst, Size = 128, Nature = Nature.Quirky, TeraType = GemType.Dragon, IVs = new(31,31,28,31,31,28), Moves = new(408,879,203,851) }, // Miraidon + new (SV) { FixedBall = Ball.Poke, Species = 734, Level = 02, Location = 064 }, // Yungoos level 2, no Marks in Inlet Grotto. Only Poké Ball available in early game. // Treasures of Ruin new(SV) { Species = 1001, Shiny = Never, Level = 60, Location = 006, Size = 128, FlawlessIVCount = 3 }, // Wo-Chien @@ -80,20 +63,56 @@ internal static class Encounters9 #endregion }; + internal static readonly EncounterStatic9[] StaticSL = + { + // Galarian Meowth from Salvatore (Specific Met Location depending on game, inside Academy's Staff Quarters) + new(SL) { FixedBall = Ball.Poke, Species = 052, Shiny = Never, Level = 05, Location = 130, Form = 2, FlawlessIVCount = 3 }, // Meowth-2 + new(VL) { FixedBall = Ball.Poke, Species = 052, Shiny = Never, Level = 05, Location = 131, Form = 2, FlawlessIVCount = 3 }, // Meowth-2 + + // Box Legendary (Battle Form) + new(SL) { Species = 1007, Shiny = Never, Level = 72, Location = 124, Size = 128, Ability = OnlyFirst, Nature = Nature.Adamant, TeraType = GemType.Fighting, IVs = new(25,31,25,31,31,25), Moves = new(416,339,878,053) }, // Koraidon + new(VL) { Species = 1008, Shiny = Never, Level = 72, Location = 124, Size = 128, Ability = OnlyFirst, Nature = Nature.Modest, TeraType = GemType.Electric, IVs = new(25,31,25,31,31,25), Moves = new(063,268,879,408) }, // Miraidon + + // Box Legendary (Ride Form) + new(SL) { FixedBall = Ball.Poke, Species = 1007, Shiny = Never, Level = 68, Location = 070, Ability = OnlyFirst, Size = 128, Nature = Nature.Quirky, TeraType = GemType.Dragon, IVs = new(31,31,28,31,31,28), Moves = new(053,878,203,851) }, // Koraidon + new(VL) { FixedBall = Ball.Poke, Species = 1008, Shiny = Never, Level = 68, Location = 070, Ability = OnlyFirst, Size = 128, Nature = Nature.Quirky, TeraType = GemType.Dragon, IVs = new(31,31,28,31,31,28), Moves = new(408,879,203,851) }, // Miraidon + + // Former Titans + new(SV) { Species = 950, Shiny = Never, Level = 16, Location = 020, Size = 255, Ability = OnlyFirst, Gender = 1, Nature = Nature.Gentle, IVs = new(30,30,30,30,30,30), Moves = new(011,249,335,317), IsTitan = true }, // Klawf + new(SV) { Species = 962, Shiny = Never, Level = 20, Location = 022, Size = 255, Ability = OnlyHidden, Gender = 1, Nature = Nature.Jolly, IVs = new(30,30,30,30,30,30), Moves = new(088,017,365,259), IsTitan = true }, // Bombirdier + new(SV) { Species = 968, Shiny = Never, Level = 29, Location = 032, Size = 255, Ability = OnlyFirst, Gender = 0, Nature = Nature.Quirky, IVs = new(30,30,30,30,30,30), Moves = new(231,029,035,201), IsTitan = true }, // Orthworm + new(SL) { Species = 984, Shiny = Never, Level = 45, Location = 024, Size = 255, Ability = OnlyFirst, Nature = Nature.Naughty, IVs = new(30,30,30,30,30,30), Moves = new(229,280,282,707), IsTitan = true }, // Great Tusk + new(VL) { Species = 990, Shiny = Never, Level = 45, Location = 024, Size = 255, Ability = OnlyFirst, Nature = Nature.Naughty, IVs = new(30,30,30,30,30,30), Moves = new(229,442,282,707), IsTitan = true }, // Iron Treads + new(SV) { Species = 978, Shiny = Never, Level = 57, Location = 040, Size = 255, Ability = OnlyFirst, Gender = 0, Nature = Nature.Quiet, IVs = new(30,30,30,30,30,30), Moves = new(330,196,269,406), IsTitan = true }, // Tatsugiri + }; + + internal static readonly EncounterStatic9[] StaticVL = + { + // Galarian Meowth from Salvatore (Specific Met Location depending on game, inside Academy's Staff Quarters) + new(VL) { FixedBall = Ball.Poke, Species = 052, Shiny = Never, Level = 05, Location = 131, Form = 2, FlawlessIVCount = 3 }, // Meowth-2 + + // Box Legendary (Battle Form) + new(VL) { Species = 1008, Shiny = Never, Level = 72, Location = 124, Size = 128, Ability = OnlyFirst, Nature = Nature.Modest, TeraType = GemType.Electric, IVs = new(25,31,25,31,31,25), Moves = new(063,268,879,408) }, // Miraidon + + // Box Legendary (Ride Form) + new(VL) { FixedBall = Ball.Poke, Species = 1008, Shiny = Never, Level = 68, Location = 070, Ability = OnlyFirst, Size = 128, Nature = Nature.Quirky, TeraType = GemType.Dragon, IVs = new(31,31,28,31,31,28), Moves = new(408,879,203,851) }, // Miraidon + + // Former Titans + new(VL) { Species = 990, Shiny = Never, Level = 45, Location = 024, Size = 255, Ability = OnlyFirst, Nature = Nature.Naughty, IVs = new(30,30,30,30,30,30), Moves = new(229,442,282,707), IsTitan = true }, // Iron Treads + }; + private const string tradeSV = "tradesv"; - private static readonly string[][] TradeSV = Util.GetLanguageStrings10(tradeSV, "zh2"); + private static readonly string[][] TradeNames = Util.GetLanguageStrings10(tradeSV, "zh2"); internal static readonly EncounterTrade9[] TradeGift_SV = { - new(SV, 872,10) { TID7 = 050724, IVs = new(31,18,13,20,28,26), OTGender = 0, Gender = 1, Nature = Nature.Bashful }, // Snom - new(SV, 194,18) { Ability = OnlySecond, TID7 = 033081, IVs = new(27,18,25,13,16,31), OTGender = 1, Gender = 0, Nature = Nature.Relaxed }, // Wooper - new(SV, 093,25) { Ability = OnlyFirst, TID7 = 016519, IVs = new(14,20,25,31,28,16), OTGender = 1, Gender = 1, Nature = Nature.Lonely, EvolveOnTrade = true }, // Haunter + new(TradeNames, 00, SV, 872,10) { Ability = Any12, ID32 = 050724, IVs = new(31,18,13,20,28,26), OTGender = 0, Gender = 1, Nature = Nature.Bashful }, // Snom + new(TradeNames, 01, SV, 194,18) { Ability = OnlySecond, ID32 = 033081, IVs = new(27,18,25,13,16,31), OTGender = 1, Gender = 0, Nature = Nature.Relaxed }, // Wooper + new(TradeNames, 02, SV, 093,25) { Ability = OnlyFirst, ID32 = 016519, IVs = new(14,20,25,31,28,16), OTGender = 1, Gender = 1, Nature = Nature.Lonely, EvolveOnTrade = true }, // Haunter }; - private static readonly EncounterTera9[] Tera = EncounterTera9.GetArray(Get("gem_paldea")); - private static readonly EncounterDist9[] Dist = EncounterDist9.GetArray(Get("dist_paldea")); - private static readonly EncounterMight9[] Might = EncounterMight9.GetArray(Get("might_paldea")); - private static readonly EncounterFixed9[] Fixed = EncounterFixed9.GetArray(Get("fixed_paldea")); - internal static readonly EncounterStatic[] StaticSL = ArrayUtil.ConcatAll(GetEncounters(Encounter_SV, SL), Tera, Dist, Might, Fixed); - internal static readonly EncounterStatic[] StaticVL = ArrayUtil.ConcatAll(GetEncounters(Encounter_SV, VL), Tera, Dist, Might, Fixed); + internal static readonly EncounterTera9[] Tera = EncounterTera9.GetArray(Get("gem_paldea")); + internal static readonly EncounterDist9[] Dist = EncounterDist9.GetArray(Get("dist_paldea")); + internal static readonly EncounterMight9[] Might = EncounterMight9.GetArray(Get("might_paldea")); + internal static readonly EncounterFixed9[] Fixed = EncounterFixed9.GetArray(Get("fixed_paldea")); } diff --git a/PKHeX.Core/Legality/Encounters/EncounterMatchRating.cs b/PKHeX.Core/Legality/Encounters/EncounterMatchRating.cs deleted file mode 100644 index f4ee3cfbb..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterMatchRating.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Enumerates encounter match quality. -/// -public enum EncounterMatchRating -{ - /// Matches all data, no other matches will be better. - Match, - - /// Matches most data, might have a better match later. - Deferred, - - /// Matches most data, might have a better match later. Less preferred than due to small errors in secondary data. - DeferredErrors, - - /// Matches some data, but will likely have a better match later. - PartialMatch, - - /// Unused -- only used as an initial "max" value that anything else will be more suitable of a match. - MaxNotMatch, -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs deleted file mode 100644 index 4d47a26ce..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using static PKHeX.Core.Species; - -namespace PKHeX.Core; - -/// -/// Wild Encounter Slot data -/// -/// Wild encounter slots are found as random encounters in-game. -public abstract record EncounterSlot(EncounterArea Area, ushort Species, byte Form, byte LevelMin, byte LevelMax) : IEncounterable, IEncounterMatch, IEncounterFormRandom -{ - public abstract int Generation { get; } - public abstract EntityContext Context { get; } - public bool EggEncounter => false; - public virtual bool IsShiny => false; - - protected readonly EncounterArea Area = Area; - public GameVersion Version => Area.Version; - public int Location => Area.Location; - public int EggLocation => 0; - public virtual Ball FixedBall => Ball.None; - public virtual Shiny Shiny => Shiny.Random; - - public bool IsFixedLevel => LevelMin == LevelMax; - public bool IsRandomLevel => LevelMin != LevelMax; - - private protected const string wild = "Wild Encounter"; - public string Name => wild; - - /// - /// Gets if the specified level inputs are within range of the and - /// - /// Single level - /// True if within slot's range, false if impossible. - public bool IsLevelWithinRange(int lvl) => LevelMin <= lvl && lvl <= LevelMax; - - /// - /// Gets if the specified level inputs are within range of the and - /// - /// Highest value the low end of levels can be - /// Lowest value the high end of levels can be - /// True if within slot's range, false if impossible. - public bool IsLevelWithinRange(byte min, byte max) => LevelMin <= max && min <= LevelMax; - - /// - /// Gets if the specified level inputs are within range of the and - /// - /// Single level - /// Highest value the low end of levels can be - /// Lowest value the high end of levels can be - /// True if within slot's range, false if impossible. - public bool IsLevelWithinRange(int lvl, int minDecrease, int maxIncrease) => LevelMin - minDecrease <= lvl && lvl <= LevelMax + maxIncrease; - - /// - /// Gets if the specified level inputs are within range of the and - /// - /// Lowest level allowed - /// Highest level allowed - /// Highest value the low end of levels can be - /// Lowest value the high end of levels can be - /// True if within slot's range, false if impossible. - public bool IsLevelWithinRange(byte min, byte max, int minDecrease, int maxIncrease) => LevelMin - minDecrease <= max && min <= LevelMax + maxIncrease; - - public virtual string LongName - { - get - { - if (Area.Type == SlotType.Any) - return wild; - return $"{wild} {Area.Type.ToString().Replace('_', ' ')}"; - } - } - - public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); - - public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) - { - var pk = GetBlank(); - tr.ApplyTo(pk); - ApplyDetails(tr, criteria, pk); - return pk; - } - - protected virtual PKM GetBlank() => EntityBlank.GetBlank(Generation, Version); - - protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - var version = this.GetCompatibleVersion((GameVersion) sav.Game); - int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID) sav.Language, version); - int level = LevelMin; - pk.Species = Species; - pk.Form = GetWildForm(pk, Form, sav); - pk.Language = lang; - pk.CurrentLevel = level; - pk.Version = (int)version; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation); - - ApplyDetailsBall(pk); - pk.Language = lang; - pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - - SetMetData(pk, level, Location); - SetPINGA(pk, criteria); - SetEncounterMoves(pk, version, pk.CurrentLevel); - - SetFormatSpecificData(pk); - - if (pk.Format < 6) - return; - - sav.ApplyHandlingTrainerInfo(pk); - if (pk is IScaledSize { HeightScalar: 0, WeightScalar: 0 } s) - { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - } - - protected virtual void ApplyDetailsBall(PKM pk) - { - var ball = FixedBall; - pk.Ball = (int)(ball == Ball.None ? Ball.Poke : ball); - } - - protected virtual void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - Span moves = stackalloc ushort[4]; - var source = GameData.GetLearnSource(version); - source.SetEncounterMoves(Species, Form, level, moves); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } - - protected virtual void SetFormatSpecificData(PKM pk) { } - - protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - var ability = criteria.GetAbilityFromNumber(Ability); - - if (Generation == 3 && Species == (int)Unown) - { - do - { - PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender); - ability ^= 1; // some nature-forms cannot have a certain PID-ability set, so just flip it as Unown doesn't have dual abilities. - } while (pk.Form != Form); - } - else - { - PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender); - } - - pk.Gender = gender; - pk.StatNature = nature; - } - - private void SetMetData(PKM pk, int level, int location) - { - if (pk.Format <= 2 && Version != GameVersion.C) - return; - - pk.Met_Location = location; - pk.Met_Level = level; - - if (pk.Format >= 4) - pk.MetDate = DateOnly.FromDateTime(DateTime.Today); - } - - public bool IsRandomUnspecificForm => Form >= FormDynamic; - private const int FormDynamic = FormVivillon; - protected internal const byte FormVivillon = 30; - protected internal const byte FormRandom = 31; - - private static byte GetWildForm(PKM pk, byte form, ITrainerInfo sav) - { - if (form < FormDynamic) // specified form - return form; - - if (form == FormRandom) // flagged as totally random - { - if (pk.Species == (int)Minior) - return (byte)Util.Rand.Next(7, 14); - return (byte)Util.Rand.Next(pk.PersonalInfo.FormCount); - } - - ushort species = pk.Species; - if (species is >= (int)Scatterbug and <= (int)Vivillon) - { - if (sav is IRegionOrigin o) - return Vivillon3DS.GetPattern(o.Country, o.Region); - } - return 0; - } - - public virtual string GetConditionString(out bool valid) - { - valid = true; - return LegalityCheckStrings.LEncCondition; - } - - public bool IsMatchExact(PKM pk, EvoCriteria evo) => true; // Matched by Area - - public virtual EncounterMatchRating GetMatchRating(PKM pk) - { - if (IsDeferredWurmple(pk)) - return EncounterMatchRating.PartialMatch; - - if (pk.Format >= 5) - { - bool isHidden = pk.AbilityNumber == 4; - if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) - return EncounterMatchRating.PartialMatch; - if (IsDeferredHiddenAbility(isHidden)) - return EncounterMatchRating.Deferred; - } - - return EncounterMatchRating.Match; - } - - protected virtual HiddenAbilityPermission IsHiddenAbilitySlot() => HiddenAbilityPermission.Never; - - public AbilityPermission Ability => IsHiddenAbilitySlot() switch - { - HiddenAbilityPermission.Never => AbilityPermission.Any12, - HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden, - _ => AbilityPermission.Any12H, - }; - - private bool IsDeferredWurmple(PKM pk) => Species == (int)Wurmple && pk.Species != (int)Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk); - - private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch - { - HiddenAbilityPermission.Never => IsHidden, - HiddenAbilityPermission.Always => !IsHidden, - _ => false, - }; - - protected enum HiddenAbilityPermission - { - Always, - Never, - Possible, - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs deleted file mode 100644 index a6a5af828..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot1 : EncounterSlot, INumberedSlot -{ - public override int Generation => 1; - public override EntityContext Context => EntityContext.Gen1; - public byte SlotNumber { get; } - public override Ball FixedBall => Ball.Poke; - - public EncounterSlot1(EncounterArea1 area, byte species, byte min, byte max, byte slot) : base(area, species, 0, min, max) - { - SlotNumber = slot; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - - var pk1 = (PK1)pk; - if (Version == GameVersion.YW) - { - // Since we don't keep track of Yellow's Personal Data, just handle any differences here. - pk1.Catch_Rate = Species switch - { - (int) Core.Species.Kadabra => 96, - (int) Core.Species.Dragonair => 27, - _ => (byte)PersonalTable.RB[Species].CatchRate, - }; - } - else - { - pk1.Catch_Rate = (byte)PersonalTable.RB[Species].CatchRate; // RB - } - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs deleted file mode 100644 index 79cae773f..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -/// Referenced Area object contains Time data which is used for origin data. -/// -/// -public sealed record EncounterSlot2 : EncounterSlot, INumberedSlot -{ - public override int Generation => 2; - public override EntityContext Context => EntityContext.Gen2; - public byte SlotNumber { get; } - public override Ball FixedBall => Ball.Poke; - public bool IsHeadbutt => SlotType == SlotType.Headbutt; - - public EncounterSlot2(EncounterArea2 area, byte species, byte min, byte max, byte slot) : base(area, species, species == 201 ? FormRandom : (byte)0, min, max) - { - SlotNumber = slot; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - - var pk2 = (PK2)pk; - - if (IsHeadbutt) - { - var id = pk2.TID16; - if (!IsTreeAvailable(id)) - { - // Get a random TID that satisfies this slot. - do { id = (ushort)Util.Rand.Next(); } - while (!IsTreeAvailable(id)); - pk2.TID16 = id; - } - } - - if (Version == GameVersion.C) - pk2.Met_TimeOfDay = ((EncounterArea2)Area).Time.RandomValidTime(); - } - - private static ReadOnlySpan TreeIndexes => new byte[] - { - 02, 04, 05, 08, 11, 12, 14, 15, 18, 20, 21, 25, 26, 34, 37, 38, 39, 91, 92, - }; - - private static ReadOnlySpan Trees => new[] - { - 0x3FF_3FF, // Route 29 - 0x0FF_3FF, // Route 30 - 0x3FE_3FF, // Route 31 - 0x3EE_3FF, // Route 32 - 0x240_3FF, // Route 33 - 0x37F_3FF, // Azalea Town - 0x3FF_3FF, // Ilex Forest - 0x001_3FE, // Route 34 - 0x261_3FF, // Route 35 - 0x3FF_3FF, // Route 36 - 0x2B9_3FF, // Route 37 - 0x3FF_3FF, // Route 38 - 0x184_3FF, // Route 39 - 0x3FF_3FF, // Route 42 - 0x3FF_3FF, // Route 43 - 0x3FF_3FF, // Lake of Rage - 0x2FF_3FF, // Route 44 - 0x200_1FF, // Route 26 - 0x2BB_3FF, // Route 27 - }; - - public bool IsTreeAvailable(ushort trainerID) - { - var treeIndex = TreeIndexes.BinarySearch((byte)Location); - if (treeIndex < 0) - return false; - var permissions = Trees[treeIndex]; - - var pivot = trainerID % 10; - var type = Area.Type; - return type switch - { - SlotType.Headbutt => (permissions & (1 << pivot)) != 0, - /*special*/ _ => (permissions & (1 << (pivot + 12))) != 0, - }; - } - - // we have "Special" bitflag. Strip it out. - public SlotType SlotType => Area.Type & (SlotType)0xF; -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs deleted file mode 100644 index 7a51a32e0..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public record EncounterSlot3 : EncounterSlot, IMagnetStatic, INumberedSlot, ISlotRNGType -{ - public sealed override int Generation => 3; - public override EntityContext Context => EntityContext.Gen3; - - public byte StaticIndex { get; } - public byte MagnetPullIndex { get; } - public byte StaticCount { get; } - public byte MagnetPullCount { get; } - public SlotType Type => Area.Type; - - public byte SlotNumber { get; } - public override Ball FixedBall => Locations.IsSafariZoneLocation3(Location) ? Ball.Safari : Ball.None; - - public EncounterSlot3(EncounterArea3 area, ushort species, byte form, byte min, byte max, byte slot, byte mpi, byte mpc, byte sti, byte stc) : base(area, species, form, min, max) - { - SlotNumber = slot; - - MagnetPullIndex = mpi; - MagnetPullCount = mpc; - - StaticIndex = sti; - StaticCount = stc; - } - - public override EncounterMatchRating GetMatchRating(PKM pk) - { - if (IsDeferredSafari3(pk.Ball == (int)Ball.Safari)) - return EncounterMatchRating.PartialMatch; - return base.GetMatchRating(pk); - } - - private bool IsDeferredSafari3(bool IsSafariBall) => IsSafariBall != Locations.IsSafariZoneLocation3(Location); -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3PokeSpot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3PokeSpot.cs deleted file mode 100644 index 16128c193..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3PokeSpot.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot3PokeSpot : EncounterSlot, INumberedSlot, IFatefulEncounterReadOnly -{ - public override int Generation => 3; - public override EntityContext Context => EntityContext.Gen3; - public bool FatefulEncounter => true; - - public byte SlotNumber { get; } - - public EncounterSlot3PokeSpot(EncounterArea3XD area, ushort species, byte min, byte max, byte slot) : base(area, species, 0, min, max) - { - SlotNumber = slot; - } - - // PokeSpot encounters always have Fateful Encounter set. - protected override void SetFormatSpecificData(PKM pk) => pk.FatefulEncounter = true; - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - int ability = criteria.GetAbilityFromNumber(0); - PIDGenerator.SetRandomPokeSpotPID(pk, nature, gender, ability, SlotNumber); - pk.Gender = gender; - pk.StatNature = nature; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs deleted file mode 100644 index 06fba7b39..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -/// Handled differently as these slots have fixed moves that are different from their normal level-up moves. -/// -/// -internal sealed record EncounterSlot3Swarm : EncounterSlot3, IMoveset -{ - public Moveset Moves { get; } - - public EncounterSlot3Swarm(EncounterArea3 area, ushort species, byte min, byte max, byte slot, - Moveset moves) : base(area, species, 0, min, max, slot, 0, 0, 0, 0) => Moves = moves; - - protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - pk.SetMoves(Moves); - pk.SetMaximumPPCurrent(Moves); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs deleted file mode 100644 index be3206d17..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot4 : EncounterSlot, IMagnetStatic, INumberedSlot, IGroundTypeTile, ISlotRNGType -{ - public override int Generation => 4; - public override EntityContext Context => EntityContext.Gen4; - public GroundTileAllowed GroundTile => ((EncounterArea4)Area).GroundTile; - - public byte StaticIndex { get; } - public byte MagnetPullIndex { get; } - public byte StaticCount { get; } - public byte MagnetPullCount { get; } - public SlotType Type => Area.Type; - - public byte SlotNumber { get; } - public override Ball FixedBall => GetRequiredBallValue(); - public bool CanUseRadar => !GameVersion.HGSS.Contains(Version) && GroundTile.HasFlag(GroundTileAllowed.Grass); - - public EncounterSlot4(EncounterArea4 area, ushort species, byte form, byte min, byte max, byte slot, byte mpi, byte mpc, byte sti, byte stc) : base(area, species, form, min, max) - { - SlotNumber = slot; - - MagnetPullIndex = mpi; - MagnetPullCount = mpc; - - StaticIndex = sti; - StaticCount = stc; - } - - protected override void SetFormatSpecificData(PKM pk) => ((PK4)pk).GroundTile = GroundTile.GetIndex(); - - public override EncounterMatchRating GetMatchRating(PKM pk) - { - if ((pk.Ball == (int)Ball.Safari) != Locations.IsSafariZoneLocation4(Location)) - return EncounterMatchRating.PartialMatch; - if ((pk.Ball == (int)Ball.Sport) != (Type == SlotType.BugContest)) - { - // Nincada => Shedinja can wipe the ball back to Poke - if (pk.Species != (int)Core.Species.Shedinja || pk.Ball != (int)Ball.Poke) - return EncounterMatchRating.PartialMatch; - } - return base.GetMatchRating(pk); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - int ctr = 0; - do - { - base.SetPINGA(pk, criteria); - var pidiv = MethodFinder.Analyze(pk); - var frames = FrameFinder.GetFrames(pidiv, pk); - foreach (var frame in frames) - { - if (frame.IsSlotCompatibile(this, pk)) - return; - } - } while (ctr++ < 10_000); - } - - private Ball GetRequiredBallValue() - { - if (Type is SlotType.BugContest) - return Ball.Sport; - return Locations.IsSafariZoneLocation4(Location) ? Ball.Safari : Ball.None; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs deleted file mode 100644 index afcc38d7b..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot5 : EncounterSlot -{ - public override int Generation => 5; - public override EntityContext Context => EntityContext.Gen5; - - public EncounterSlot5(EncounterArea5 area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) - { - } - - public bool IsHiddenGrotto => Area.Type == SlotType.HiddenGrotto; - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => Area.Type == SlotType.HiddenGrotto ? HiddenAbilityPermission.Always : HiddenAbilityPermission.Never; -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs deleted file mode 100644 index 5c6d57122..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot6AO : EncounterSlot -{ - public override int Generation => 6; - public override EntityContext Context => EntityContext.Gen6; - public bool CanDexNav => Area.Type != SlotType.Rock_Smash; - public bool IsHorde => Area.Type == SlotType.Horde; - - public bool Pressure { get; init; } - public bool DexNav { get; init; } - public bool WhiteFlute { get; init; } - public bool BlackFlute { get; init; } - - public EncounterSlot6AO(EncounterArea6AO area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) - { - } - - protected override void SetFormatSpecificData(PKM pk) - { - var pk6 = (PK6)pk; - if (CanDexNav) - { - var eggMoves = GetDexNavMoves(); - if (eggMoves.Length > 0) - pk6.RelearnMove1 = eggMoves[Util.Rand.Next(eggMoves.Length)]; - } - pk6.SetRandomMemory6(); - pk6.SetRandomEC(); - } - - public override string GetConditionString(out bool valid) - { - valid = true; - if (WhiteFlute) // Decreased Level Encounters - return Pressure ? LegalityCheckStrings.LEncConditionWhiteLead : LegalityCheckStrings.LEncConditionWhite; - if (BlackFlute) // Increased Level Encounters - return Pressure ? LegalityCheckStrings.LEncConditionBlackLead : LegalityCheckStrings.LEncConditionBlack; - if (DexNav) - return LegalityCheckStrings.LEncConditionDexNav; - - return Pressure ? LegalityCheckStrings.LEncConditionLead : LegalityCheckStrings.LEncCondition; - } - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => CanDexNav || IsHorde ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; - - private ReadOnlySpan GetDexNavMoves() - { - var et = EvolutionTree.Evolves6; - var baby = et.GetBaseSpeciesForm(Species, Form); - return LearnSource6AO.Instance.GetEggMoves(baby.Species, baby.Form); - } - - public bool CanBeDexNavMove(ushort move) - { - var baseEgg = GetDexNavMoves(); - return baseEgg.Contains(move); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6XY.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6XY.cs deleted file mode 100644 index 9fe9e04b9..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6XY.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot6XY : EncounterSlot -{ - public override int Generation => 6; - public override EntityContext Context => EntityContext.Gen6; - public bool Pressure { get; init; } - public bool IsFriendSafari => Area.Type == SlotType.FriendSafari; - public bool IsHorde => Area.Type == SlotType.Horde; - - public EncounterSlot6XY(EncounterArea6XY area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) - { - } - - protected override void SetFormatSpecificData(PKM pk) - { - var pk6 = (PK6)pk; - pk6.SetRandomMemory6(); - pk6.SetRandomEC(); - } - - public override string GetConditionString(out bool valid) - { - valid = true; - return Pressure ? LegalityCheckStrings.LEncConditionLead : LegalityCheckStrings.LEncCondition; - } - - public EncounterSlot6XY CreatePressureFormCopy(byte form) => this with {Form = form, Pressure = true}; - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => IsHorde || IsFriendSafari ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs deleted file mode 100644 index 072e54572..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot7 : EncounterSlot -{ - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7; - public bool IsSOS => Area.Type == SlotType.SOS; - - public EncounterSlot7(EncounterArea7 area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) - { - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - pk.PID = Util.Rand32(); - pk.Nature = (int)criteria.GetNature(Nature.Random); - pk.Gender = criteria.GetGender(-1, pi); - criteria.SetRandomIVs(pk); - - var num = Ability; - if (IsSOS && pk.FlawlessIVCount < 2) - num = 0; // let's fake it as an insufficient chain, no HA possible. - var ability = criteria.GetAbilityFromNumber(num); - pk.RefreshAbility(ability); - pk.SetRandomEC(); - } - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => IsSOS ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; - - public override Ball FixedBall => Location == Locations.Pelago7 ? Ball.Poke : Ball.None; -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7b.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7b.cs deleted file mode 100644 index 8e0425d04..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7b.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot7b : EncounterSlot -{ - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7b; - - public EncounterSlot7b(EncounterArea7b area, ushort species, byte min, byte max) : base(area, species, 0, min, max) - { - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.SetRandomEC(); - var pb = (PB7)pk; - pb.ResetHeight(); - pb.ResetWeight(); - pb.ResetCP(); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs deleted file mode 100644 index 908cd87a0..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs +++ /dev/null @@ -1,138 +0,0 @@ -using static PKHeX.Core.OverworldCorrelation8Requirement; - -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot8 : EncounterSlot, IOverworldCorrelation8 -{ - public readonly AreaWeather8 Weather; - public readonly AreaSlotType8 SlotType; - public override string LongName => $"{wild} [{SlotType}] - {Weather.ToString().Replace("_", string.Empty)}"; - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8; - - // Fishing are only from the hidden table (not symbol). - public bool CanEncounterViaFishing => SlotType.CanEncounterViaFishing(Weather); - public bool CanEncounterViaCurry - { - get - { - if (!SlotType.CanEncounterViaCurry()) - return false; - - if ((Weather & AreaWeather8.All) == 0) - return false; - - if (EncounterArea8.IsWildArea(Location)) - return false; - - return true; - } - } - - public EncounterSlot8(EncounterArea8 area, ushort species, byte form, byte min, byte max, AreaWeather8 weather, AreaSlotType8 slotType) : base(area, species, form, min, max) - { - Weather = weather; - SlotType = slotType; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - bool symbol = ((EncounterArea8)Area).PermitCrossover; - var c = symbol ? EncounterCriteria.Unrestricted : criteria; - if (!symbol && Location is 30 or 54 && (Weather & AreaWeather8.Fishing) == 0) - ((PK8)pk).RibbonMarkCurry = true; - - base.ApplyDetails(sav, c, pk); - if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) - pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel; - - var req = GetRequirement(pk); - if (req != MustHave) - { - pk.SetRandomEC(); - return; - } - // Don't bother honoring shiny state. - Overworld8RNG.ApplyDetails(pk, c, Shiny.Random); - } - - public OverworldCorrelation8Requirement GetRequirement(PKM pk) - { - if (((EncounterArea8)Area).PermitCrossover) - return MustHave; // symbol walking overworld - - bool curry = pk is IRibbonSetMark8 {RibbonMarkCurry: true} || (pk.Species == (int)Core.Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon:(int)RibbonIndex.MarkCurry}); - if (curry) - return MustNotHave; - - // Tree encounters are generated via the global seed, not the u32 - if ((Weather & AreaWeather8.Shaking_Trees) != 0) - { - // Some tree encounters are present in the regular encounters. - return Weather == AreaWeather8.Shaking_Trees - ? MustNotHave - : CanBeEither; - } - - return MustHave; - } - - public bool IsOverworldCorrelationCorrect(PKM pk) - { - var flawless = GetFlawlessIVCount(pk.Met_Level); - return Overworld8RNG.ValidateOverworldEncounter(pk, flawless: flawless); - } - - private int GetFlawlessIVCount(int met) - { - const int none = 0; - const int any023 = -1; - - // Brilliant encounters are boosted to max level for the slot. - if (met < LevelMax) - return none; - - var area = (EncounterArea8) Area; - if (area.PermitCrossover) - return any023; // Symbol - if ((Weather & AreaWeather8.Fishing) != 0) - return any023; // Fishing - return none; // Hidden - } - - public override EncounterMatchRating GetMatchRating(PKM pk) - { - bool isHidden = pk.AbilityNumber == 4; - if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) - return EncounterMatchRating.PartialMatch; - - if (pk is IRibbonSetMark8 m) - { - if (m.RibbonMarkCurry && (Weather & AreaWeather8.All) == 0) - return EncounterMatchRating.DeferredErrors; - if (m.RibbonMarkFishing && (Weather & AreaWeather8.Fishing) == 0) - return EncounterMatchRating.DeferredErrors; - - // Check if it has a mark and the weather does not permit the mark. - // Tree/Fishing slots should be deferred here and are checked later. - if (!Weather.IsMarkCompatible(m)) - return EncounterMatchRating.DeferredErrors; - - // Galar Mine hidden encounters can only be found via Curry or Fishing. - if (Location is (30 or 54) && SlotType is AreaSlotType8.HiddenMain && !m.RibbonMarkCurry && !SlotType.CanEncounterViaFishing(Weather)) - return EncounterMatchRating.DeferredErrors; - } - - var req = GetRequirement(pk); - return req switch - { - MustHave when !IsOverworldCorrelationCorrect(pk) => EncounterMatchRating.DeferredErrors, - MustNotHave when IsOverworldCorrelationCorrect(pk) => EncounterMatchRating.DeferredErrors, - _ => EncounterMatchRating.Match, - }; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs deleted file mode 100644 index dec3a573b..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot8b : EncounterSlot -{ - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8b; - public bool IsUnderground => Area.Location is (>= 508 and <= 617); - public bool IsMarsh => Area.Location is (>= 219 and <= 224); - public override Ball FixedBall => IsMarsh ? Ball.Safari : Ball.None; - - public EncounterSlot8b(EncounterArea8b area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) - { - } - - public override EncounterMatchRating GetMatchRating(PKM pk) - { - bool isHidden = pk.AbilityNumber == 4; - if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) - return EncounterMatchRating.PartialMatch; - return base.GetMatchRating(pk); - } - - protected override void SetFormatSpecificData(PKM pk) - { - if (IsUnderground) - { - if (GetBaseEggMove(out var move1)) - pk.RelearnMove1 = move1; - } - pk.SetRandomEC(); - } - - public bool CanBeUndergroundMove(ushort move) - { - var et = PersonalTable.BDSP; - var sf = et.GetFormEntry(Species, Form); - var species = sf.HatchSpecies; - var baseEgg = LearnSource8BDSP.Instance.GetEggMoves(species, 0); - if (baseEgg.Length == 0) - return move == 0; - return baseEgg.Contains(move); - } - - public bool GetBaseEggMove(out ushort move) - { - var et = PersonalTable.BDSP; - var sf = et.GetFormEntry(Species, Form); - var species = sf.HatchSpecies; - var baseEgg = LearnSource8BDSP.Instance.GetEggMoves(species, 0); - if (baseEgg.Length == 0) - { - move = 0; - return false; - } - - // Official method creates a new List() with all the egg moves, removes all ignored, then picks a random index. - // However, the "excluded egg moves" list was unreferenced in v1.0, so all egg moves are allowed. - // We can't know which patch the encounter originated from, because they never added any new content. - var rnd = Util.Rand; - { - var index = rnd.Next(baseEgg.Length); - move = baseEgg[index]; - return true; - } - } - - public bool CanUseRadar => Area.Type is SlotType.Grass && !IsUnderground && !IsMarsh && Location switch - { - 195 or 196 => false, // Oreburgh Mine - 203 or 204 or 205 or 208 or 209 or 210 or 211 or 212 or 213 or 214 or 215 => false, // Mount Coronet, 206/207 exterior - >= 225 and <= 243 => false, // Solaceon Ruins - 244 or 245 or 246 or 247 or 248 or 249 => false, // Victory Road - 252 => false, // Ravaged Path - 255 or 256 => false, // Oreburgh Gate - 260 or 261 or 262 => false, // Stark Mountain, 259 exterior - >= 264 and <= 284 => false, // Turnback Cave - 286 or 287 or 288 or 289 or 290 or 291 => false, // Snowpoint Temple - 292 or 293 => false, // Wayward Cave - 294 or 295 => false, // Ruin Maniac Cave - 296 => false, // Maniac Tunnel - 299 or 300 or 301 or 302 or 303 or 304 or 305 => false, // Iron Island, 298 exterior - 306 or 307 or 308 or 309 or 310 or 311 or 312 or 313 or 314 => false, // Old Chateau - 368 or 369 or 370 or 371 or 372 => false, // Route 209 (Lost Tower) - _ => true, - }; - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => CanUseRadar ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot9.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot9.cs deleted file mode 100644 index e5492b103..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot9.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Collections.Generic; -using static PKHeX.Core.AreaWeather9; - -namespace PKHeX.Core; - -/// -/// Encounter Slot found in . -/// -/// -public sealed record EncounterSlot9 : EncounterSlot -{ - public override int Generation => 9; - public override EntityContext Context => EntityContext.Gen9; - public sbyte Gender { get; } - public byte Time { get; } // disallow at time bit flag - - public EncounterSlot9(EncounterArea9 area, ushort species, byte form, byte min, byte max, sbyte gender, byte time) : base(area, species, form, min, max) - { - Gender = gender; - Time = time; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - - var pk9 = (PK9)pk; - pk9.Obedience_Level = (byte)pk.Met_Level; - var rand = new Xoroshiro128Plus(Util.Rand.Rand64()); - var type = Tera9RNG.GetTeraTypeFromPersonal(Species, Form, rand.Next()); - pk9.TeraTypeOriginal = (MoveType)type; - if (criteria.TeraType != -1 && type != criteria.TeraType) - pk9.SetTeraType(type); // sets the override type - if (Gender != -1) - pk.Gender = (byte)Gender; - pk9.Scale = PokeSizeUtil.GetRandomScalar(); - if (Species == (int)Core.Species.Toxtricity) - pk.Nature = ToxtricityUtil.GetRandomNature(ref rand, Form); - pk9.EncryptionConstant = Util.Rand32(); - } - - private static int GetTime(RibbonIndex mark) => mark switch - { - RibbonIndex.MarkLunchtime => 0, - RibbonIndex.MarkSleepyTime => 1, - RibbonIndex.MarkDusk => 2, - RibbonIndex.MarkDawn => 3, - _ => 4, - }; - - public bool CanSpawnAtTime(RibbonIndex mark) => (Time & (1 << GetTime(mark))) == 0; - - public bool CanSpawnInWeather(RibbonIndex mark) - { - if (AreaWeather.TryGetValue((byte)Area.Location, out var areaWeather)) - return areaWeather.IsMarkCompatible(mark); - return false; - } - - /// - /// Location IDs matched with possible weather types. - /// - internal static readonly Dictionary AreaWeather = new() - { - { 6, Standard }, // South Province (Area One) - { 10, Standard }, // Pokémon League - { 12, Standard }, // South Province (Area Two) - { 14, Standard }, // South Province (Area Four) - { 16, Standard }, // South Province (Area Six) - { 18, Standard }, // South Province (Area Five) - { 20, Standard }, // South Province (Area Three) - { 22, Standard }, // West Province (Area One) - { 24, Sand }, // Asado Desert - { 26, Standard }, // West Province (Area Two) - { 28, Standard }, // West Province (Area Three) - { 30, Standard }, // Tagtree Thicket - { 32, Standard }, // East Province (Area Three) - { 34, Standard }, // East Province (Area One) - { 36, Standard }, // East Province (Area Two) - { 38, Snow }, // Glaseado Mountain (1) - { 40, Standard }, // Casseroya Lake - { 44, Standard }, // North Province (Area Three) - { 46, Standard }, // North Province (Area One) - { 48, Standard }, // North Province (Area Two) - { 50, Standard }, // Great Crater of Paldea - { 56, Standard }, // South Paldean Sea - { 58, Standard }, // West Paldean Sea - { 60, Standard }, // East Paldean Sea - { 62, Standard }, // North Paldean Sea - { 64, Inside }, // Inlet Grotto - { 67, Inside }, // Alfornada Cavern - { 69, Standard | Inside | Snow | Snow },// Dalizapa Passage (Near Medali, Tunnels, Near Pokémon Center, Near Zapico) - { 70, Standard }, // Poco Path - { 80, Standard }, // Cabo Poco - { 109, Standard }, // Socarrat Trail - { 124, Inside }, // Area Zero (5) - }; -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs deleted file mode 100644 index 86878e07c..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Encounter Slot found in (GO Park, ). -/// -/// -public sealed record EncounterSlot7GO : EncounterSlotGO -{ - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7b; - public override Ball FixedBall => Ball.None; // GO Park can override the ball; obey capture rules for LGP/E - - public EncounterSlot7GO(EncounterArea7g area, ushort species, byte form, int start, int end, Shiny shiny, Gender gender, PogoType type) - : base(area, species, form, start, end, shiny, gender, type) - { - } - - protected override PB7 GetBlank() => new(); - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pb = (PB7) pk; - pb.AwakeningSetAllTo(2); - pk.SetRandomEC(); - pb.HeightScalar = PokeSizeUtil.GetRandomScalar(); - pb.WeightScalar = PokeSizeUtil.GetRandomScalar(); - pb.ResetHeight(); - pb.ResetWeight(); - pb.ResetCP(); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - var ability = criteria.GetAbilityFromNumber(Ability); - - pk.PID = Util.Rand32(); - pk.Nature = pk.StatNature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - pk.SetRandomIVsGO(); - base.SetPINGA(pk, criteria); - } - - protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - Span moves = stackalloc ushort[4]; - ILearnSource source = LearnSource7GG.Instance; - source.SetEncounterMoves(Species, Form, level, moves); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs deleted file mode 100644 index 3728c5cfa..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Contains details about an encounter that can be found in . -/// -public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot -{ - /// - public int StartDate { get; } - - /// - public int EndDate { get; } - - public override Shiny Shiny { get; } - - /// - public PogoType Type { get; } - - /// - public Gender Gender { get; } - - public override bool IsShiny => Shiny.IsShiny(); - - public override Ball FixedBall => Type.GetValidBall(); - - protected EncounterSlotGO(EncounterArea area, ushort species, byte form, int start, int end, Shiny shiny, Gender gender, PogoType type) : base(area, species, form, type.GetMinLevel(), EncountersGO.MAX_LEVEL) - { - StartDate = start; - EndDate = end; - - Shiny = shiny; - Gender = gender; - Type = type; - } - - public sealed override string LongName - { - get - { - var init = $"{Name} ({Type})"; - if (StartDate == 0 && EndDate == 0) - return init; - return $"{init}: {GetDateString(StartDate)}-{GetDateString(EndDate)}"; - } - } - - private static string GetDateString(int time) => time == 0 ? "X" : $"{GetDate(time):yyyy.MM.dd}"; - - private static DateOnly GetDate(int time) - { - var d = time & 0xFF; - var m = (time >> 8) & 0xFF; - var y = time >> 16; - return new DateOnly(y, m, d); - } - - public bool IsWithinStartEnd(int stamp) - { - if (EndDate == 0) - return StartDate <= stamp && GetDate(stamp) <= GetMaxDate(); - if (StartDate == 0) - return stamp <= EndDate; - return StartDate <= stamp && stamp <= EndDate; - } - - /// - /// Converts a split timestamp into a single integer. - /// - public static int GetTimeStamp(int year, int month, int day) => (year << 16) | (month << 8) | day; - - private static DateOnly GetMaxDate() => DateOnly.FromDateTime(DateTime.UtcNow.AddHours(12)); // UTC+12 for Kiribati, no daylight savings - - /// - /// Gets a random date within the availability range. - /// - public DateOnly GetRandomValidDate() - { - if (StartDate == 0) - return EndDate == 0 ? GetMaxDate() : GetDate(EndDate); - - var start = GetDate(StartDate); - if (EndDate == 0) - return start; - var end = GetDate(EndDate); - return DateUtil.GetRandomDateWithin(start, end); - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - if (StartDate != 0 || EndDate != 0) - pk.MetDate = GetRandomValidDate(); - if (Gender != Gender.Random) - pk.Gender = (int)Gender; - pk.SetRandomIVsGO(Type.GetMinIV()); - } - - public bool GetIVsAboveMinimum(PKM pk) - { - int min = Type.GetMinIV(); - if (min == 0) - return true; - return GetIVsAboveMinimum(pk, min); - } - - private static bool GetIVsAboveMinimum(PKM pk, int min) - { - if (pk.IV_ATK >> 1 < min) // ATK - return false; - if (pk.IV_DEF >> 1 < min) // DEF - return false; - return pk.IV_HP >> 1 >= min; // HP - } - - public bool GetIVsValid(PKM pk) - { - if (!GetIVsAboveMinimum(pk)) - return false; - - // HP * 2 | 1 -> HP - // ATK * 2 | 1 -> ATK&SPA - // DEF * 2 | 1 -> DEF&SPD - // Speed is random. - - // All IVs must be odd (except speed) and equal to their counterpart. - if ((pk.GetIV(1) & 1) != 1 || pk.GetIV(1) != pk.GetIV(4)) // ATK=SPA - return false; - if ((pk.GetIV(2) & 1) != 1 || pk.GetIV(2) != pk.GetIV(5)) // DEF=SPD - return false; - return (pk.GetIV(0) & 1) == 1; // HP - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - switch (Shiny) - { - case Shiny.Random when !pk.IsShiny && criteria.Shiny.IsShiny(): - case Shiny.Always when !pk.IsShiny: // Force Square - var low = pk.PID & 0xFFFF; - pk.PID = ((low ^ pk.TID16 ^ pk.SID16 ^ 0) << 16) | low; - break; - - case Shiny.Random when pk.IsShiny && !criteria.Shiny.IsShiny(): - case Shiny.Never when pk.IsShiny: // Force Not Shiny - pk.PID ^= 0x1000_0000; - break; - } - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/IPogoSlot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/IPogoSlot.cs deleted file mode 100644 index a26708161..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/IPogoSlot.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Stores details about encounters relevant for legality. -/// -public interface IPogoSlot -{ - /// Start date the encounter became available. If zero, no date specified (unbounded start). - int StartDate { get; } - - /// Last day the encounter was available. If zero, no date specified (unbounded finish). - /// If there is no end date (yet), we'll try to clamp to a date in the near-future to prevent it from being open-ended. - int EndDate { get; } - - /// Possibility of shiny for the encounter. - Shiny Shiny { get; } - - /// Method the Pokmon may be encountered with. - PogoType Type { get; } - - /// Gender the Pokmon may be encountered with. - Gender Gender { get; } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/INumberedSlot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/INumberedSlot.cs deleted file mode 100644 index 1ccea3db2..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/INumberedSlot.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace PKHeX.Core; - -/// -/// that contains information about what Index it is within the 's type-group of slots. -/// -/// -/// Useful for checking legality (if the RNG can yield this slot). -/// -public interface INumberedSlot -{ - /// - /// Number Index of the . - /// - byte SlotNumber { get; } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/DreamWorldEntry.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/DreamWorldEntry.cs deleted file mode 100644 index 5b878f865..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/DreamWorldEntry.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Intermediary Representation of Dream World Data -/// -public readonly record struct DreamWorldEntry(ushort Species, byte Level, ushort Move1 = 0, ushort Move2 = 0, ushort Move3 = 0, byte Form = 0, sbyte Gender = -1) -{ - private int EntryCount => Move1 == 0 ? 1 : Move2 == 0 ? 1 : Move3 == 0 ? 2 : 3; - - private void AddTo(GameVersion game, EncounterStatic5[] result, ref int ctr) - { - var p = PersonalTable.B2W2[Species]; - var a = p.HasHiddenAbility ? AbilityPermission.OnlyHidden : AbilityPermission.OnlyFirst; - if (Move1 == 0) - { - result[ctr++] = new EncounterStatic5(game) - { - Species = Species, - Form = Form, - Gender = Gender, - Level = Level, - Ability = a, - Location = 075, - Shiny = Shiny.Never, - }; - return; - } - result[ctr++] = Create(game, a, Move1); - if (Move2 == 0) - return; - result[ctr++] = Create(game, a, Move2); - if (Move3 == 0) - return; - result[ctr++] = Create(game, a, Move3); - } - - private EncounterStatic5 Create(GameVersion game, AbilityPermission ability, ushort move) => new(game) - { - Species = Species, - Form = Form, - Gender = Gender, - Level = Level, - Ability = ability, - Location = 075, - Shiny = Shiny.Never, - Moves = new(move), - }; - - public static EncounterStatic5[] GetArray(GameVersion game, ReadOnlySpan t) - { - // Split encounters with multiple permitted special moves -- a pk can only be obtained with 1 of the special moves! - int count = 0; - foreach (var e in t) - count += e.EntryCount; - var result = new EncounterStatic5[count]; - - int ctr = 0; - foreach (var s in t) - s.AddTo(game, result, ref ctr); - return result; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterFixed9.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterFixed9.cs deleted file mode 100644 index 7befe1dcc..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterFixed9.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using static System.Buffers.Binary.BinaryPrimitives; - -namespace PKHeX.Core; - -/// -/// Generation 9 Fixed Spawn Encounter -/// -public sealed record EncounterFixed9 : EncounterStatic, IGemType -{ - public override int Generation => 9; - public override int Location => Location0; - public override EntityContext Context => EntityContext.Gen9; - public GemType TeraType { get; private init; } - private byte Location0 { get; init; } - private byte Location1 { get; init; } - private byte Location2 { get; init; } - private byte Location3 { get; init; } - - private const int MinScaleStrongTera = 200; // [200,255] - - public static EncounterFixed9[] GetArray(ReadOnlySpan data) - { - const int size = 0x14; - var count = data.Length / size; - var result = new EncounterFixed9[count]; - for (int i = 0; i < result.Length; i++) - result[i] = ReadEncounter(data.Slice(i * size, size)); - return result; - } - - private EncounterFixed9() : base(GameVersion.SV) { } - - private static EncounterFixed9 ReadEncounter(ReadOnlySpan data) => new() - { - Species = ReadUInt16LittleEndian(data), - Form = data[0x02], - Level = data[0x03], - FlawlessIVCount = data[0x04], - TeraType = (GemType)data[0x05], - Gender = (sbyte)data[0x06], - // 1 byte reserved - Moves = new Moveset( - ReadUInt16LittleEndian(data[0x08..]), - ReadUInt16LittleEndian(data[0x0A..]), - ReadUInt16LittleEndian(data[0x0C..]), - ReadUInt16LittleEndian(data[0x0E..])), - Location0 = data[0x10], - Location1 = data[0x11], - Location2 = data[0x12], - Location3 = data[0x13], - }; - - protected override bool IsMatchLocation(PKM pk) - { - var metState = LocationsHOME.GetRemapState(Context, pk.Context); - if (metState == LocationRemapState.Original) - return IsMatchLocationExact(pk); - if (metState == LocationRemapState.Remapped) - return IsMatchLocationRemapped(pk); - return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); - } - - private bool IsMatchLocationRemapped(PKM pk) - { - var met = (ushort)pk.Met_Location; - var version = pk.Version; - if (pk.Context == EntityContext.Gen8) - return LocationsHOME.IsValidMetSV(met, version); - return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; - } - - private bool IsMatchLocationExact(PKM pk) - { - var loc = pk.Met_Location; - return loc == Location0 || loc == Location1 || loc == Location2 || loc == Location3; - } - - protected override bool IsMatchForm(PKM pk, EvoCriteria evo) - { - if (Species is (int)Core.Species.Deerling or (int)Core.Species.Sawsbuck) - return pk.Form <= 3; - return base.IsMatchForm(pk, evo); - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal)) - return false; - if (TeraType != 0) - { - if (pk is IScaledSize3 size3) - { - if (size3.Scale < MinScaleStrongTera) - return false; - } - else if (pk is IScaledSize s2) - { - if (s2.HeightScalar < MinScaleStrongTera) - return false; - } - } - - if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount) - return false; - return base.IsMatchExact(pk, evo); - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - var pk9 = (PK9)pk; - pk9.Obedience_Level = (byte)pk9.Met_Level; - var type = Tera9RNG.GetTeraType(Util.Rand.Rand64(), TeraType, Species, Form); - pk9.TeraTypeOriginal = (MoveType)type; - if (criteria.TeraType != -1 && type != criteria.TeraType) - pk9.SetTeraType(type); // sets the override type - - pk9.HeightScalar = PokeSizeUtil.GetRandomScalar(); - pk9.WeightScalar = PokeSizeUtil.GetRandomScalar(); - pk9.Scale = TeraType != 0 ? (byte)(MinScaleStrongTera + Util.Rand.Next(byte.MaxValue - MinScaleStrongTera + 1)) : PokeSizeUtil.GetRandomScalar(); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs deleted file mode 100644 index d5af4a6de..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs +++ /dev/null @@ -1,328 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Static Encounter Data -/// -/// -/// Static Encounters are fixed position encounters with properties that are not subject to Wild Encounter conditions. -/// -public abstract record EncounterStatic(GameVersion Version) : IEncounterable, IMoveset, IEncounterMatch, IFatefulEncounterReadOnly -{ - public ushort Species { get; init; } - public byte Form { get; init; } - public virtual byte Level { get; init; } - public virtual byte LevelMin => Level; - public virtual byte LevelMax => Level; - public abstract int Generation { get; } - public abstract EntityContext Context { get; } - - public virtual int Location { get; init; } - public AbilityPermission Ability { get; init; } - public Shiny Shiny { get; init; } - public Nature Nature { get; init; } = Nature.Random; - public sbyte Gender { get; init; } = -1; - - public ushort HeldItem { get; init; } - public bool Gift { get; init; } - public bool FatefulEncounter { get; init; } - - public byte EggCycles { get; init; } - public byte FlawlessIVCount { get; init; } - public byte Ball { get; init; } = 4; // Only checked when is Gift - - public int EggLocation { get; init; } - - public Ball FixedBall => Gift ? (Ball)Ball : Core.Ball.None; - - public Moveset Moves { get; init; } - public IndividualValueSet IVs { get; init; } - - public virtual bool EggEncounter => EggLocation != 0; - - private const string _name = "Static Encounter"; - public string Name => _name; - public string LongName => $"{_name} ({Version})"; - public bool IsShiny => Shiny.IsShiny(); - - protected virtual PKM GetBlank(ITrainerInfo tr) => EntityBlank.GetBlank(Generation, Version); - - public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); - - public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) - { - var pk = GetBlank(tr); - tr.ApplyTo(pk); - - ApplyDetails(tr, criteria, pk); - return pk; - } - - protected virtual void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - pk.EncryptionConstant = Util.Rand32(); - pk.Species = Species; - pk.Form = Form; - - var version = this.GetCompatibleVersion((GameVersion)tr.Game); - int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); - int level = GetMinimalLevel(); - - pk.Version = (int)version; - pk.Language = lang = GetEdgeCaseLanguage(pk, lang); - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation); - - pk.CurrentLevel = level; - ApplyDetailsBall(pk); - pk.HeldItem = HeldItem; - pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - - var today = DateTime.Today; - SetMetData(pk, level, today); - if (EggEncounter) - SetEggMetData(pk, tr, today); - - SetPINGA(pk, criteria); - SetEncounterMoves(pk, version, level); - - if (FatefulEncounter) - pk.FatefulEncounter = true; - - if (pk.Format < 6) - return; - - if (this is IRelearn relearn) - pk.SetRelearnMoves(relearn.Relearn); - - tr.ApplyHandlingTrainerInfo(pk); - - if (pk is IScaledSize { HeightScalar: 0, WeightScalar: 0 } s) - { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - if (this is IGigantamaxReadOnly g && pk is PK8 pg) - pg.CanGigantamax = g.CanGigantamax; - if (this is IDynamaxLevelReadOnly d && pk is PK8 pd) - pd.DynamaxLevel = d.DynamaxLevel; - } - - protected virtual void ApplyDetailsBall(PKM pk) => pk.Ball = Ball; - - protected virtual int GetMinimalLevel() => LevelMin; - - protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(Gender, pi); - int nature = (int)criteria.GetNature(Nature); - int ability = criteria.GetAbilityFromNumber(Ability); - - var pidtype = GetPIDType(); - PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender, pidtype); - SetIVs(pk); - pk.StatNature = pk.Nature; - } - - private void SetEggMetData(PKM pk, ITrainerInfo tr, DateTime today) - { - pk.Met_Location = Math.Max(0, EncounterSuggestion.GetSuggestedEggMetLocation(pk)); - pk.Met_Level = EncounterSuggestion.GetSuggestedEncounterEggMetLevel(pk); - - if (Generation >= 4) - { - bool traded = (int)Version == tr.Game; - pk.Egg_Location = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(Generation, Version, traded); - pk.EggMetDate = DateOnly.FromDateTime(today); - } - pk.Egg_Location = EggLocation; - pk.EggMetDate = DateOnly.FromDateTime(today); - } - - protected virtual void SetMetData(PKM pk, int level, DateTime today) - { - if (pk.Format <= 2) - return; - - pk.Met_Location = Location; - pk.Met_Level = level; - if (pk.Format >= 4) - pk.MetDate = DateOnly.FromDateTime(today); - } - - protected virtual void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - if (Moves.HasMoves) - { - pk.SetMoves(Moves); - pk.SetMaximumPPCurrent(Moves); - } - else - { - Span moves = stackalloc ushort[4]; - var source = GameData.GetLearnSource(version); - source.SetEncounterMoves(Species, Form, level, moves); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } - } - - protected void SetIVs(PKM pk) - { - if (IVs.IsSpecified) - pk.SetRandomIVsTemplate(IVs, FlawlessIVCount); - else if (FlawlessIVCount > 0) - pk.SetRandomIVs(minFlawless: FlawlessIVCount); - } - - private int GetEdgeCaseLanguage(PKM pk, int lang) - { - switch (this) - { - // Cannot trade between languages - case IFixedGBLanguage { Language: EncounterGBLanguage.Japanese } when lang != 1: - pk.OT_Name = "ゲーフリ"; - return (int)LanguageID.Japanese; - case IFixedGBLanguage { Language: not EncounterGBLanguage.Japanese } when lang == 1: - pk.OT_Name = "GF"; - return (int)LanguageID.English; - - // E-Reader was only available to Japanese games. - case EncounterStaticShadow { EReader: true } when lang != 1: - // Old Sea Map was only distributed to Japanese games. - case EncounterStatic3 { Species: (int)Core.Species.Mew } when lang != 1: - pk.OT_Name = "ゲーフリ"; - return (int)LanguageID.Japanese; - - // Deoxys for Emerald was not available for Japanese games. - case EncounterStatic3 { Species: (ushort)Core.Species.Deoxys, Version: GameVersion.E } when lang == 1: - pk.OT_Name = "GF"; - return (int)LanguageID.English; - - default: - return lang; - } - } - - private PIDType GetPIDType() - { - switch (Generation) - { - case 3 when this is EncounterStatic3 {Roaming: true, Version: not GameVersion.E}: // Roamer IV glitch was fixed in Emerald - return PIDType.Method_1_Roamer; - case 4 when Shiny == Shiny.Always: // Lake of Rage Gyarados - return PIDType.ChainShiny; - case 4 when Species == (int)Core.Species.Pichu: // Spiky Eared Pichu - case 4 when Location == Locations.PokeWalker4: // Pokéwalker - return PIDType.Pokewalker; - case 5 when Shiny == Shiny.Always: - return PIDType.G5MGShiny; - - default: return PIDType.None; - } - } - - public virtual bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (Nature != Nature.Random && pk.Nature != (int) Nature) - return false; - - if (!IsMatchEggLocation(pk)) - return false; - if (!IsMatchLocation(pk)) - return false; - if (!IsMatchLevel(pk, evo)) - return false; - if (!IsMatchGender(pk)) - return false; - if (!IsMatchForm(pk, evo)) - return false; - if (!IsMatchIVs(pk)) - return false; - - if (this is IContestStatsReadOnly es && pk is IContestStatsReadOnly s && s.IsContestBelow(es)) - return false; - - // Defer to EC/PID check - // if (e.Shiny != null && e.Shiny != pk.IsShiny) - // continue; - - // Defer ball check to later - // if (e.Gift && pk.Ball != 4) // PokéBall - // continue; - - return true; - } - - private bool IsMatchIVs(PKM pk) - { - if (!IVs.IsSpecified) - return true; // nothing to check, IVs are random - if (Generation <= 2 && pk.Format > 2) - return true; // IVs are regenerated on VC transfer upward - - return Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk); - } - - protected virtual bool IsMatchForm(PKM pk, EvoCriteria evo) - { - return Form == evo.Form || FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context); - } - - // override me if the encounter type has any eggs - protected virtual bool IsMatchEggLocation(PKM pk) - { - var expect = pk is PB8 ? Locations.Default8bNone : 0; - return pk.Egg_Location == expect; - } - - private bool IsMatchGender(PKM pk) - { - if (Gender == -1 || Gender == pk.Gender) - return true; - - if (Species == (int) Core.Species.Azurill && Generation == 4 && Location == 233 && pk.Gender == 0) - return EntityGender.GetFromPIDAndRatio(pk.PID, 0xBF) == 1; - - return false; - } - - protected virtual bool IsMatchLocation(PKM pk) - { - if (EggEncounter) - return true; - if (Location == 0) - return true; - if (!pk.HasOriginalMetLocation) - return true; - return Location == pk.Met_Location; - } - - protected virtual bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - return pk.Met_Level == Level; - } - - public virtual EncounterMatchRating GetMatchRating(PKM pk) - { - if (IsMatchPartial(pk)) - return EncounterMatchRating.PartialMatch; - return IsMatchDeferred(pk); - } - - /// - /// Checks if the provided might not be the best match, or even a bad match due to minor reasons. - /// - protected virtual EncounterMatchRating IsMatchDeferred(PKM pk) => EncounterMatchRating.Match; - - /// - /// Checks if the provided is not an exact match due to minor reasons. - /// - protected virtual bool IsMatchPartial(PKM pk) - { - if (pk is { Format: >= 5, AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) - return true; - return pk.FatefulEncounter != FatefulEncounter; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1.cs deleted file mode 100644 index 06f2f1032..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 1 Static Encounter -/// -/// -public record EncounterStatic1 : EncounterStatic -{ - public override int Generation => 1; - public override EntityContext Context => EntityContext.Gen1; - public sealed override byte Level { get; init; } - - private const int LightBallPikachuCatchRate = 0xA3; // 163 - - public EncounterStatic1(byte species, byte level, GameVersion game) : base(game) - { - Species = species; - Level = level; - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - - var pk1 = (PK1) pk; - if (Species == (int) Core.Species.Pikachu && Version == GameVersion.YW && Level == 5 && !Moves.HasMoves) - { - pk1.Catch_Rate = LightBallPikachuCatchRate; // Light Ball - return; - } - - // Encounters can have different Catch Rates (RBG vs Y) - var table = Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; - pk1.Catch_Rate = (byte)table[Species].CatchRate; - } - - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - // Met Level is not stored in the PK1 format. - // Check if it is at or above the encounter level. - return Level <= evo.LevelMax; - } - - protected override bool IsMatchLocation(PKM pk) - { - // Met Location is not stored in the PK1 format. - return true; - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (!base.IsMatchExact(pk, evo)) - return false; - - // Encounters with this version have to originate from the Japanese Blue game. - if (!pk.Japanese && Version == GameVersion.BU) - return false; - - return true; - } - - protected override bool IsMatchPartial(PKM pk) - { - if (pk is not PK1 pk1) - return false; - if (ParseSettings.AllowGen1Tradeback && PK1.IsCatchRateHeldItem(pk1.Catch_Rate)) - return false; - if (IsCatchRateValid(pk1)) - return false; - return true; - } - - private bool IsCatchRateValid(PK1 pk1) - { - var catch_rate = pk1.Catch_Rate; - - // Light Ball (Yellow) starter - if (Version == GameVersion.YW && Species == (int)Core.Species.Pikachu && Level == 5) - { - return catch_rate == LightBallPikachuCatchRate; - } - if (Version == GameVersion.Stadium) - { - // Amnesia Psyduck has different catch rates depending on language - if (Species == (int)Core.Species.Psyduck) - return catch_rate == (pk1.Japanese ? 167 : 168); - return catch_rate is 167 or 168; - } - - // Encounters can have different Catch Rates (RBG vs Y) - return GBRestrictions.RateMatchesEncounter(Species, Version, catch_rate); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1E.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1E.cs deleted file mode 100644 index 5c73ab0c1..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1E.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace PKHeX.Core; - -/// -/// Event data for Generation 1 -/// -/// -public sealed record EncounterStatic1E : EncounterStatic1, IFixedGBLanguage -{ - public EncounterGBLanguage Language { get; init; } = EncounterGBLanguage.Japanese; - - /// Trainer name for the event. - public string OT_Name { get; init; } = string.Empty; - - public IReadOnlyList OT_Names { get; init; } = Array.Empty(); - - /// Trainer ID for the event. - public ushort TID16 { get; init; } = UnspecifiedID; - - public const ushort UnspecifiedID = 0; - - public EncounterStatic1E(byte species, byte level, GameVersion game) : base(species, level, game) - { - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (!base.IsMatchExact(pk, evo)) - return false; - - if (Language != EncounterGBLanguage.Any && pk.Japanese != (Language == EncounterGBLanguage.Japanese)) - return false; - - // EC/PID check doesn't exist for these, so check Shiny state here. - if (!IsShinyValid(pk)) - return false; - - if (TID16 != UnspecifiedID && pk.TID16 != TID16) - return false; - - if (OT_Name.Length != 0) - { - if (pk.OT_Name != OT_Name) - return false; - } - else if (OT_Names.Count != 0) - { - if (!OT_Names.Contains(pk.OT_Name)) - return false; - } - - return true; - } - - private bool IsShinyValid(PKM pk) => Shiny switch - { - Shiny.Never => !pk.IsShiny, - Shiny.Always => pk.IsShiny, - _ => true, - }; - - protected override PK1 GetBlank(ITrainerInfo tr) => Language switch - { - EncounterGBLanguage.Japanese => new(true), - EncounterGBLanguage.International => new(), - _ => new(tr.Language == 1), - }; - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - - if (Version == GameVersion.Stadium) - { - var pk1 = (PK1)pk; - // Amnesia Psyduck has different catch rates depending on language - if (Species == (int)Core.Species.Psyduck) - pk1.Catch_Rate = pk1.Japanese ? (byte)167 : (byte)168; - else - pk1.Catch_Rate = Util.Rand.Next(2) == 0 ? (byte)167 : (byte)168; - } - - if (TID16 != UnspecifiedID) - pk.TID16 = TID16; - - if (OT_Name.Length != 0) - pk.OT_Name = OT_Name; - else if (OT_Names.Count != 0) - pk.OT_Name = OT_Names[Util.Rand.Next(OT_Names.Count)]; - } -} - -/// -/// Exposes info on language restriction for Gen1/2. -/// -public interface IFixedGBLanguage -{ - /// - /// Language restriction for the encounter template. - /// - EncounterGBLanguage Language { get; } -} - -/// -/// Generations 1 & 2 cannot communicate between Japanese & International versions. -/// -public enum EncounterGBLanguage -{ - /// Can only be obtained in Japanese games. - Japanese, - - /// Can only be obtained in International (not Japanese) games. - International, - - /// Can be obtained in any localization. - Any, -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2.cs deleted file mode 100644 index 73cc77c7f..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Generation 2 Static Encounter -/// -/// -public record EncounterStatic2 : EncounterStatic -{ - public sealed override int Generation => 2; - public override EntityContext Context => EntityContext.Gen2; - public sealed override byte Level { get; init; } - - public EncounterStatic2(byte species, byte level, GameVersion game) : base(game) - { - Species = species; - Level = level; - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (Shiny == Shiny.Always && !pk.IsShiny) - return false; - return base.IsMatchExact(pk, evo); - } - - protected override bool IsMatchEggLocation(PKM pk) - { - if (pk.Format > 2) - return true; - - if (pk.IsEgg) - { - if (!EggEncounter) - return false; - if (pk.Met_Location != 0 && pk.Met_Level != 0) - return false; - if (pk.OT_Friendship > EggCycles) // Dizzy Punch eggs start with below-normal hatch counters. - return false; - } - else - { - switch (pk.Met_Level) - { - case 0 when pk.Met_Location != 0: - return false; - case 1: // 0 = second floor of every Pokémon Center, valid - return true; - default: - if (pk.Met_Location == 0 && pk.Met_Level != 0) - return false; - break; - } - } - - return true; - } - - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - if (pk is ICaughtData2 {CaughtData: not 0}) - return pk.Met_Level == (EggEncounter ? 1 : Level); - - return Level <= evo.LevelMax; - } - - protected override bool IsMatchLocation(PKM pk) - { - if (EggEncounter) - return true; - if (Location == 0) - return true; - if (pk is ICaughtData2 c2) - { - if (c2.CaughtData is not 0) - return Location == pk.Met_Location; - if (pk.Species == (int)Core.Species.Celebi) - return false; // Cannot reset the Met data - } - return true; - } - - protected override bool IsMatchPartial(PKM pk) => false; - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - var pk2 = (PK2)pk; - if (Shiny == Shiny.Always) - pk2.SetShiny(); - } - - protected override void SetMetData(PKM pk, int level, DateTime today) - { - if (Version != GameVersion.C && pk.OT_Gender != 1) - return; - var pk2 = (PK2)pk; - pk2.Met_Location = Location; - pk2.Met_Level = level; - pk2.Met_TimeOfDay = EncounterTime.Any.RandomValidTime(); - } -} - -public sealed record EncounterStatic2Odd : EncounterStatic2 -{ - public EncounterStatic2Odd(byte species) : base(species, 5, GameVersion.C) - { - EggLocation = 256; - EggCycles = 20; - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - // Let it get picked up as regular EncounterEgg under other conditions. - if (pk.Format > 2) - return false; - if (!pk.HasMove((int)Move.DizzyPunch)) - return false; - if (pk.IsEgg && pk.EXP != 125) - return false; - return base.IsMatchExact(pk, evo); - } -} - -public sealed record EncounterStatic2Roam : EncounterStatic2 -{ - // Routes 29-46, except 40 & 41; total 16. - // 02, 04, 05, 08, 11, 15, 18, 20, - // 21, 25, 26, 34, 37, 39, 43, 45, - private const ulong RoamLocations = 0b10_1000_1010_0100_0000_0110_0011_0100_1000_1001_0011_0100; - public override int Location => 2; - - public EncounterStatic2Roam(byte species, byte level, GameVersion ver) : base(species, level, ver) { } - - protected override bool IsMatchLocation(PKM pk) - { - if (!pk.HasOriginalMetLocation) - return true; - // Gen2 met location is always u8 - var loc = pk.Met_Location; - return loc <= 45 && ((RoamLocations & (1UL << loc)) != 0); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2E.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2E.cs deleted file mode 100644 index ec3f97a11..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2E.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace PKHeX.Core; - -/// -/// Event data for Generation 2 -/// -/// -public sealed record EncounterStatic2E : EncounterStatic2, IFixedGBLanguage -{ - public EncounterGBLanguage Language { get; init; } = EncounterGBLanguage.Japanese; - - /// Trainer name for the event. - public string OT_Name { get; init; } = string.Empty; - - public IReadOnlyList OT_Names { get; init; } = Array.Empty(); - - private const ushort UnspecifiedID = 0; - - /// Trainer ID for the event. - public ushort TID16 { get; init; } = UnspecifiedID; - - public bool IsGift => TID16 != UnspecifiedID; - - public int CurrentLevel { get; init; } = -1; - - public EncounterStatic2E(byte species, byte level, GameVersion ver) : base(species, level, ver) - { - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (!base.IsMatchExact(pk, evo)) - return false; - - if (Language != EncounterGBLanguage.Any && pk.Japanese != (Language == EncounterGBLanguage.Japanese)) - return false; - - if (CurrentLevel != -1 && CurrentLevel > pk.CurrentLevel) - return false; - - // EC/PID check doesn't exist for these, so check Shiny state here. - if (!IsShinyValid(pk)) - return false; - - if (EggEncounter && !pk.IsEgg) - return true; - - // Check OT Details - if (TID16 != UnspecifiedID && pk.TID16 != TID16) - return false; - - if (OT_Name.Length != 0) - { - if (pk.OT_Name != OT_Name) - return false; - } - else if (OT_Names.Count != 0) - { - if (!OT_Names.Contains(pk.OT_Name)) - return false; - } - - return true; - } - - private bool IsShinyValid(PKM pk) => Shiny switch - { - Shiny.Never => !pk.IsShiny, - Shiny.Always => pk.IsShiny, - _ => true, - }; - - protected override int GetMinimalLevel() => CurrentLevel == -1 ? base.GetMinimalLevel() : CurrentLevel; - - protected override PK2 GetBlank(ITrainerInfo tr) => Language switch - { - EncounterGBLanguage.Japanese => new(true), - EncounterGBLanguage.International => new(), - _ => new(tr.Language == 1), - }; - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - if (CurrentLevel != -1) // Restore met level - pk.Met_Level = LevelMin; - - if (TID16 != UnspecifiedID) - pk.TID16 = TID16; - if (IsGift) - pk.OT_Gender = 0; - - if (OT_Name.Length != 0) - pk.OT_Name = OT_Name; - else if (OT_Names.Count != 0) - pk.OT_Name = OT_Names[Util.Rand.Next(OT_Names.Count)]; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs deleted file mode 100644 index 243dbbaae..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Generation 3 Static Encounter -/// -/// -public sealed record EncounterStatic3 : EncounterStatic -{ - public override int Generation => 3; - public override EntityContext Context => EntityContext.Gen3; - public bool Roaming { get; init; } - - public EncounterStatic3(ushort species, byte level, GameVersion game) : base(game) - { - Species = species; - Level = level; - } - - protected override bool IsMatchEggLocation(PKM pk) - { - if (pk.Format == 3) - return !pk.IsEgg || EggLocation == 0 || EggLocation == pk.Met_Location; - return base.IsMatchEggLocation(pk); - } - - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - if (pk.Format != 3) // Met Level lost on PK3=>PK4 - return Level <= evo.LevelMax; - - if (EggEncounter) - return pk is { Met_Level: 0, CurrentLevel: >= 5 }; // met level 0, origin level 5 - - return pk.Met_Level == Level; - } - - protected override bool IsMatchLocation(PKM pk) - { - if (EggEncounter) - return true; - if (pk.Format != 3) - return true; // transfer location verified later - - var met = pk.Met_Location; - if (!Roaming) - return Location == met; - - // Route 101-138 - if (Version <= GameVersion.E) - return met is >= 16 and <= 49; - // Route 1-25 encounter is possible either in grass or on water - return met is >= 101 and <= 125; - } - - protected override bool IsMatchPartial(PKM pk) - { - if (Gift && pk.Ball != Ball) - return true; - return base.IsMatchPartial(pk); - } - - protected override void SetMetData(PKM pk, int level, DateTime today) - { - pk.Met_Level = level; - pk.Met_Location = !Roaming ? Location : (Version <= GameVersion.E ? 16 : 101); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs deleted file mode 100644 index 6a6ba7155..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using static PKHeX.Core.GroundTileAllowed; - -namespace PKHeX.Core; - -/// -/// Generation 4 Static Encounter -/// -/// -public sealed record EncounterStatic4(GameVersion Version) : EncounterStatic(Version), IGroundTypeTile -{ - public override int Generation => 4; - public override EntityContext Context => EntityContext.Gen4; - - /// Indicates if the encounter is a Roamer (variable met location) - public bool Roaming { get; init; } - - /// values permitted for the encounter. - public GroundTileAllowed GroundTile { get; init; } = None; - - protected override bool IsMatchLocation(PKM pk) - { - if (!Roaming) - return base.IsMatchLocation(pk); - - // Met location is lost on transfer - if (pk is not G4PKM pk4) - return true; - - return pk4.GroundTile switch - { - GroundTileType.Grass => IsMatchLocationGrass(Location, pk4.Met_Location), - GroundTileType.Water => IsMatchLocationWater(Location, pk4.Met_Location), - _ => false, - }; - } - - private static bool IsMatchLocationGrass(int location, int met) => location switch - { - FirstS => IsMatchRoamerLocation(PermitGrassS, met, FirstS), - FirstJ => IsMatchRoamerLocation(PermitGrassJ, met, FirstJ), - FirstH => IsMatchRoamerLocation(PermitGrassH, met, FirstH), - _ => false, - }; - - private static bool IsMatchLocationWater(int location, int met) => location switch - { - FirstS => IsMatchRoamerLocation(PermitWaterS, met, FirstS), - FirstJ => IsMatchRoamerLocation(PermitWaterJ, met, FirstJ), - FirstH => IsMatchRoamerLocation(PermitWaterH, met, FirstH), - _ => false, - }; - - protected override bool IsMatchEggLocation(PKM pk) - { - if (!EggEncounter) - return base.IsMatchEggLocation(pk); - - var eggloc = pk.Egg_Location; - // Transferring 4->5 clears Pt/HG/SS location value and keeps Faraway Place - if (pk is not G4PKM pk4) - { - if (eggloc == Locations.LinkTrade4) - return true; - var cmp = Locations.IsPtHGSSLocationEgg(EggLocation) ? Locations.Faraway4 : EggLocation; - return eggloc == cmp; - } - - if (!pk4.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade4; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pk4.Met_Location is not (0 or Locations.LinkTrade4)) - return false; - return true; - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - SanityCheckVersion(pk); - } - - private void SanityCheckVersion(PKM pk) - { - // Unavailable encounters in DP, morph them to Pt so they're legal. - switch (Species) - { - case (int)Core.Species.Darkrai when Location == 079: // DP Darkrai - case (int)Core.Species.Shaymin when Location == 063: // DP Shaymin - pk.Version = (int)GameVersion.Pt; - return; - } - } - - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - if (pk.Format != 4) // Met Level lost on PK4=>PK5 - return Level <= evo.LevelMax; - - return pk.Met_Level == (EggEncounter ? 0 : Level); - } - - protected override bool IsMatchPartial(PKM pk) - { - if (Gift && pk.Ball != Ball) - return true; - return base.IsMatchPartial(pk); - } - - protected override void SetMetData(PKM pk, int level, DateTime today) - { - var pk4 = (PK4)pk; - pk4.GroundTile = Roaming ? GroundTileType.Grass : GroundTile.GetIndex(); - pk.Met_Location = Location; - pk.Met_Level = level; - pk.MetDate = DateOnly.FromDateTime(today); - } - - public static bool IsMatchRoamerLocation(ulong permit, int location, int first) - { - var value = location - first; - if ((uint)value >= 64) - return false; - return (permit & (1ul << value)) != 0; - } - - public static bool IsMatchRoamerLocation(uint permit, int location, int first) - { - var value = location - first; - if ((uint)value >= 32) - return false; - return (permit & (1u << value)) != 0; - } - - // Merged all locations into a bitmask for quick computation. - private const int FirstS = 16; - private const ulong PermitGrassS = 0x2_8033FFFF; // 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, 47, 49, - private const ulong PermitWaterS = 0x2_803E3B9E; // 18, 19, 20, 23, 24, 25, 27, 28, 29, 33, 34, 35, 36, 37, 47, 49, - - private const int FirstJ = 177; - private const uint PermitGrassJ = 0x0003E7FF; // 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 190, 191, 192, 193, 194, - private const uint PermitWaterJ = 0x0001E06E; // 178, 179, 180, 182, 183, 190, 191, 192, 193, - - private const int FirstH = 149; - private const uint PermitGrassH = 0x0AB3FFFF; // 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 169, 170, 172, 174, 176, - private const uint PermitWaterH = 0x0ABC1B28; // 152, 154, 157, 158, 160, 161, 167, 168, 169, 170, 172, 174, 176, -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4Pokewalker.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4Pokewalker.cs deleted file mode 100644 index e892f1c38..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4Pokewalker.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using static System.Buffers.Binary.BinaryPrimitives; - -namespace PKHeX.Core; - -/// -/// Generation 4 Pokéwalker Encounter -/// -/// -public sealed record EncounterStatic4Pokewalker : EncounterStatic -{ - public override int Generation => 4; - public override EntityContext Context => EntityContext.Gen4; - public PokewalkerCourse4 Course { get; } - - public EncounterStatic4Pokewalker(ReadOnlySpan data, PokewalkerCourse4 course) : base(GameVersion.HGSS) - { - Species = ReadUInt16LittleEndian(data); - Level = data[2]; - Gender = (sbyte)data[3]; - Course = course; - var move1 = ReadUInt16LittleEndian(data[0x4..]); - var move2 = ReadUInt16LittleEndian(data[0x6..]); - var move3 = ReadUInt16LittleEndian(data[0x8..]); - var move4 = ReadUInt16LittleEndian(data[0xA..]); - Moves = new(move1, move2, move3, move4); - - // All obtained entities are in Poke Ball and have a met location of "PokeWalker" - Gift = true; - Location = Locations.PokeWalker4; - } - - protected override bool IsMatchLocation(PKM pk) - { - if (pk.Format == 4) - return Location == pk.Met_Location; - return true; // transfer location verified later - } - - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - if (pk.Format != 4) // Met Level lost on PK4=>PK5 - return Level <= evo.LevelMax; - - return pk.Met_Level == Level; - } - - protected override bool IsMatchPartial(PKM pk) - { - if (pk.Ball != 4) - return true; - return base.IsMatchPartial(pk); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(Gender, pi); - int nature = (int)criteria.GetNature(Nature.Random); - - // Cannot force an ability; nature-gender-trainerID only yield fixed PIDs. - // int ability = criteria.GetAbilityFromNumber(Ability, pi); - - PIDGenerator.SetRandomPIDPokewalker(pk, nature, gender); - criteria.SetRandomIVs(pk); - } - - public static EncounterStatic4Pokewalker[] GetAll(ReadOnlySpan data) - { - const int size = 0xC; - var count = data.Length / size; - System.Diagnostics.Debug.Assert(count == 6 * (int)PokewalkerCourse4.MAX_COUNT); - var result = new EncounterStatic4Pokewalker[count]; - for (int i = 0; i < result.Length; i++) - { - var offset = i * size; - var slice = data[offset..]; - var course = (PokewalkerCourse4)(i / 6); - result[i] = new(slice, course); - } - return result; - } -} - -public enum PokewalkerCourse4 : byte -{ - RefreshingField = 0, - NoisyForest = 1, - RuggedRoad = 2, - BeautifulBeach = 3, - SuburbanArea = 4, - DimCave = 5, - BlueLake = 6, - TownOutskirts = 7, - HoennField = 8, - WarmBeach = 9, - VolcanoPath = 10, - Treehouse = 11, - ScaryCave = 12, - SinnohField = 13, - IcyMountainRoad = 14, - BigForest = 15, - WhiteLake = 16, - StormyBeach = 17, - Resort = 18, - QuietCave = 19, - BeyondTheSea = 20, - NightSkysEdge = 21, - YellowForest = 22, - Rally = 23, // JPN Exclusive - Sightseeing = 24, // JPN/KOR Exclusive - WinnersPath = 25, - AmityMeadow = 26, // JPN Exclusive - MAX_COUNT = 27, -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs deleted file mode 100644 index b3018e668..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 5 Static Encounter -/// -/// -public record EncounterStatic5(GameVersion Version) : EncounterStatic(Version) -{ - public sealed override int Generation => 5; - public override EntityContext Context => EntityContext.Gen5; - public bool Roaming { get; init; } - public bool IsWildCorrelationPID => !Roaming && Shiny == Shiny.Random && Species != (int)Core.Species.Crustle; - - public bool EntreeForestDreamWorld => Location == 75; - - protected sealed override bool IsMatchPartial(PKM pk) - { - // BW/2 Jellicent collision with wild surf slot, resolved by duplicating the encounter with any abil - if (Ability == AbilityPermission.OnlyHidden && pk.AbilityNumber != 4 && pk.Format <= 7) - return true; - return base.IsMatchPartial(pk); - } - - protected sealed override bool IsMatchLocation(PKM pk) - { - if (!Roaming) - return base.IsMatchLocation(pk); - return IsRoamerMet(pk.Met_Location); - } - - protected override bool IsMatchEggLocation(PKM pk) - { - if (!EggEncounter) - return base.IsMatchEggLocation(pk); - - var eggloc = pk.Egg_Location; - if (!pk.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade5; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pk.Met_Location is not (0 or Locations.LinkTrade5)) - return false; - return true; - } - - // 25,26,27,28, // Route 12, 13, 14, 15 Night latter half - // 15,16,31, // Route 2, 3, 18 Morning - // 17,18,29, // Route 4, 5, 16 Daytime - // 19,20,21, // Route 6, 7, 8 Evening - // 22,23,24, // Route 9, 10, 11 Night former half - private static bool IsRoamerMet(int location) - { - if ((uint)location >= 32) - return false; - return (0b10111111111111111000000000000000 & (1 << location)) != 0; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5DR.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5DR.cs deleted file mode 100644 index 8c894e7fa..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5DR.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 5 Dream Radar gift encounters -/// -/// -public sealed record EncounterStatic5DR : EncounterStatic5 -{ - public EncounterStatic5DR(ushort species, byte form, AbilityPermission ability = AbilityPermission.OnlyHidden) : base(GameVersion.B2W2) - { - Species = species; - Form = form; - Ability = ability; - Location = 30015; - Gift = true; - Ball = 25; - Level = 5; // to 40 - Shiny = Shiny.Never; - } - - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - // Level from 5->40 depends on the number of badges - var met = pk.Met_Level; - if (met % 5 != 0) - return false; - return (uint) (met - 5) <= 35; // 5 <= x <= 40 - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs deleted file mode 100644 index 5502af5f3..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 5 Static Encounter from N -/// -/// -internal sealed record EncounterStatic5N : EncounterStatic5 -{ - public readonly uint PID; - public const bool NSparkle = true; - - internal EncounterStatic5N(uint pid) : base(GameVersion.B2W2) - { - Shiny = Shiny.FixedValue; - PID = pid; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pk.PersonalInfo); - int nature = (int)Nature; - int ability = Ability.IsSingleValue(out var index) ? index : 0; - - pk.PID = PID; - pk.Gender = gender; - SetIVs(pk); - - pk.Nature = nature; - pk.RefreshAbility(ability); - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (PID != pk.PID) - return false; - return base.IsMatchExact(pk, evo); - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - SetNPokemonData((PK5)pk, pk.Language); - } - - private static void SetNPokemonData(PK5 pk5, int lang) - { - pk5.IV_HP = pk5.IV_ATK = pk5.IV_DEF = pk5.IV_SPA = pk5.IV_SPD = pk5.IV_SPE = 30; - pk5.NSparkle = NSparkle; - pk5.OT_Name = GetOT(lang); - pk5.ID32 = 00002; - } - - public static string GetOT(int lang) => lang == (int)LanguageID.Japanese ? "N" : "N"; -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs deleted file mode 100644 index c89e3f661..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 6 Static Encounter -/// -/// -public sealed record EncounterStatic6(GameVersion Version) : EncounterStatic(Version), IContestStatsReadOnly -{ - public override int Generation => 6; - public override EntityContext Context => EntityContext.Gen6; - - public byte CNT_Cool { get; init; } - public byte CNT_Beauty { get; init; } - public byte CNT_Cute { get; init; } - public byte CNT_Smart { get; init; } - public byte CNT_Tough { get; init; } - public byte CNT_Sheen { get; init; } - - protected override bool IsMatchLocation(PKM pk) - { - if (base.IsMatchLocation(pk)) - return true; - - if (Species != (int) Core.Species.Pikachu) - return false; - - // Cosplay Pikachu is given from multiple locations - var loc = pk.Met_Location; - return loc is 180 or 186 or 194; - } - - protected override bool IsMatchEggLocation(PKM pk) - { - if (!EggEncounter) - return base.IsMatchEggLocation(pk); - - var eggloc = pk.Egg_Location; - if (!pk.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade6; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pk.Met_Location is not (0 or Locations.LinkTrade6)) - return false; - return true; - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - var pk6 = (PK6)pk; - this.CopyContestStatsTo(pk6); - pk6.SetRandomMemory6(); - pk6.SetRandomEC(); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs deleted file mode 100644 index 706aaa49e..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs +++ /dev/null @@ -1,101 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 7 Static Encounter -/// -/// -public sealed record EncounterStatic7(GameVersion Version) : EncounterStatic(Version), IRelearn, IEncounterFormRandom -{ - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7; - public Moveset Relearn { get; init; } - - public bool IsTotem => FormInfo.IsTotemForm(Species, Form); - public bool IsTotemNoTransfer => Species is (int)Core.Species.Marowak or (int)Core.Species.Araquanid or (int)Core.Species.Togedemaru or (int)Core.Species.Ribombee; - public int GetTotemBaseForm() => FormInfo.GetTotemBaseForm(Species, Form); - - public bool IsRandomUnspecificForm => Form >= FormDynamic; - private const int FormDynamic = FormVivillon; - internal const int FormVivillon = 30; - //protected const int FormRandom = 31; - - protected override bool IsMatchLocation(PKM pk) - { - if (EggLocation == Locations.Daycare5 && !Relearn.HasMoves && pk.RelearnMove1 != 0) // Gift Eevee edge case - return false; - return base.IsMatchLocation(pk); - } - - protected override bool IsMatchEggLocation(PKM pk) - { - if (!EggEncounter) - return base.IsMatchEggLocation(pk); - - var eggloc = pk.Egg_Location; - if (!pk.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade6; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pk.Met_Location is not (0 or Locations.LinkTrade6)) - return false; - return true; - } - - protected override bool IsMatchForm(PKM pk, EvoCriteria evo) - { - if (IsRandomUnspecificForm) - return true; - - if (IsTotem) - { - var expectForm = pk.Format == 7 ? Form : FormInfo.GetTotemBaseForm(Species, Form); - return expectForm == evo.Form; - } - return base.IsMatchForm(pk, evo); - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - if (Species == (int)Core.Species.Magearna && pk is IRibbonSetEvent4 e4) - e4.RibbonWishing = true; - if (Form == FormVivillon && pk is PK7 pk7) - pk.Form = Vivillon3DS.GetPattern(pk7.Country, pk7.Region); - pk.SetRandomEC(); - } - - internal static EncounterStatic7 GetVC1(ushort species, byte metLevel) - { - bool mew = species == (int)Core.Species.Mew; - return new EncounterStatic7(GameVersion.RBY) - { - Species = species, - Gift = true, // Forces Poké Ball - Ability = TransporterLogic.IsHiddenDisallowedVC1(species) ? AbilityPermission.OnlyFirst : AbilityPermission.OnlyHidden, // Hidden by default, else first - Shiny = mew ? Shiny.Never : Shiny.Random, - FatefulEncounter = mew, - Location = Locations.Transfer1, - Level = metLevel, - FlawlessIVCount = mew ? (byte)5 : (byte)3, - }; - } - - internal static EncounterStatic7 GetVC2(ushort species, byte metLevel) - { - bool mew = species == (int)Core.Species.Mew; - bool fateful = mew || species == (int)Core.Species.Celebi; - return new EncounterStatic7(GameVersion.GSC) - { - Species = species, - Gift = true, // Forces Poké Ball - Ability = TransporterLogic.IsHiddenDisallowedVC2(species) ? AbilityPermission.OnlyFirst : AbilityPermission.OnlyHidden, // Hidden by default, else first - Shiny = mew ? Shiny.Never : Shiny.Random, - FatefulEncounter = fateful, - Location = Locations.Transfer2, - Level = metLevel, - FlawlessIVCount = fateful ? (byte)5 : (byte)3, - }; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7b.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7b.cs deleted file mode 100644 index 03bf5c562..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7b.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 7 Static Encounter ( -/// -/// -public sealed record EncounterStatic7b(GameVersion Version) : EncounterStatic(Version) -{ - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7b; - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - pk.SetRandomEC(); - var pb = (PB7)pk; - pb.ResetHeight(); - pb.ResetWeight(); - pb.ResetCP(); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs deleted file mode 100644 index 48abcc65e..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs +++ /dev/null @@ -1,136 +0,0 @@ -using static PKHeX.Core.OverworldCorrelation8Requirement; - -namespace PKHeX.Core; - -/// -/// Generation 8 Static Encounter -/// -/// -public sealed record EncounterStatic8(GameVersion Version = GameVersion.SWSH) : EncounterStatic(Version), IDynamaxLevelReadOnly, IGigantamaxReadOnly, IRelearn, IOverworldCorrelation8 -{ - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8; - public bool ScriptedNoMarks { get; init; } - public bool CanGigantamax { get; init; } - public byte DynamaxLevel { get; init; } - public Moveset Relearn { get; init; } - public Crossover8 Crossover { get; init; } - - public AreaWeather8 Weather { get; init; } = AreaWeather8.Normal; - - protected override bool IsMatchLocation(PKM pk) - { - var met = pk.Met_Location; - if (met == Location) - return true; - if ((uint)met > byte.MaxValue) - return false; - return Crossover.IsMatchLocation((byte)met); - } - - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - var met = pk.Met_Level; - var lvl = Level; - if (met == lvl) - return true; - if (lvl < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60(Location)) - return met == EncounterArea8.BoostLevel; - return false; - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) - return false; - if (pk.Met_Level < EncounterArea8.BoostLevel && Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) - return false; - return base.IsMatchExact(pk, evo); - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) - pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel; - - var req = GetRequirement(pk); - if (req != MustHave) - { - pk.SetRandomEC(); - return; - } - var shiny = Shiny == Shiny.Random ? Shiny.FixedValue : Shiny; - Overworld8RNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount); - } - - public bool IsOverworldCorrelation - { - get - { - if (Gift) - return false; // gifts can have any 128bit seed from overworld - if (ScriptedNoMarks) - return false; // scripted encounters don't act as saved spawned overworld encounters - return true; - } - } - - public OverworldCorrelation8Requirement GetRequirement(PKM pk) => IsOverworldCorrelation - ? MustHave - : MustNotHave; - - public bool IsOverworldCorrelationCorrect(PKM pk) - { - return Overworld8RNG.ValidateOverworldEncounter(pk, Shiny == Shiny.Random ? Shiny.FixedValue : Shiny, FlawlessIVCount); - } - - public override EncounterMatchRating GetMatchRating(PKM pk) - { - var rating = base.GetMatchRating(pk); - if (rating != EncounterMatchRating.Match) - return rating; - - var req = GetRequirement(pk); - bool correlation = IsOverworldCorrelationCorrect(pk); - if ((req == MustHave) != correlation) - return EncounterMatchRating.DeferredErrors; - - // Only encounter slots can have these marks; defer for collisions. - if (pk.Species == (int) Core.Species.Shedinja) - { - // Loses Mark on evolution to Shedinja, but not affixed ribbon value. - return pk switch - { - IRibbonSetMark8 {RibbonMarkCurry: true} => EncounterMatchRating.DeferredErrors, - PK8 {AffixedRibbon: (int) RibbonIndex.MarkCurry} => EncounterMatchRating.Deferred, - _ => EncounterMatchRating.Match, - }; - } - - if (pk is IRibbonSetMark8 m && (m.RibbonMarkCurry || m.RibbonMarkFishing || m.HasWeatherMark())) - return EncounterMatchRating.DeferredErrors; - - return EncounterMatchRating.Match; - } -} - -public interface IOverworldCorrelation8 -{ - OverworldCorrelation8Requirement GetRequirement(PKM pk); - bool IsOverworldCorrelationCorrect(PKM pk); -} - -public enum OverworldCorrelation8Requirement -{ - CanBeEither, - MustHave, - MustNotHave, -} - -public readonly record struct Crossover8(byte L1 = 0, byte L2 = 0, byte L3 = 0, byte L4 = 0, byte L5 = 0, byte L6 = 0, byte L7 = 0) -{ - public bool IsMatchLocation(byte location) => location != 0 && L1 != 0 && ( - location == L1 || location == L2 || location == L3 || - location == L4 || location == L5 || location == L6 || location == L7); -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs deleted file mode 100644 index 28a214daf..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using static PKHeX.Core.Encounters8Nest; -using static PKHeX.Core.AbilityPermission; - -namespace PKHeX.Core; - -/// -/// Generation 8 Nest Encounter (Raid) -/// -/// -public abstract record EncounterStatic8Nest(GameVersion Version) : EncounterStatic(Version), IGigantamaxReadOnly, IDynamaxLevelReadOnly where T : EncounterStatic8Nest -{ - public sealed override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8; - public static Func? VerifyCorrelation { private get; set; } - public static Action? GenerateData { private get; set; } - - public bool CanGigantamax { get; init; } - public byte DynamaxLevel { get; init; } - public override int Location { get => SharedNest; init { } } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) - return false; - - // Required Ability - if (Ability == OnlyHidden && pk.AbilityNumber != 4) - return false; // H - - if (Version != GameVersion.SWSH && pk.Version != (int)Version && pk.Met_Location != SharedNest) - return false; - - if (VerifyCorrelation != null && !VerifyCorrelation(pk, (T)this)) - return false; - - if (pk is IRibbonSetMark8 { HasMarkEncounter8: true }) - return false; - if (pk.Species == (int)Core.Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon: >= (int)RibbonIndex.MarkLunchtime }) - return false; - - return base.IsMatchExact(pk, evo); - } - - protected sealed override EncounterMatchRating IsMatchDeferred(PKM pk) - { - if (Ability != Any12H) - { - // HA-Only is a strict match. Ability Capsule and Patch can potentially change these. - var num = pk.AbilityNumber; - if (num == 4) - { - if (Ability is not OnlyHidden && !AbilityVerifier.CanAbilityPatch(8, PersonalTable.SWSH.GetFormEntry(Species, Form), pk.Species)) - return EncounterMatchRating.DeferredErrors; - } - else if (Ability.IsSingleValue(out int index) && 1 << index != num) // Fixed regular ability - { - if (Ability is OnlyFirst or OnlySecond && !AbilityVerifier.CanAbilityCapsule(8, PersonalTable.SWSH.GetFormEntry(Species, Form))) - return EncounterMatchRating.DeferredErrors; - } - } - - return base.IsMatchDeferred(pk); - } - - protected override bool IsMatchPartial(PKM pk) - { - if (pk is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pk.Species, pk.Form, Species, Form)) - return true; - if (Species == (int)Core.Species.Alcremie && pk is IFormArgument { FormArgument: not 0 }) - return true; - if (Species == (int)Core.Species.Runerigus && pk is IFormArgument { FormArgument: not 0 }) - return true; - - switch (Shiny) - { - case Shiny.Never when pk.IsShiny: - case Shiny.Always when !pk.IsShiny: - return true; - } - - return base.IsMatchPartial(pk); - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - if (GenerateData == null) - pk.SetRandomEC(); - } - - protected sealed override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - if (GenerateData != null) - { - GenerateData(pk, (T)this, criteria); - return; - } - - base.SetPINGA(pk, criteria); - if (Species == (int) Core.Species.Toxtricity) - { - while (true) - { - var result = EvolutionMethod.GetAmpLowKeyResult(pk.Nature); - if (result == pk.Form) - break; - pk.Nature = Util.Rand.Next(25); - } - - // Might be originally generated with a Neutral nature, then above logic changes to another. - // Realign the stat nature to Serious mint. - if (pk.Nature != pk.StatNature && ((Nature)pk.StatNature).IsNeutral()) - pk.StatNature = (int)Nature.Serious; - } - var pid = pk.PID; - RaidRNG.ForceShinyState(pk, Shiny == Shiny.Always, ref pid); - pk.PID = pid; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs deleted file mode 100644 index 42497236e..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Generation 8 Static Encounter -/// -/// -public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Version), IAlphaReadOnly, IMasteryInitialMoveShop8, IScaledSizeReadOnly -{ - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8a; - - public byte HeightScalar { get; } - public byte WeightScalar { get; } - public bool IsAlpha { get; init; } - public EncounterStatic8aCorrelation Method { get; init; } - - public bool HasFixedHeight => HeightScalar != NoScalar; - public bool HasFixedWeight => WeightScalar != NoScalar; - private const byte NoScalar = 0; - - public EncounterStatic8a(ushort species, byte form, byte level, byte h = NoScalar, byte w = NoScalar) : this(GameVersion.PLA) - { - Species = species; - Form = form; - Level = level; - HeightScalar = h; - WeightScalar = w; - Shiny = Shiny.Never; - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - - var pa = (PA8)pk; - - if (IsAlpha) - pa.IsAlpha = true; - - if (HasFixedHeight) - pa.HeightScalar = HeightScalar; - if (HasFixedWeight) - pa.WeightScalar = WeightScalar; - pa.Scale = pa.HeightScalar; - - pa.ResetHeight(); - pa.ResetWeight(); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var para = GetParams(); - var (_, slotSeed) = Overworld8aRNG.ApplyDetails(pk, criteria, para, IsAlpha); - // We don't override LevelMin, so just handle the two species cases. - if (Species == (int)Core.Species.Zorua) - pk.CurrentLevel = pk.Met_Level = Overworld8aRNG.GetRandomLevel(slotSeed, 27, 29); - else if (Species == (int)Core.Species.Phione) - pk.CurrentLevel = pk.Met_Level = Overworld8aRNG.GetRandomLevel(slotSeed, 33, 36); - - if (Method == EncounterStatic8aCorrelation.Fixed) - pk.EncryptionConstant = Util.Rand32(); - } - - protected override void ApplyDetailsBall(PKM pk) => pk.Ball = Gift ? Ball : (int)Core.Ball.LAPoke; - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (!base.IsMatchExact(pk, evo)) - return false; - - if (pk is IScaledSize s) - { - // 3 of the Alpha statics were mistakenly set as 127 scale. If they enter HOME on 3.0.1, they'll get bumped to 255. - if (IsAlpha && this is { HeightScalar: 127, WeightScalar: 127 }) // Average Size Alphas - { - // HOME >=3.0.1 ensures 255 scales for the 127's - // PLA and S/V could have safe-harbored them via <=3.0.0 - if (pk.Context is EntityContext.Gen8a or EntityContext.Gen9) - { - if (s is not { HeightScalar: 127, WeightScalar: 127 }) // Original? - { - // Must match the HOME updated values AND must have the Alpha ribbon (visited HOME). - if (s is not { HeightScalar: 255, WeightScalar: 255 }) - return false; - if (pk is IRibbonSetMark9 { RibbonMarkAlpha: false }) - return false; - if (pk.IsUntraded) - return false; - } - } - else - { - // Must match the HOME updated values - if (s is not { HeightScalar: 255, WeightScalar: 255 }) - return false; - } - } - else - { - if (HasFixedHeight && s.HeightScalar != HeightScalar) - return false; - if (HasFixedWeight && s.WeightScalar != WeightScalar) - return false; - } - } - - if (pk is IAlpha a && a.IsAlpha != IsAlpha) - return false; - - return true; - } - - protected override bool IsMatchLocation(PKM pk) - { - var metState = LocationsHOME.GetRemapState(Context, pk.Context); - if (metState == LocationRemapState.Original) - return base.IsMatchLocation(pk); - if (metState == LocationRemapState.Remapped) - return IsMetRemappedSWSH(pk); - return base.IsMatchLocation(pk) || IsMetRemappedSWSH(pk); - } - - private static bool IsMetRemappedSWSH(PKM pk) => pk.Met_Location == LocationsHOME.SWLA; - - public override EncounterMatchRating GetMatchRating(PKM pk) - { - var result = GetMatchRatingInternal(pk); - var orig = base.GetMatchRating(pk); - return result > orig ? result : orig; - } - - private EncounterMatchRating GetMatchRatingInternal(PKM pk) - { - if (Shiny != Shiny.Random && !Shiny.IsValid(pk)) - return EncounterMatchRating.DeferredErrors; - if (Gift && pk.Ball != Ball) - return EncounterMatchRating.DeferredErrors; - - var orig = base.GetMatchRating(pk); - if (orig is not EncounterMatchRating.Match) - return orig; - - if (!IsForcedMasteryCorrect(pk)) - return EncounterMatchRating.DeferredErrors; - - if (!MarkRules.IsMarkValidAlpha(pk, IsAlpha)) - return EncounterMatchRating.DeferredErrors; - - if (IsAlpha && pk is PA8 { AlphaMove: 0 }) - return EncounterMatchRating.Deferred; - - return EncounterMatchRating.Match; - } - - public bool IsForcedMasteryCorrect(PKM pk) - { - ushort alpha = 0; - if (IsAlpha && Moves.HasMoves) - { - if (pk is PA8 pa && (alpha = pa.AlphaMove) != Moves.Move1) - return false; - } - - if (pk is not IMoveShop8Mastery p) - return true; - - const bool allowAlphaPurchaseBug = true; // Everything else Alpha is pre-1.1 - var level = pk.Met_Level; - var (learn, mastery) = GetLevelUpInfo(); - if (!p.IsValidPurchasedEncounter(learn, level, alpha, allowAlphaPurchaseBug)) - return false; - - Span moves = stackalloc ushort[4]; - if (Moves.HasMoves) - Moves.CopyTo(moves); - else - learn.SetEncounterMoves(level, moves); - - return p.IsValidMasteredEncounter(moves, learn, mastery, level, alpha, allowAlphaPurchaseBug); - } - - protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - var pa8 = (PA8)pk; - Span moves = stackalloc ushort[4]; - var (learn, mastery) = GetLevelUpInfo(); - LoadInitialMoveset(pa8, moves, learn, level); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - pa8.SetEncounterMasteryFlags(moves, mastery, level); - if (pa8.AlphaMove != 0) - pa8.SetMasteryFlagMove(pa8.AlphaMove); - } - - public (Learnset Learn, Learnset Mastery) GetLevelUpInfo() - { - return LearnSource8LA.GetLearnsetAndMastery(Species, Form); - } - - public void LoadInitialMoveset(PA8 pa8, Span moves, Learnset learn, int level) - { - if (Moves.HasMoves) - Moves.CopyTo(moves); - else - learn.SetEncounterMoves(level, moves); - if (IsAlpha) - pa8.AlphaMove = moves[0]; - } - - private OverworldParam8a GetParams() - { - var gender = GetGenderRatio(); - return new OverworldParam8a - { - IsAlpha = IsAlpha, - FlawlessIVs = FlawlessIVCount, - Shiny = Shiny, - RollCount = 1, // Everything is shiny locked anyways - GenderRatio = gender, - }; - } - - private byte GetGenderRatio() => Gender switch - { - 0 => PersonalInfo.RatioMagicMale, - 1 => PersonalInfo.RatioMagicFemale, - _ => GetGenderRatioPersonal(), - }; - - private byte GetGenderRatioPersonal() - { - var pt = PersonalTable.LA; - var entry = pt.GetFormEntry(Species, Form); - return entry.Gender; - } -} - -public enum EncounterStatic8aCorrelation : byte -{ - WildGroup, - Fixed, -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs deleted file mode 100644 index 3f15989a5..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using static PKHeX.Core.StaticCorrelation8bRequirement; - -namespace PKHeX.Core; - -/// -/// Generation 8 Static Encounter -/// -/// -public sealed record EncounterStatic8b : EncounterStatic, IStaticCorrelation8b -{ - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8b; - - public bool Roaming { get; init; } - public override bool EggEncounter => EggLocation != Locations.Default8bNone; - - public EncounterStatic8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone; - - protected override bool IsMatchLocation(PKM pk) - { - var metState = LocationsHOME.GetRemapState(Context, pk.Context); - if (metState == LocationRemapState.Original) - return IsMatchLocationExact(pk); - if (metState == LocationRemapState.Remapped) - return IsMatchLocationRemapped(pk); - return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); - } - - private bool IsMatchLocationRemapped(PKM pk) - { - var met = (ushort)pk.Met_Location; - var version = pk.Version; - if (pk.Context == EntityContext.Gen8) - return LocationsHOME.IsValidMetBDSP(met, version); - return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; - } - - private bool IsMatchLocationExact(PKM pk) - { - if (!Roaming) - return base.IsMatchLocation(pk); - return IsRoamingLocation(pk); - } - - private static bool IsRoamingLocation(PKM pk) - { - var location = pk.Met_Location; - foreach (var value in Roaming_MetLocation_BDSP) - { - if (value == location) - return true; - } - return false; - } - - public StaticCorrelation8bRequirement GetRequirement(PKM pk) => Roaming - ? MustHave - : MustNotHave; - - public bool IsStaticCorrelationCorrect(PKM pk) - { - return Roaming8bRNG.ValidateRoamingEncounter(pk, Shiny == Shiny.Random ? Shiny.FixedValue : Shiny, FlawlessIVCount); - } - - protected override bool IsMatchEggLocation(PKM pk) - { - var metState = LocationsHOME.GetRemapState(Context, pk.Context); - if (metState == LocationRemapState.Original) - return IsMatchEggLocationExact(pk); - if (metState == LocationRemapState.Remapped) - return IsMatchEggLocationRemapped(pk); - // Either - return IsMatchEggLocationExact(pk) || IsMatchEggLocationRemapped(pk); - } - - private bool IsMatchEggLocationRemapped(PKM pk) - { - if (!EggEncounter) - return pk.Egg_Location == 0; - return LocationsHOME.IsLocationSWSHEgg(pk.Version, pk.Met_Location, pk.Egg_Location, (ushort)EggLocation); - } - - private bool IsMatchEggLocationExact(PKM pk) - { - var eggloc = pk.Egg_Location; - if (!EggEncounter) - return eggloc == EggLocation; - - if (!pk.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade6NPC; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pk.Met_Location is not (Locations.Default8bNone or Locations.LinkTrade6NPC)) - return false; - return true; - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - pk.Met_Location = pk.Egg_Location = Locations.Default8bNone; - base.ApplyDetails(tr, criteria, pk); - var req = GetRequirement(pk); - if (req == MustHave) // Roamers - { - var shiny = Shiny == Shiny.Random ? Shiny.FixedValue : Shiny; - Roaming8bRNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount); - } - else - { - var shiny = Shiny == Shiny.Never ? Shiny.Never : Shiny.Random; - Wild8bRNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount, Ability); - } - } - - protected override void SetMetData(PKM pk, int level, DateTime today) - { - pk.Met_Level = level; - pk.Met_Location = !Roaming ? Location : Roaming_MetLocation_BDSP[0]; - pk.MetDate = DateOnly.FromDateTime(today); - } - - // defined by mvpoke in encounter data - private static ReadOnlySpan Roaming_MetLocation_BDSP => new ushort[] - { - 197, 201, 354, 355, 356, 357, 358, 359, 361, 362, 364, 365, 367, 373, 375, 377, - 378, 379, 383, 385, 392, 394, 395, 397, 400, 403, 404, 407, - 485, - }; -} - -public interface IStaticCorrelation8b -{ - StaticCorrelation8bRequirement GetRequirement(PKM pk); - bool IsStaticCorrelationCorrect(PKM pk); -} - -public enum StaticCorrelation8bRequirement -{ - CanBeEither, - MustHave, - MustNotHave, -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic9.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic9.cs deleted file mode 100644 index 42aef69bd..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic9.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 9 Static Encounter -/// -/// -public sealed record EncounterStatic9(GameVersion Version) : EncounterStatic(Version), IGemType -{ - public override int Generation => 9; - public override EntityContext Context => EntityContext.Gen9; - public byte Size { get; init; } - public GemType TeraType { get; init; } - public bool IsTitan { get; init; } - - private bool NoScalarsDefined => Size == 0; - public bool GiftWithLanguage => Gift && !ScriptedYungoos; // Nice error by GameFreak -- all gifts (including eggs) set the HT_Language memory value in addition to OT_Language. - public bool StarterBoxLegend => Gift && Species is (int)Core.Species.Koraidon or (int)Core.Species.Miraidon; - public bool ScriptedYungoos => Species == (int)Core.Species.Yungoos && Level == 2; - - public SizeType9 ScaleType => NoScalarsDefined ? SizeType9.RANDOM : SizeType9.VALUE; - - protected override bool IsMatchLocation(PKM pk) - { - var metState = LocationsHOME.GetRemapState(Context, pk.Context); - if (metState == LocationRemapState.Original) - return IsMatchLocationExact(pk); - if (metState == LocationRemapState.Remapped) - return IsMatchLocationRemapped(pk); - return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); - } - - private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; - - private bool IsMatchLocationRemapped(PKM pk) - { - var met = (ushort)pk.Met_Location; - var version = pk.Version; - if (pk.Context == EntityContext.Gen8) - return LocationsHOME.IsValidMetSV(met, version); - return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; - } - - protected override bool IsMatchPartial(PKM pk) - { - if (pk is IScaledSize v && !NoScalarsDefined) - { - if (Gift) - { - if (v.HeightScalar != Size) - return true; - if (v.WeightScalar != Size) - return true; - } - var current = pk is IScaledSize3 size3 ? size3.Scale : v.HeightScalar; - if (current != Size) - return false; - } - return base.IsMatchPartial(pk); - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal)) - return false; - return base.IsMatchExact(pk, evo); - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - var pk9 = (PK9)pk; - if (Gift && !ScriptedYungoos) - pk9.HT_Language = (byte)pk.Language; - if (StarterBoxLegend) - pk9.FormArgument = 1; // Not Ride Form. - if (IsTitan) - pk9.RibbonMarkTitan = true; - pk9.Obedience_Level = (byte)pk9.Met_Level; - - const byte undefinedSize = 0; - byte height, weight, scale; - if (NoScalarsDefined) - { - height = weight = scale = undefinedSize; - } - else - { - // Gifts have a defined H/W/S, while capture-able only have scale. - height = weight = Gift ? Size : undefinedSize; - scale = Size; - } - - const byte rollCount = 1; - var pi = PersonalTable.SV.GetFormEntry(Species, Form); - var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, rollCount, height, weight, ScaleType, scale, Ability, Shiny); - - ulong init = Util.Rand.Rand64(); - var success = this.TryApply64(pk9, init, param, criteria, IVs.IsSpecified); - if (!success) - this.TryApply64(pk9, init, param, EncounterCriteria.Unrestricted, IVs.IsSpecified); - if (IVs.IsSpecified) - { - pk.IV_HP = IVs.HP; - pk.IV_ATK = IVs.ATK; - pk.IV_DEF = IVs.DEF; - pk.IV_SPA = IVs.SPA; - pk.IV_SPD = IVs.SPD; - pk.IV_SPE = IVs.SPE; - } - if (Nature != Nature.Random) - pk.Nature = pk.StatNature = (int)Nature; - if (Gender != -1) - pk.Gender = (byte)Gender; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStaticShadow.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStaticShadow.cs deleted file mode 100644 index 32b2d2e10..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStaticShadow.cs +++ /dev/null @@ -1,117 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Shadow Pokémon Encounter found in -/// -/// -/// Initial Shadow Gauge value. -/// Initial Shadow Gauge value. -/// Team Specification with required , and Gender. -// ReSharper disable NotAccessedPositionalProperty.Global -public sealed record EncounterStaticShadow(GameVersion Version, byte ID, short Gauge, TeamLock[] Locks) : EncounterStatic(Version) -{ - // ReSharper restore NotAccessedPositionalProperty.Global - public override int Generation => 3; - public override EntityContext Context => EntityContext.Gen3; - - /// - /// Originates from the EReader scans (Japanese Only) - /// - public bool EReader => Location == 128; // @ Card e Room (Japanese games only) - - protected override bool IsMatchLocation(PKM pk) - { - if (pk.Format != 3) - return true; // transfer location verified later - - var met = pk.Met_Location; - if (met == Location) - return true; - - // XD can re-battle with Miror B - // Realgam Tower, Rock, Oasis, Cave, Pyrite Town - return Version == GameVersion.XD && met is (59 or 90 or 91 or 92 or 113); - } - - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - if (pk.Format != 3) // Met Level lost on PK3=>PK4 - return Level <= evo.LevelMax; - - return pk.Met_Level == Level; - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - ((IRibbonSetEvent3)pk).RibbonNational = true; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - if (!EReader) - SetPINGA_Regular(pk, criteria); - else - SetPINGA_EReader(pk); - } - - private void SetPINGA_Regular(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - int ability = criteria.GetAbilityFromNumber(0); - - // Ensure that any generated specimen has valid Shadow Locks - // This can be kinda slow, depending on how many locks / how strict they are. - // Cancel this operation if too many attempts are made to prevent infinite loops. - int ctr = 0; - const int max = 100_000; - do - { - PIDGenerator.SetRandomWildPID(pk, 3, nature, ability, gender, PIDType.CXD); - var pidiv = MethodFinder.Analyze(pk); - var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); - if (result) - break; - } - while (++ctr <= max); - -#if DEBUG - System.Diagnostics.Debug.Assert(ctr < 100_000); -#endif - } - - private void SetPINGA_EReader(PKM pk) - { - // E-Reader have all IVs == 0 - for (int i = 0; i < 6; i++) - pk.SetIV(i, 0); - - // All E-Reader shadows are actually nature/gender locked. - var locked = Locks[0].Locks[^1]; - var (nature, gender) = locked.GetLock; - - // Ensure that any generated specimen has valid Shadow Locks - // This can be kinda slow, depending on how many locks / how strict they are. - // Cancel this operation if too many attempts are made to prevent infinite loops. - int ctr = 0; - const int max = 100_000; - do - { - var seed = Util.Rand32(); - PIDGenerator.SetValuesFromSeedXDRNG_EReader(pk, seed); - if (pk.Nature != nature || pk.Gender != gender) - continue; - var pidiv = new PIDIV(PIDType.CXD, seed); - var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); - if (result) - break; - } - while (++ctr <= max); - -#if DEBUG - System.Diagnostics.Debug.Assert(ctr < 100_000); -#endif - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs deleted file mode 100644 index 8c2aa5ef4..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PKHeX.Core; - -/// -/// Trade Encounter data -/// -/// -/// Trade data is fixed level in all cases except for the first few generations of games. -/// -public abstract record EncounterTrade(GameVersion Version) : IEncounterable, IMoveset, IEncounterMatch -{ - public ushort Species { get; init; } - public byte Form { get; init; } - public byte Level { get; init; } - public virtual byte LevelMin => Level; - public byte LevelMax => 100; - public abstract int Generation { get; } - public abstract EntityContext Context { get; } - - public int CurrentLevel { get; init; } = -1; - public abstract int Location { get; } - - public AbilityPermission Ability { get; init; } - public Nature Nature { get; init; } = Nature.Random; - public virtual Shiny Shiny => Shiny.Never; - public sbyte Gender { get; init; } = -1; - - public sbyte OTGender { get; init; } = -1; - public bool IsNicknamed { get; init; } = true; - public bool EvolveOnTrade { get; init; } - public byte Ball { get; init; } = 4; - - public int EggLocation { get; init; } - - public ushort TID16 { get; init; } - public ushort SID16 { get; init; } - - public Moveset Moves { get; init; } - public IndividualValueSet IVs { get; init; } - - public Ball FixedBall => (Ball)Ball; - public bool EggEncounter => false; - - public int TID7 - { - init - { - TID16 = (ushort) value; - SID16 = (ushort)(value >> 16); - } - } - - private const string _name = "In-game Trade"; - public string Name => _name; - public string LongName => _name; - public bool IsShiny => Shiny.IsShiny(); - - public IReadOnlyList Nicknames { get; internal set; } = Array.Empty(); - public IReadOnlyList TrainerNames { get; internal set; } = Array.Empty(); - public string GetNickname(int language) => (uint)language < Nicknames.Count ? Nicknames[language] : string.Empty; - public string GetOT(int language) => (uint)language < TrainerNames.Count ? TrainerNames[language] : string.Empty; - public bool HasNickname => Nicknames.Count != 0 && IsNicknamed; - public bool HasTrainerName => TrainerNames.Count != 0; - - public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); - - public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) - { - var pk = EntityBlank.GetBlank(Generation, Version); - tr.ApplyTo(pk); - - ApplyDetails(tr, criteria, pk); - return pk; - } - - protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - var version = this.GetCompatibleVersion((GameVersion)sav.Game); - int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)sav.Language, version); - int level = CurrentLevel > 0 ? CurrentLevel : LevelMin; - if (level == 0) - level = Math.Max((byte)1, LevelMin); - - ushort species = Species; - if (EvolveOnTrade) - species++; - - pk.EncryptionConstant = Util.Rand32(); - pk.Species = species; - pk.Form = Form; - pk.Language = lang; - pk.OT_Name = pk.Format == 1 ? StringConverter12.G1TradeOTStr : HasTrainerName ? GetOT(lang) : sav.OT; - pk.OT_Gender = HasTrainerName ? Math.Max(0, (int)OTGender) : sav.Gender; - pk.SetNickname(HasNickname ? GetNickname(lang) : string.Empty); - - pk.CurrentLevel = level; - pk.Version = (int) version; - pk.TID16 = TID16; - pk.SID16 = SID16; - pk.Ball = Ball; - pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - - SetPINGA(pk, criteria); - SetMoves(pk, version, level); - - var time = DateTime.Now; - if (pk.Format != 2 || version == GameVersion.C) - { - SetMetData(pk, level, Location, time); - } - else - { - pk.OT_Gender = 0; - } - - if (EggLocation != 0) - SetEggMetData(pk, time); - - if (pk.Format < 6) - return; - - sav.ApplyHandlingTrainerInfo(pk, force: true); - pk.SetRandomEC(); - - if (pk is IScaledSize s) - { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - if (pk is PK6 pk6) - pk6.SetRandomMemory6(); - } - - protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(Gender, pi); - int nature = (int)criteria.GetNature(Nature); - int ability = criteria.GetAbilityFromNumber(Ability); - - PIDGenerator.SetRandomWildPID(pk, Generation, nature, ability, gender); - pk.Nature = pk.StatNature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - - SetIVs(pk); - } - - protected void SetIVs(PKM pk) - { - if (IVs.IsSpecified) - pk.SetRandomIVsTemplate(IVs, 0); - else - pk.SetRandomIVs(minFlawless: 3); - } - - private void SetMoves(PKM pk, GameVersion version, int level) - { - if (Moves.HasMoves) - { - pk.SetMoves(Moves); - pk.SetMaximumPPCurrent(Moves); - } - else - { - Span moves = stackalloc ushort[4]; - var source = GameData.GetLearnSource(version); - source.SetEncounterMoves(Species, Form, level, moves); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } - } - - private void SetEggMetData(PKM pk, DateTime time) - { - pk.Egg_Location = EggLocation; - pk.EggMetDate = DateOnly.FromDateTime(time); - } - - private static void SetMetData(PKM pk, int level, int location, DateTime time) - { - pk.Met_Level = level; - pk.Met_Location = location; - pk.MetDate = DateOnly.FromDateTime(time); - } - - public virtual bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (IVs.IsSpecified) - { - if (!Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) - return false; - } - - if (!IsMatchNatureGenderShiny(pk)) - return false; - if (TID16 != pk.TID16) - return false; - if (SID16 != pk.SID16) - return false; - - if (!IsMatchLevel(pk, evo)) - return false; - - if (CurrentLevel != -1 && CurrentLevel > pk.CurrentLevel) - return false; - - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) - return false; - if (OTGender != -1 && OTGender != pk.OT_Gender) - return false; - if (!IsMatchEggLocation(pk)) - return false; - // if (z.Ability == 4 ^ pk.AbilityNumber == 4) // defer to Ability - // continue; - - int version = pk.Version; - if (Generation >= 8 && Context != EntityContext.Gen8 && pk is PK8) - version = LocationsHOME.GetVersionSWSHOriginal((ushort)pk.Met_Location); - if (!Version.Contains((GameVersion)version)) - return false; - - return true; - } - - protected virtual bool IsMatchEggLocation(PKM pk) - { - var expect = EggLocation; - if (pk is PB8 && expect is 0) - expect = Locations.Default8bNone; - return pk.Egg_Location == expect; - } - - private bool IsMatchLevel(PKM pk, EvoCriteria evo) - { - if (!pk.HasOriginalMetLocation) - return evo.LevelMax >= Level; - - if (Location != pk.Met_Location) - { - if (!LocationsHOME.IsMatchLocation(Context, pk.Context, pk.Met_Location, Location, LocationsHOME.GetVersionSWSHOriginal((ushort)pk.Met_Location))) - return false; - } - - if (pk.Format < 5) - return evo.LevelMax >= Level; - - return pk.Met_Level == Level; - } - - protected virtual bool IsMatchNatureGenderShiny(PKM pk) - { - if (!Shiny.IsValid(pk)) - return false; - if (Gender != -1 && Gender != pk.Gender) - return false; - - if (Nature != Nature.Random && pk.Nature != (int)Nature) - return false; - - return true; - } - - public EncounterMatchRating GetMatchRating(PKM pk) - { - if (IsMatchPartial(pk)) - return EncounterMatchRating.PartialMatch; - if (IsMatchDeferred(pk)) - return EncounterMatchRating.Deferred; - return EncounterMatchRating.Match; - } - - protected virtual bool IsMatchDeferred(PKM pk) => false; - protected virtual bool IsMatchPartial(PKM pk) => false; -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade1.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade1.cs deleted file mode 100644 index 6abfd7eef..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade1.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Linq; - -namespace PKHeX.Core; - -/// -/// Trade Encounter data with a fixed Catch Rate -/// -/// -/// Generation 1 specific value used in detecting unmodified/un-traded Generation 1 Trade Encounter data. -/// Species & Minimum level (legal) possible to acquire at. -/// -public sealed record EncounterTrade1 : EncounterTradeGB -{ - public override int Generation => 1; - public override EntityContext Context => EntityContext.Gen1; - public override byte LevelMin => CanObtainMinGSC() ? LevelMinGSC : LevelMinRBY; - - private readonly byte LevelMinRBY; - private readonly byte LevelMinGSC; - public override int Location => 0; - public override Shiny Shiny => Shiny.Random; - - public EncounterTrade1(ushort species, GameVersion game, byte rby, byte gsc) : base(species, gsc, game) - { - TrainerNames = StringConverter12.G1TradeOTName; - - LevelMinRBY = rby; - LevelMinGSC = gsc; - } - - public EncounterTrade1(ushort species, GameVersion game, byte rby) : this(species, game, rby, rby) { } - - public byte GetInitialCatchRate() - { - var pt = Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; - return (byte)pt[Species].CatchRate; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pk1 = (PK1)pk; - pk1.Catch_Rate = GetInitialCatchRate(); - } - - internal bool IsNicknameValid(PKM pk) - { - var nick = pk.Nickname; - if (pk.Format <= 2) - return Nicknames.Contains(nick); - - // Converted string 1/2->7 to language specific value - // Nicknames can be from any of the languages it can trade between. - int lang = pk.Language; - if (lang == 1) - { - // Special consideration for Hiragana strings that are transferred - if (Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio) - return nick == "ぐりお"; - return nick == Nicknames[1]; - } - - return GetNicknameIndex(nick) >= 2; - } - - internal bool IsTrainerNameValid(PKM pk) - { - string ot = pk.OT_Name; - if (pk.Format <= 2) - return ot == StringConverter12.G1TradeOTStr; - - // Converted string 1/2->7 to language specific value - int lang = pk.Language; - var tr = GetOT(lang); - return ot == tr; - } - - private int GetNicknameIndex(string nickname) - { - var nn = Nicknames; - for (int i = 0; i < nn.Count; i++) - { - if (nn[i] == nickname) - return i; - } - return -1; - } - - private bool CanObtainMinGSC() - { - if (!ParseSettings.AllowGen1Tradeback) - return false; - if (Version == GameVersion.BU && EvolveOnTrade) - return ParseSettings.AllowGBCartEra; - return true; - } - - private bool IsMatchLevel(PKM pk, int lvl) - { - if (pk is not PK1) - return lvl >= LevelMinGSC; - return lvl >= LevelMin; - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (!IsMatchLevel(pk, pk.CurrentLevel)) // minimum required level - return false; - - if (Version == GameVersion.BU) - { - // Encounters with this version have to originate from the Japanese Blue game. - if (!pk.Japanese) - return false; - // Stadium 2 can transfer from GSC->RBY without a "Trade", thus allowing un-evolved outsiders - if (EvolveOnTrade && !ParseSettings.AllowGBCartEra && pk.CurrentLevel < LevelMinRBY) - return false; - } - - return true; - } - - protected override bool IsMatchPartial(PKM pk) - { - if (!IsTrainerNameValid(pk)) - return true; - if (!IsNicknameValid(pk)) - return true; - - if (ParseSettings.AllowGen1Tradeback) - return false; - if (pk is not PK1 pk1) - return false; - - var req = GetInitialCatchRate(); - return req != pk1.Catch_Rate; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade2.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade2.cs deleted file mode 100644 index 02faa80eb..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade2.cs +++ /dev/null @@ -1,99 +0,0 @@ -using static PKHeX.Core.Species; - -namespace PKHeX.Core; - -/// -/// Generation 2 Trade Encounter -/// -/// -public sealed record EncounterTrade2 : EncounterTradeGB -{ - public override int Generation => 2; - public override EntityContext Context => EntityContext.Gen2; - public override int Location => Locations.LinkTrade2NPC; - - public EncounterTrade2(ushort species, byte level, ushort tid) : base(species, level, GameVersion.GSC) - { - TID16 = tid; - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (Level > pk.CurrentLevel) // minimum required level - return false; - if (TID16 != pk.TID16) - return false; - - if (pk.Format <= 2) - { - if (Gender >= 0 && Gender != pk.Gender) - return false; - if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) - return false; - if (pk is { Format: 2, Met_Location: not (0 or 126) }) - return false; - } - - if (!IsValidTradeOTGender(pk)) - return false; - return IsValidTradeOTName(pk); - } - - private bool IsValidTradeOTGender(PKM pk) - { - if (OTGender == 1) - { - // Female, can be cleared if traded to RBY (clears met location) - if (pk.Format <= 2) - return pk.OT_Gender == (pk.Met_Location != 0 ? 1 : 0); - return pk.OT_Gender == 0 || !pk.VC1; // require male except if transferred from GSC - } - return pk.OT_Gender == 0; - } - - private bool IsValidTradeOTName(PKM pk) - { - var OT = pk.OT_Name; - if (pk.Japanese) - return GetOT((int)LanguageID.Japanese) == OT; - if (pk.Korean) - return GetOT((int)LanguageID.Korean) == OT; - - var lang = GetInternationalLanguageID(OT); - if (pk.Format < 7) - return lang != -1; - - switch (Species) - { - case (int)Voltorb when pk.Language == (int)LanguageID.French: - if (lang == (int)LanguageID.Spanish) - return false; - if (lang != -1) - return true; - return OT == "FALCçN"; // FALCÁN - - case (int)Shuckle when pk.Language == (int)LanguageID.French: - if (lang == (int)LanguageID.Spanish) - return false; - if (lang != -1) - return true; - return OT == "MANôA"; // MANÍA - - default: return lang != -1; - } - } - - private int GetInternationalLanguageID(string OT) - { - const int start = (int)LanguageID.English; - const int end = (int)LanguageID.Spanish; - - var tr = TrainerNames; - for (int i = start; i <= end; i++) - { - if (tr[i] == OT) - return i; - } - return -1; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade3.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade3.cs deleted file mode 100644 index 5c0fef1ac..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade3.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; - -namespace PKHeX.Core; - -/// -/// Generation 3 Trade Encounter -/// -/// -public sealed record EncounterTrade3 : EncounterTrade, IContestStatsReadOnly -{ - public override int Generation => 3; - public override EntityContext Context => EntityContext.Gen3; - public override int Location => Locations.LinkTrade3NPC; - - /// - /// Fixed value the encounter must have. - /// - public readonly uint PID; - - public override Shiny Shiny => Shiny.FixedValue; - - public byte CNT_Cool { get; private init; } - public byte CNT_Beauty { get; private init; } - public byte CNT_Cute { get; private init; } - public byte CNT_Smart { get; private init; } - public byte CNT_Tough { get; private init; } - public byte CNT_Sheen { get; private init; } - - public ReadOnlySpan Contest - { - init - { - CNT_Cool = value[0]; - CNT_Beauty = value[1]; - CNT_Cute = value[2]; - CNT_Smart = value[3]; - CNT_Tough = value[4]; - CNT_Sheen = value[5]; - } - } - - public EncounterTrade3(GameVersion game, uint pid, ushort species, byte level) : base(game) - { - PID = pid; - Species = species; - Level = level; - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (!base.IsMatchExact(pk, evo)) - return false; - - if (pk is IContestStatsReadOnly s && s.IsContestBelow(this)) - return false; - - return true; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pk3 = (PK3) pk; - - // Italian LG Jynx untranslated from English name - if (Species == (int)Core.Species.Jynx && pk3 is { Version: (int)GameVersion.LG, Language: (int)LanguageID.Italian }) - { - pk3.OT_Name = GetOT((int)LanguageID.English); - pk3.SetNickname(GetNickname((int)LanguageID.English)); - } - - this.CopyContestStatsTo((PK3)pk); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pi); - int nature = (int)criteria.GetNature(Nature); - int ability = criteria.GetAbilityFromNumber(Ability); - - pk.PID = PID; - pk.Nature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - - SetIVs(pk); - } - - protected override bool IsMatchNatureGenderShiny(PKM pk) - { - return PID == pk.EncryptionConstant; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade4.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade4.cs deleted file mode 100644 index 33da7acae..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade4.cs +++ /dev/null @@ -1,169 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 4 Trade Encounter -/// -/// -public abstract record EncounterTrade4(GameVersion Version) : EncounterTrade(Version) -{ - public sealed override int Generation => 4; - public override EntityContext Context => EntityContext.Gen4; - - protected static readonly string[] RanchOTNames = { string.Empty, "ユカリ", "Hayley", "EULALIE", "GIULIA", "EUKALIA", string.Empty, "Eulalia" }; -} - -/// -/// Generation 4 Trade Encounter with a fixed PID value. -/// -/// -public sealed record EncounterTrade4PID : EncounterTrade4, IContestStatsReadOnly -{ - /// - /// Fixed value the encounter must have. - /// - public readonly uint PID; - - public override Shiny Shiny => Shiny.FixedValue; - - public EncounterTrade4PID(GameVersion game, uint pid, ushort species, byte level) : base(game) - { - PID = pid; - Species = species; - Level = level; - } - - public byte CNT_Cool { get; init; } - public byte CNT_Beauty { get; init; } - public byte CNT_Cute { get; init; } - public byte CNT_Smart { get; init; } - public byte CNT_Tough { get; init; } - public byte CNT_Sheen { get; init; } - - public byte Contest - { - init - { - CNT_Cool = value; - CNT_Beauty = value; - CNT_Cute = value; - CNT_Smart = value; - CNT_Tough = value; - //CNT_Sheen = value; - } - } - - public int MetLocation { get; init; } - public override int Location => MetLocation == default ? Locations.LinkTrade4NPC : MetLocation; - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (!base.IsMatchExact(pk, evo)) - return false; - - if (pk is IContestStatsReadOnly s && s.IsContestBelow(this)) - return false; - - return true; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pk4 = (PK4) pk; - - if (Version == GameVersion.DPPt) - { - // Has German Language ID for all except German origin, which is English - if (Species == (int)Core.Species.Magikarp) - pk4.Language = (int)(pk4.Language == (int)LanguageID.German ? LanguageID.English : LanguageID.German); - // All other trades received (DP only): English games have a Japanese language ID instead of English. - else if (pk4.Version is not (int)GameVersion.Pt && pk4.Language == (int)LanguageID.English) - pk4.Language = (int)LanguageID.Japanese; - } - else // HGSS - { - // Has English Language ID for all except English origin, which is French - if (Species == (int)Core.Species.Pikachu) - pk4.Language = (int)(pk4.Language == (int)LanguageID.English ? LanguageID.French : LanguageID.English); - } - - this.CopyContestStatsTo(pk4); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - pk.PID = PID; - pk.Nature = (int)(PID % 25); - pk.Gender = Gender; - pk.RefreshAbility(Ability.GetSingleValue()); - SetIVs(pk); - } - - protected override bool IsMatchNatureGenderShiny(PKM pk) - { - return PID == pk.EncryptionConstant; - } -} - -/// -/// Generation 4 Trade Encounter with a fixed PID value, met location, and version. -/// -/// -public sealed record EncounterTrade4RanchGift : EncounterTrade4 -{ - /// - /// Fixed value the encounter must have. - /// - public readonly uint PID; - - public int MetLocation { private get; init; } - public override int Location => MetLocation; - public override Shiny Shiny => Shiny.FixedValue; - - public EncounterTrade4RanchGift(uint pid, ushort species, byte level) : base(GameVersion.D) - { - PID = pid; - Species = species; - Level = level; - TrainerNames = RanchOTNames; - } - - protected override bool IsMatchNatureGenderShiny(PKM pk) - { - return PID == pk.EncryptionConstant; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - pk.PID = PID; - pk.Nature = (int)(PID % 25); - pk.Gender = Gender; - pk.RefreshAbility(Ability.GetSingleValue()); - SetIVs(pk); - } -} - -/// -/// Generation 4 Trade Encounter with a fixed location and version, as well as special details. -/// -/// -public sealed record EncounterTrade4RanchSpecial : EncounterTrade4, IFatefulEncounterReadOnly -{ - public override int Location => 3000; - public bool FatefulEncounter => true; - - public EncounterTrade4RanchSpecial(ushort species, byte level) : base(GameVersion.D) - { - Species = species; - Level = level; - Ball = 0x10; - OTGender = 1; - TrainerNames = RanchOTNames; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.FatefulEncounter = true; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade5.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade5.cs deleted file mode 100644 index de6fa20b6..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade5.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 5 Trade Encounter -/// -/// -public sealed record EncounterTrade5(GameVersion Version) : EncounterTrade(Version) -{ - public override int Generation => 5; - public override EntityContext Context => EntityContext.Gen5; - public override int Location => Locations.LinkTrade5NPC; -} - -/// Generation 5 Trade with Fixed PID -/// Fixed value the encounter must have. -public sealed record EncounterTrade5PID(GameVersion Version, uint PID) : EncounterTrade(Version) -{ - public override int Generation => 5; - public override EntityContext Context => EntityContext.Gen5; - public override int Location => Locations.LinkTrade5NPC; - - public override Shiny Shiny => Shiny.FixedValue; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - - // Trades for JPN games have language ID of 0, not 1. - if (pk.Language == (int) LanguageID.Japanese) - pk.Language = 0; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pi); - int nature = (int)criteria.GetNature(Nature); - int ability = criteria.GetAbilityFromNumber(Ability); - - pk.PID = PID; - pk.Nature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - - SetIVs(pk); - } - - protected override bool IsMatchNatureGenderShiny(PKM pk) - { - if (PID != pk.EncryptionConstant) - return false; - if (Nature != Nature.Random && (int)Nature != pk.Nature) // gen5 BW only - return false; - return true; - } - - public static bool IsValidMissingLanguage(PKM pk) - { - // Generation 5 trades from B/W forgot to set the Language ID, so it remains as 0. - // This value is fixed when it is transferred from PK5->PK6 - // B2/W2 is unaffected by this game data bug. - return pk is { Context: EntityContext.Gen5, BW: true }; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade6.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade6.cs deleted file mode 100644 index e37881a0e..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade6.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 6 Trade Encounter -/// -/// -public sealed record EncounterTrade6(GameVersion Version, byte OT_Memory, byte OT_Intensity, byte OT_Feeling, ushort OT_TextVar) : EncounterTrade(Version), IMemoryOTReadOnly -{ - public override int Generation => 6; - public override EntityContext Context => EntityContext.Gen6; - public override int Location => Locations.LinkTrade6NPC; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - if (pk is IMemoryOT o) - { - o.OT_Memory = OT_Memory; - o.OT_Intensity = OT_Intensity; - o.OT_Feeling = OT_Feeling; - o.OT_TextVar = OT_TextVar; - } - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7.cs deleted file mode 100644 index 0ff46776c..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 7 Trade Encounter -/// -/// -public sealed record EncounterTrade7(GameVersion Version) : EncounterTrade(Version), IMemoryOTReadOnly -{ - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7; - public override int Location => Locations.LinkTrade6NPC; - public byte OT_Memory => 1; - public byte OT_Intensity => 3; - public byte OT_Feeling => 5; - public ushort OT_TextVar => 40; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pk7 = (PK7)pk; - pk7.OT_Memory = OT_Memory; - pk7.OT_Intensity = OT_Intensity; - pk7.OT_Feeling = OT_Feeling; - pk7.OT_TextVar = OT_TextVar; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7b.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7b.cs deleted file mode 100644 index addaaa2de..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7b.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 7 LGP/E Trade Encounter -/// -/// -public sealed record EncounterTrade7b : EncounterTrade -{ - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7b; - public override int Location => Locations.LinkTrade6NPC; - public override Shiny Shiny => Shiny.Random; - - public EncounterTrade7b(GameVersion game) : base(game) => IsNicknamed = false; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.SetRandomEC(); - var pb = (PB7)pk; - pb.ResetHeight(); - pb.ResetWeight(); - pb.ResetCP(); - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs deleted file mode 100644 index 56acc3626..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 8 Trade Encounter -/// -/// -public sealed record EncounterTrade8 : EncounterTrade, IDynamaxLevelReadOnly, IRelearn, IMemoryOTReadOnly -{ - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8; - public override int Location => Locations.LinkTrade6NPC; - public Moveset Relearn { get; init; } - - public ushort OT_TextVar { get; } - public byte OT_Memory { get; } - public byte OT_Feeling { get; } - public byte OT_Intensity { get; } - public byte DynamaxLevel { get; init; } - public byte FlawlessIVCount { get; init; } - public override Shiny Shiny { get; } - - public EncounterTrade8(GameVersion game, ushort species, byte level, byte memory, ushort arg, byte feel, byte intensity, Shiny shiny = Shiny.Never) : base(game) - { - Species = species; - Level = level; - Shiny = shiny; - - OT_Memory = memory; - OT_TextVar = arg; - OT_Feeling = feel; - OT_Intensity = intensity; - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) - return false; - if (pk.FlawlessIVCount < FlawlessIVCount) - return false; - return base.IsMatchExact(pk, evo); - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.SetRelearnMoves(Relearn); - - var pk8 = (PK8)pk; - pk8.DynamaxLevel = DynamaxLevel; - pk8.HT_Language = (byte)sav.Language; - pk8.OT_Memory = OT_Memory; - pk8.OT_TextVar = OT_TextVar; - pk8.OT_Feeling = OT_Feeling; - pk8.OT_Intensity = OT_Intensity; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs deleted file mode 100644 index 2a351ee95..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 8 BD/SP Trade Encounter -/// -/// -public sealed record EncounterTrade8b : EncounterTrade, IContestStatsReadOnly, IScaledSizeReadOnly, IFixedOTFriendship -{ - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8b; - public override int Location => Locations.LinkTrade6NPC; - - public EncounterTrade8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone; - public byte CNT_Cool => BaseContest; - public byte CNT_Beauty => BaseContest; - public byte CNT_Cute => BaseContest; - public byte CNT_Smart => BaseContest; - public byte CNT_Tough => BaseContest; - public byte CNT_Sheen => 0; - public byte HeightScalar { get; init; } - public byte WeightScalar { get; init; } - public byte OT_Friendship => Species == (int)Core.Species.Chatot ? (byte)35 : (byte)50; - private byte BaseContest => Species == (int)Core.Species.Chatot ? (byte)20 : (byte)0; - public uint PID { get; init; } - public uint EncryptionConstant { get; init; } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (pk.EncryptionConstant != EncryptionConstant) - return false; - if (pk.PID != PID) - return false; - if (pk is IContestStatsReadOnly s && s.IsContestBelow(this)) - return false; - if (pk is IScaledSize h && h.HeightScalar != HeightScalar) - return false; - if (pk is IScaledSize w && w.WeightScalar != WeightScalar) - return false; - if (!IsMatchLocation(pk)) - return false; - return base.IsMatchExact(pk, evo); - } - - private bool IsMatchLocation(PKM pk) - { - var metState = LocationsHOME.GetRemapState(Context, pk.Context); - if (metState == LocationRemapState.Original) - return IsMatchLocationExact(pk); - if (metState == LocationRemapState.Remapped) - return IsMatchLocationRemapped(pk); - return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); - } - - private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; - - private bool IsMatchLocationRemapped(PKM pk) - { - var met = (ushort)pk.Met_Location; - var version = pk.Version; - if (pk.Context == EntityContext.Gen8) - return LocationsHOME.IsValidMetBDSP(met, version); - return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; - } - - protected override bool IsMatchEggLocation(PKM pk) - { - var metState = LocationsHOME.GetRemapState(Context, pk.Context); - if (metState == LocationRemapState.Original) - return IsMatchEggLocationExact(pk); - if (metState == LocationRemapState.Remapped) - return IsMatchEggLocationRemapped(pk); - // Either - return IsMatchEggLocationExact(pk) || IsMatchEggLocationRemapped(pk); - } - - private static bool IsMatchEggLocationRemapped(PKM pk) => pk.Egg_Location == 0; - private bool IsMatchEggLocationExact(PKM pk) => pk.Egg_Location == EggLocation; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pb8 = (PB8)pk; - pb8.EncryptionConstant = EncryptionConstant; - pb8.PID = PID; - - // Has German Language ID for all except German origin, which is Japanese - if (Species == (int)Core.Species.Magikarp) - pb8.Language = (int)(pb8.Language == (int)LanguageID.German ? LanguageID.Japanese : LanguageID.German); - - this.CopyContestStatsTo(pb8); - pb8.HT_Language = (byte)sav.Language; - pb8.HeightScalar = HeightScalar; - pb8.WeightScalar = WeightScalar; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade9.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade9.cs deleted file mode 100644 index fe428bc14..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade9.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Generation 9 Trade Encounter -/// -/// -public sealed record EncounterTrade9 : EncounterTrade, IGemType -{ - public override int Generation => 9; - public override EntityContext Context => EntityContext.Gen9; - public override int Location => Locations.LinkTrade6NPC; - public override Shiny Shiny { get; } - - public GemType TeraType => GemType.Default; - - public EncounterTrade9(GameVersion game, ushort species, byte level, Shiny shiny = Shiny.Never) : base(game) - { - Species = species; - Level = level; - Shiny = shiny; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - - var pk9 = (PK9)pk; - pk9.HT_Language = (byte)pk.Language; - pk9.Obedience_Level = Level; - pk9.SetRandomEC(); - if (TeraType is GemType.Default) - pk9.TeraTypeOriginal = (MoveType)PersonalTable.SV.GetFormEntry(Species, Form).Type1; - else if (TeraType.IsSpecified(out var type)) - pk9.TeraTypeOriginal = (MoveType)type; - else - pk9.TeraTypeOriginal = (MoveType)Util.Rand.Next(0, 18); - pk9.Scale = PokeSizeUtil.GetRandomScalar(); - } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal)) - return false; - if (!base.IsMatchExact(pk, evo)) - return false; - if (!IsMatchLocation(pk)) - return false; - return true; - } - - - private bool IsMatchLocation(PKM pk) - { - var metState = LocationsHOME.GetRemapState(Context, pk.Context); - if (metState == LocationRemapState.Original) - return IsMatchLocationExact(pk); - if (metState == LocationRemapState.Remapped) - return IsMatchLocationRemapped(pk); - return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); - } - - private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; - - private bool IsMatchLocationRemapped(PKM pk) - { - var met = (ushort)pk.Met_Location; - var version = pk.Version; - if (pk.Context == EntityContext.Gen8) - return LocationsHOME.IsValidMetSV(met, version); - return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; - } -} diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTradeGB.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTradeGB.cs deleted file mode 100644 index 5956a26c6..000000000 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTradeGB.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PKHeX.Core; - -/// -public abstract record EncounterTradeGB : EncounterTrade -{ - protected EncounterTradeGB(ushort species, byte level, GameVersion game) : base(game) - { - Species = species; - Level = level; - } - - public abstract override bool IsMatchExact(PKM pk, EvoCriteria evo); -} diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator1.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator1.cs index 880f0dc89..d40b920ab 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator1.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator1.cs @@ -1,104 +1,17 @@ using System; using System.Collections.Generic; -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; - namespace PKHeX.Core; public sealed class EncounterGenerator1 : IEncounterGenerator { public static readonly EncounterGenerator1 Instance = new(); - public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) + public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = GetGifts(); - foreach (var enc in GetPossibleGifts(chain, table)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - var table = GetTrade(game); - foreach (var enc in GetPossibleTrades(chain, table)) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = GetStatic(game); - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = GetAreas(game, pk.Japanese); - foreach (var area in GetPossibleSlots(chain, areas)) - yield return area; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, EncounterStatic1E[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, EncounterTrade1[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic1[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea1[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } + var iterator = new EncounterPossible1(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) @@ -114,100 +27,13 @@ public sealed class EncounterGenerator1 : IEncounterGenerator var chain = EncounterOrigin.GetOriginChain12(pk, game); if (chain.Length == 0) return Array.Empty(); - return GetEncounters(pk, chain, game); + return GetEncounters(pk, chain); } - private IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, GameVersion game) + private static IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain) { - IEncounterable? deferred = null; - - foreach (var enc in GetTrade(game)) - { - if (!(enc.Version.Contains(game) || game.Contains(enc.Version))) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match != Match) - deferred = enc; - else - yield return enc; - break; - } - } - foreach (var enc in GetStatic(game)) - { - if (!(enc.Version.Contains(game) || game.Contains(enc.Version))) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (enc.IsMatchExact(pk, evo)) - yield return enc; - break; - } - } - if (CanBeWildEncounter(pk)) - { - foreach (var area in GetAreas(game, pk.Japanese)) - { - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - yield return slot; - } - } - foreach (var enc in GetGifts()) - { - if (!(enc.Version.Contains(game) || game.Contains(enc.Version))) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (enc.IsMatchExact(pk, evo)) - yield return enc; - break; - } - } - - if (deferred != null) - yield return deferred; + var iterator = new EncounterEnumerator1(pk, chain); + foreach (var enc in iterator) + yield return enc.Encounter; } - - private static EncounterStatic1E[] GetGifts() - { - if (!ParseSettings.AllowGBCartEra) - return Encounters1.StaticEventsVC; - return Encounters1.StaticEventsGB; - } - - private static EncounterArea1[] GetAreas(GameVersion game, bool japanese) => game switch - { - GameVersion.RD => Encounters1.SlotsRD, - GameVersion.GN => Encounters1.SlotsGN, - GameVersion.BU => japanese ? Encounters1.SlotsBU : Array.Empty(), - GameVersion.YW => Encounters1.SlotsYW, - - _ when japanese => Encounters1.SlotsRGBY, - _ => Encounters1.SlotsRBY, - }; - - private static EncounterStatic1[] GetStatic(GameVersion game) => game switch - { - _ => Encounters1.StaticRBY, - }; - - private static EncounterTrade1[] GetTrade(GameVersion game) => game switch - { - _ => Encounters1.TradeGift_RBY, - }; } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator2.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator2.cs index 68cca2369..0318ad765 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator2.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator2.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; namespace PKHeX.Core; @@ -13,102 +10,9 @@ public sealed class EncounterGenerator2 : IEncounterGenerator public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - bool korean = pk.Korean; - if (groups.HasFlag(Mystery)) - { - var table = GetGifts(korean); - foreach (var enc in GetPossibleGifts(chain, table)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - var table = GetTrade(game); - foreach (var enc in GetPossibleTrades(chain, table)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(pk, chain); - foreach (var egg in eggs) - yield return egg; - } - if (groups.HasFlag(Static)) - { - var table = GetStatic(game, korean); - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = GetAreas(game, korean); - foreach (var enc in GetPossibleSlots(chain, areas, pk)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, EncounterStatic2E[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, EncounterTrade2[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic2[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea2[] areas, ITrainerID16 pk) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - - if (slot.IsHeadbutt && !slot.IsTreeAvailable(pk.TID16)) - break; - yield return slot; - break; - } - } - } + var iterator = new EncounterPossible2(chain, groups, game, pk); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) @@ -121,154 +25,20 @@ public sealed class EncounterGenerator2 : IEncounterGenerator var chain = EncounterOrigin.GetOriginChain12(pk, game); if (chain.Length == 0) return Array.Empty(); - return GetEncounters(pk, chain, game); + return GetEncounters(pk, chain); } - private IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, GameVersion game) + private static IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain) { - // Since encounter matching is super weak due to limited stored data in the structure - // Calculate all 3 at the same time and pick the best result (by species). - // Favor special event move gifts as Static Encounters when applicable - IEncounterable? deferred = null; - - bool korean = pk.Korean; - foreach (var enc in GetTrade(game)) - { - if (!(enc.Version.Contains(game) || game.Contains(enc.Version))) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match != Match) - deferred = enc; - else - yield return enc; - break; - } - } - foreach (var enc in GetStatic(game, korean)) - { - if (!(enc.Version.Contains(game) || game.Contains(enc.Version))) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (enc.IsMatchExact(pk, evo)) - yield return enc; - break; - } - } - if (CanBeWildEncounter(pk)) - { - foreach (var area in GetAreas(game, korean)) - { - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - yield return slot; - } - } - if (GetCanBeEgg(pk)) - { - foreach (var e in GetEggs(pk, chain)) - yield return e; - } - foreach (var enc in GetGifts(korean)) - { - if (!(enc.Version.Contains(game) || game.Contains(enc.Version))) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (enc.IsMatchExact(pk, evo)) - yield return enc; - break; - } - } - - if (deferred != null) - yield return deferred; + var iterator = new EncounterEnumerator2(pk, chain); + foreach (var enc in iterator) + yield return enc.Encounter; } - private static EncounterStatic2E[] GetGifts(bool korean) - { - if (korean) - return Array.Empty(); - if (!ParseSettings.AllowGBCartEra) - return Encounters2.StaticEventsVC; - return Encounters2.StaticEventsGB; - } - - private static EncounterArea2[] GetAreas(GameVersion game, bool korean) => game switch - { - GameVersion.GD => Encounters2.SlotsGD, - GameVersion.SI => Encounters2.SlotsSV, - GameVersion.C => !korean ? Encounters2.SlotsC : Array.Empty(), - GameVersion.GS => Encounters2.SlotsGS, - GameVersion.GSC => !korean ? Encounters2.SlotsGSC : Encounters2.SlotsGS, - _ => throw new ArgumentOutOfRangeException(nameof(game), game, null), - }; - - private static EncounterStatic2[] GetStatic(GameVersion game, bool korean) => game switch - { - GameVersion.GD => Encounters2.StaticGS, - GameVersion.SI => Encounters2.StaticGS, - GameVersion.C => !korean ? Encounters2.StaticC : Array.Empty(), - GameVersion.GS => Encounters2.StaticGS, - GameVersion.GSC => !korean ? Encounters2.StaticGSC : Encounters2.StaticGS, - _ => throw new ArgumentOutOfRangeException(nameof(game), game, null), - }; - - private static EncounterTrade2[] GetTrade(GameVersion game) => game switch - { - _ => Encounters2.TradeGift_GSC, - }; - private const int Generation = 2; private const EntityContext Context = EntityContext.Gen2; private const byte EggLevel = 5; - private static IEnumerable GetEggs(PKM pk, EvoCriteria[] chain) - { - var devolved = chain[^1]; - if (!devolved.InsideLevelRange(EggLevel)) - yield break; - - // Ensure most devolved species is the same as the egg species. - var (species, form) = GetBaby(devolved); - if (species != devolved.Species) - yield break; // no split-breed. - - // Sanity Check 1 - if (!Breeding.CanHatchAsEgg(species)) - yield break; - // Sanity Check 3 - if (!PersonalTable.C.IsPresentInGame(species, form)) - yield break; - - // Gen2 was before split-breed species existed; try to ensure that the egg we try and match to can actually originate in the game. - // Species must be < 251 - // Form must be 0 (Unown cannot breed). - if (form != 0) - yield break; // Forms don't exist in Gen2, besides Unown (which can't breed). Nothing can form-change. - - // Depending on the game it was hatched (GS vs C), met data will be present. - // Since met data can't be used to infer which game it was created on, we yield both if possible. - var egg = CreateEggEncounter(species, 0, GameVersion.GS); - yield return egg; - if (ParseSettings.AllowGen2Crystal(pk)) - yield return egg with { Version = GameVersion.C }; - } - private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version) { if (FormInfo.IsBattleOnlyForm(species, form, Generation)) @@ -281,28 +51,41 @@ public sealed class EncounterGenerator2 : IEncounterGenerator return EvolutionTree.Evolves2.GetBaseSpeciesForm(lowest.Species, lowest.Form); } - private static bool GetCanBeEgg(PKM pk) + public static bool TryGetEgg(ReadOnlySpan chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) { - if (pk.Format == 1 && !ParseSettings.AllowGen1Tradeback) + result = null; + var devolved = chain[^1]; + if (!devolved.InsideLevelRange(EggLevel)) return false; - if (pk.CurrentLevel < EggLevel) + // Ensure most devolved species is the same as the egg species. + var (species, form) = GetBaby(devolved); + if (species != devolved.Species) + return false; // not a split-breed. + + // Sanity Check 1 + if (!Breeding.CanHatchAsEgg(species)) + return false; + if (form != 0) + return false; // Forms don't exist in Gen2, besides Unown (which can't breed). Nothing can form-change. + // Sanity Check 3 + if (!PersonalTable.C.IsPresentInGame(species, form)) return false; - var format = pk.Format; - if (pk.IsEgg) - return format == 2; + result = CreateEggEncounter(species, form, version); + return true; + } - var metLevel = pk.Met_Level; - if (format > 2) - return metLevel >= EggLevel; - - // 2->1->2 clears met info - return metLevel switch + // Depending on the game it was hatched (GS vs C), met data will be present. + // Since met data can't be used to infer which game it was created on, we yield both if possible. + public static bool TryGetEggCrystal(PKM pk, EncounterEgg egg, [NotNullWhen(true)] out EncounterEgg? crystal) + { + if (!ParseSettings.AllowGen2Crystal(pk)) { - 0 => pk.Met_Location == 0, - 1 => true, // Met location of 0 is valid -- second floor of every Pokémon Center - _ => false, - }; + crystal = null; + return false; + } + crystal = egg with { Version = GameVersion.C }; + return true; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs index 5d8e32428..cb4c0c734 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; namespace PKHeX.Core; @@ -14,108 +11,9 @@ public sealed class EncounterGenerator3 : IEncounterGenerator public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncountersWC3.Encounter_WC3; - foreach (var enc in GetPossibleGifts(chain, table, game)) - yield return enc; - } - - if (groups.HasFlag(Trade)) - { - var table = GetTrades(game); - foreach (var enc in GetPossibleTrades(chain, table, game)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(chain, game); - foreach (var egg in eggs) - yield return egg; - } - if (groups.HasFlag(Slot)) - { - var areas = GetAreas(game); - foreach (var enc in GetPossibleAreas(chain, areas)) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = GetStatic(game); - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, WC3[] table, GameVersion game) - { - foreach (var enc in table) - { - if (!enc.Version.Contains(game)) - continue; - if (enc.NotDistributed) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, EncounterTrade3[] table, - GameVersion game) - { - foreach (var enc in table) - { - if (!enc.Version.Contains(game)) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleAreas(EvoCriteria[] chain, EncounterArea3[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic3[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible3(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, LegalInfo info) @@ -134,11 +32,18 @@ public sealed class EncounterGenerator3 : IEncounterGenerator foreach (var z in GetEncountersInner(pk, chain, info)) { - if (info.PIDIV.Type.IsCompatible3(z, pk)) + if (IsTypeCompatible(z, pk, info.PIDIV.Type)) yield return z; else partial ??= z; } + static bool IsTypeCompatible(IEncounterTemplate enc, PKM pk, PIDType type) + { + if (enc is IRandomCorrelation r) + return r.IsCompatible(type, pk); + return type == PIDType.None; + } + if (partial == null) yield break; @@ -149,157 +54,26 @@ public sealed class EncounterGenerator3 : IEncounterGenerator private static IEnumerable GetEncountersInner(PKM pk, EvoCriteria[] chain, LegalInfo info) { var game = (GameVersion)pk.Version; - - // Mystery Gifts - foreach (var z in EncountersWC3.Encounter_WC3) + var iterator = new EncounterEnumerator3(pk, chain, game); + EncounterSlot3? deferSlot = null; + List? frames = null; + foreach (var enc in iterator) { - if (!z.Version.Contains(game)) + var e = enc.Encounter; + if (e is not EncounterSlot3 s3) + { + yield return e; continue; - - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - // Don't bother deferring matches. - var match = z.GetMatchRating(pk); - if (match != PartialMatch) - yield return z; - break; - } - } - - // Trades - var trades = GetTrades(game); - foreach (var z in trades) - { - if (!z.Version.Contains(game)) - continue; - - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - // Don't bother deferring matches. - var match = z.GetMatchRating(pk); - if (match != PartialMatch) - yield return z; - break; - } - } - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - // Static Encounter - // Defer everything if Safari Ball - bool safari = pk.Ball == (byte)Ball.Safari; // never static encounters - if (!safari) - { - var table = GetStatic(game); - foreach (var z in table) - { - if (!z.Version.Contains(game)) - continue; - - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - if (match == PartialMatch) - partial ??= z; - else - yield return z; - break; - } - } - } - - // Encounter Slots - List? wildFrames = null; - if (CanBeWildEncounter(pk)) - { - wildFrames = AnalyzeFrames(pk, info); - var areas = GetAreas(game); - foreach (var area in areas) - { - var all = area.GetMatchingSlots(pk, chain); - foreach (var z in all) - { - var match = z.GetMatchRating(pk); - if (match == PartialMatch) - { - partial ??= z; - continue; - } - - var frame = wildFrames.Find(s => s.IsSlotCompatibile(z, pk)); - if (frame == null) - { - deferred ??= z; - continue; - } - yield return z; - } } - info.FrameMatches = false; - if (deferred is EncounterSlot3 x) - yield return x; + var wildFrames = frames ?? AnalyzeFrames(pk, info); + var frame = wildFrames.Find(s => s.IsSlotCompatibile(s3, pk)); + if (frame != null) + yield return s3; + deferSlot ??= s3; } - - // Due to the lack of Egg Met Location, eggs can be confused with Slots. Yield them now. - var eggs = GetEggs(chain, game); - foreach (var z in eggs) - yield return z; - - if (partial is EncounterSlot3 y) - { - wildFrames ??= AnalyzeFrames(pk, info); - var frame = wildFrames.Find(s => s.IsSlotCompatibile(y, pk)); - info.FrameMatches = frame != null; - yield return y; - } - - // Do static encounters if they were deferred to end, spit out any possible encounters for invalid pk - if (!safari) - yield break; - - partial = null; - - var encStatic = GetStatic(game); - foreach (var z in encStatic) - { - if (!z.Version.Contains(game)) - continue; - - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - if (match == PartialMatch) - partial ??= z; - else - yield return z; - break; - } - } - - if (partial is not null) - yield return partial; + if (deferSlot != null) + yield return deferSlot; } private static List AnalyzeFrames(PKM pk, LegalInfo info) @@ -307,79 +81,10 @@ public sealed class EncounterGenerator3 : IEncounterGenerator return FrameFinder.GetFrames(info.PIDIV, pk).ToList(); } - private static EncounterStatic3[] GetStatic(GameVersion gameSource) => gameSource switch - { - GameVersion.R => Encounters3RSE.StaticR, - GameVersion.S => Encounters3RSE.StaticS, - GameVersion.E => Encounters3RSE.StaticE, - GameVersion.FR => Encounters3FRLG.StaticFR, - GameVersion.LG => Encounters3FRLG.StaticLG, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - - private static EncounterArea3[] GetAreas(GameVersion gameSource) => gameSource switch - { - GameVersion.R => Encounters3RSE.SlotsR, - GameVersion.S => Encounters3RSE.SlotsS, - GameVersion.E => Encounters3RSE.SlotsE, - GameVersion.FR => Encounters3FRLG.SlotsFR, - GameVersion.LG => Encounters3FRLG.SlotsLG, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - - private static EncounterTrade3[] GetTrades(GameVersion gameSource) => gameSource switch - { - GameVersion.R => Encounters3RSE.TradeGift_RSE, - GameVersion.S => Encounters3RSE.TradeGift_RSE, - GameVersion.E => Encounters3RSE.TradeGift_RSE, - GameVersion.FR => Encounters3FRLG.TradeGift_FRLG, - GameVersion.LG => Encounters3FRLG.TradeGift_FRLG, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - private const int Generation = 3; private const EntityContext Context = EntityContext.Gen3; private const byte EggLevel = 5; - private static IEnumerable GetEggs(EvoCriteria[] chain, GameVersion version) - { - var devolved = chain[^1]; - if (!devolved.InsideLevelRange(EggLevel)) - yield break; - - // Ensure most devolved species is the same as the egg species. - var (species, form) = GetBaby(devolved); - if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies3(devolved.Species)) - yield break; // no split-breed. - - // Sanity Check 1 - if (!Breeding.CanHatchAsEgg(species)) - yield break; - // Sanity Check 2 - if (!Breeding.CanHatchAsEgg(species, form, Context)) - yield break; - // Sanity Check 3 - if (!PersonalTable.E.IsPresentInGame(species, form)) - yield break; - - yield return CreateEggEncounter(species, form, version); - // Version is not updated when hatching an Egg in Gen3. Version is a clear indicator of the game it originated on. - - // Check for split-breed - if (species == devolved.Species) - { - if (chain.Length < 2) - yield break; // no split-breed - devolved = chain[^2]; - } - if (!Breeding.IsSplitBreedNotBabySpecies3(devolved.Species)) - yield break; - - species = devolved.Species; - form = devolved.Form; - yield return CreateEggEncounter(species, form, version); - } - private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version) { if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Castform) @@ -391,4 +96,50 @@ public sealed class EncounterGenerator3 : IEncounterGenerator { return EvolutionTree.Evolves3.GetBaseSpeciesForm(lowest.Species, lowest.Form); } + + public static bool TryGetEgg(ReadOnlySpan chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + var devolved = chain[^1]; + if (!devolved.InsideLevelRange(EggLevel)) + return false; + + // Ensure most devolved species is the same as the egg species. + var (species, form) = GetBaby(devolved); + if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies3(devolved.Species)) + return false; // not a split-breed. + + // Sanity Check 1 + if (!Breeding.CanHatchAsEgg(species)) + return false; + // Sanity Check 2 + if (!Breeding.CanHatchAsEgg(species, form, Context)) + return false; + // Sanity Check 3 + if (!PersonalTable.E.IsPresentInGame(species, form)) + return false; + + result = CreateEggEncounter(species, form, version); + return true; + } + + // Version is not updated when hatching an Egg in Gen3. Version is a clear indicator of the game it originated on. + + public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan chain, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + // Check for split-breed + var devolved = chain[^1]; + if (other.Species == devolved.Species) + { + if (chain.Length < 2) + return false; // no split-breed + devolved = chain[^2]; + } + if (!Breeding.IsSplitBreedNotBabySpecies3(devolved.Species)) + return false; + + result = other with { Species = devolved.Species, Form = devolved.Form }; + return true; + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3GC.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3GC.cs index 4192733c1..0eba66366 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3GC.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3GC.cs @@ -1,71 +1,17 @@ using System; using System.Collections.Generic; -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; - namespace PKHeX.Core; public sealed class EncounterGenerator3GC : IEncounterGenerator { public static readonly EncounterGenerator3GC Instance = new(); - public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) + public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion __, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncountersWC3.Encounter_WC3CXD; - foreach (var enc in GetPossible(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = Encounters3XD.SlotsXD; - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - if (groups.HasFlag(Static)) - { - foreach (var enc in GetPossible(chain, Encounters3XD.Encounter_CXDShadow)) - yield return enc; - foreach (var enc in GetPossible(chain, Encounters3XD.Encounter_CXDGift)) - yield return enc; - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea3XD[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossible(EvoCriteria[] chain, T[] table) where T : class, IEncounterable - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible3GC(chain, groups); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, LegalInfo info) @@ -82,23 +28,29 @@ public sealed class EncounterGenerator3GC : IEncounterGenerator info.PIDIV = MethodFinder.Analyze(pk); foreach (var z in IterateInner(pk, chain)) { - if (z is EncounterSlot3PokeSpot w) + if (z is EncounterSlot3XD w) { var pidiv = MethodFinder.GetPokeSpotSeedFirst(pk, w.SlotNumber); if (pidiv.Type == PIDType.PokeSpot) info.PIDIV = pidiv; } - else if (z is EncounterStaticShadow s) + else if (z is IShadow3 s) { bool valid = GetIsShadowLockValid(pk, info, s); if (!valid) { - partial ??= s; + partial ??= z; continue; } } + static bool IsTypeCompatible(IEncounterTemplate enc, PKM pk, PIDType type) + { + if (enc is IRandomCorrelation r) + return r.IsCompatible(type, pk); + return type == PIDType.None; + } - if (info.PIDIV.Type.IsCompatible3(z, pk)) + if (IsTypeCompatible(z, pk, info.PIDIV.Type)) yield return z; else partial ??= z; @@ -113,82 +65,19 @@ public sealed class EncounterGenerator3GC : IEncounterGenerator private static IEnumerable IterateInner(PKM pk, EvoCriteria[] chain) { - IEncounterable? partial = null; - foreach (var enc in EncountersWC3.Encounter_WC3CXD) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - // Don't bother deferring matches. - var match = enc.GetMatchRating(pk); - if (match != PartialMatch) - yield return enc; - break; - } - } - foreach (var enc in Encounters3XD.Encounter_CXDShadow) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == PartialMatch) - partial ??= enc; - else - yield return enc; - break; - } - } - foreach (var enc in Encounters3XD.Encounter_CXDGift) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == PartialMatch) - partial ??= enc; - else - yield return enc; - break; - } - } - if (CanBeWildEncounter(pk)) - { - foreach (var area in Encounters3XD.SlotsXD) - { - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - if (match == PartialMatch) - partial ??= slot; - else - yield return slot; - } - } - } - - if (partial != null) - yield return partial; + var iterator = new EncounterEnumerator3GC(pk, chain); + foreach (var enc in iterator) + yield return enc.Encounter; } - private static bool GetIsShadowLockValid(PKM pk, LegalInfo info, EncounterStaticShadow s) + private static bool GetIsShadowLockValid(PKM pk, LegalInfo info, IShadow3 s) => s switch { - if (!s.EReader) - return LockFinder.IsAllShadowLockValid(s, info.PIDIV, pk); + EncounterShadow3Colo { EReader: true } => GetIsShadowLockValidEReader(pk, info, s), + _ => LockFinder.IsAllShadowLockValid(s, info.PIDIV, pk), + }; + private static bool GetIsShadowLockValidEReader(PKM pk, LegalInfo info, IShadow3 s) + { // E-Reader have fixed IVs, and aren't recognized as CXD (no PID-IV correlation). Span seeds = stackalloc uint[XDRNG.MaxCountSeedsPID]; var count = XDRNG.GetSeeds(seeds, pk.EncryptionConstant); diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs index 6afde220f..92229e7c7 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; namespace PKHeX.Core; @@ -13,7 +10,7 @@ public sealed class EncounterGenerator4 : IEncounterGenerator public static readonly EncounterGenerator4 Instance = new(); // Utility - private static readonly PGT RangerManaphy = new() { Data = { [0] = 7, [8] = 1 } }; + internal static readonly PGT RangerManaphy = new() { Data = { [0] = 7, [8] = 1 } }; public IEnumerable GetEncounters(PKM pk, LegalInfo info) { @@ -25,103 +22,9 @@ public sealed class EncounterGenerator4 : IEncounterGenerator public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - if (chain[^1].Species == (int)Species.Manaphy) - yield return RangerManaphy; - - var table = EncounterEvent.MGDB_G4; - foreach (var enc in GetPossibleGifts(chain, table, game)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(chain, game); - foreach (var enc in eggs) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = GetStatic(game); - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = GetAreas(game); - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - var table = GetTrades(game); - foreach (var enc in GetPossibleTrades(chain, table)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, IReadOnlyList table, GameVersion game) - { - foreach (var enc in table) - { - if (!enc.CanBeReceivedByVersion((int)game)) - continue; - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea4[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, EncounterTrade[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible4(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) @@ -132,14 +35,30 @@ public sealed class EncounterGenerator4 : IEncounterGenerator foreach (var z in GetEncountersInner(pk, chain, info)) { - if (!info.PIDIV.Type.IsCompatible4(z, pk)) + if (!IsTypeCompatible(z, pk, info.PIDIV.Type)) deferredPIDIV.Add(z); - else if (pk is IGroundTile e && !(z is IGroundTypeTile t ? t.GroundTile.Contains(e.GroundTile) : e.GroundTile == 0)) + else if (!IsTileCompatible(z, pk)) deferredEType.Add(z); else yield return z; } + static bool IsTileCompatible(IEncounterable encounterable, PKM pk) + { + if (pk is not IGroundTile e) + return true; // No longer has the data to check + if (encounterable is not IGroundTypeTile t) + return e.GroundTile == 0; + return t.GroundTile.Contains(e.GroundTile); + } + + static bool IsTypeCompatible(IEncounterTemplate enc, PKM pk, PIDType type) + { + if (enc is IRandomCorrelation r) + return r.IsCompatible(type, pk); + return type == PIDType.None; + } + foreach (var z in deferredEType) yield return z; @@ -154,148 +73,26 @@ public sealed class EncounterGenerator4 : IEncounterGenerator private static IEnumerable GetEncountersInner(PKM pk, EvoCriteria[] chain, LegalInfo info) { var game = (GameVersion)pk.Version; - if (pk.FatefulEncounter) + var iterator = new EncounterEnumerator4(pk, chain, game); + EncounterSlot4? deferSlot = null; + List? frames = null; + foreach (var enc in iterator) { - if (PGT.IsRangerManaphy(pk)) + var e = enc.Encounter; + if (e is not EncounterSlot4 s4) { - yield return RangerManaphy; - yield break; + yield return e; + continue; } - bool yielded = false; - foreach (var mg in EncounterEvent.MGDB_G4) - { - foreach (var evo in chain) - { - if (evo.Species != mg.Species) - continue; - if (mg.IsMatchExact(pk, evo)) - { - yield return mg; - yielded = true; - } - break; - } - } - if (yielded) - yield break; + var wildFrames = frames ?? AnalyzeFrames(pk, info); + var frame = wildFrames.Find(s => s.IsSlotCompatibile(s4, pk)); + if (frame != null) + yield return s4; + deferSlot ??= s4; } - if (Locations.IsEggLocationBred4(pk.Egg_Location, game)) - { - var eggs = GetEggs(chain, game); - foreach (var egg in eggs) - yield return egg; - } - - var encTrade = GetTrades(game); - foreach (var enc in encTrade) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (enc.IsMatchExact(pk, evo)) - yield return enc; - break; - } - } - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - bool safariSport = pk.Ball is (int)Ball.Sport or (int)Ball.Safari; // never static encounters - if (!safariSport) - { - var encStatic = GetStatic(game); - foreach (var enc in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == PartialMatch) - partial ??= enc; - else - yield return enc; - break; - } - } - } - - if (CanBeWildEncounter(pk)) - { - var wildFrames = AnalyzeFrames(pk, info); - var areas = GetAreas(game); - foreach (var area in areas) - { - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - if (match == PartialMatch) - { - partial ??= slot; - continue; - } - - // Can use Radar to force the encounter slot to stay consistent across encounters. - if (slot.CanUseRadar) - { - yield return slot; - continue; - } - - var frame = wildFrames.Find(s => s.IsSlotCompatibile(slot, pk)); - if (frame == null) - { - deferred ??= slot; - continue; - } - yield return slot; - } - } - - info.FrameMatches = false; - if (deferred is EncounterSlot4 x) - yield return x; - - if (partial is EncounterSlot4 y) - { - var frame = wildFrames.Find(s => s.IsSlotCompatibile(y, pk)); - info.FrameMatches = frame != null; - yield return y; - } - } - - // do static encounters if they were deferred to end, spit out any possible encounters for invalid pk - if (!safariSport) - yield break; - - var encStatic2 = GetStatic(game); - foreach (var enc in encStatic2) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == PartialMatch) - partial ??= enc; - else - yield return enc; - break; - } - } - - if (partial is not null) - yield return partial; + if (deferSlot != null) + yield return deferSlot; } private static List AnalyzeFrames(PKM pk, LegalInfo info) @@ -303,79 +100,10 @@ public sealed class EncounterGenerator4 : IEncounterGenerator return FrameFinder.GetFrames(info.PIDIV, pk).ToList(); } - private static EncounterStatic[] GetStatic(GameVersion gameSource) => gameSource switch - { - GameVersion.D => Encounters4DPPt.StaticD, - GameVersion.P => Encounters4DPPt.StaticP, - GameVersion.Pt => Encounters4DPPt.StaticPt, - GameVersion.HG => Encounters4HGSS.StaticHG, - GameVersion.SS => Encounters4HGSS.StaticSS, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - - private static EncounterArea4[] GetAreas(GameVersion gameSource) => gameSource switch - { - GameVersion.D => Encounters4DPPt.SlotsD, - GameVersion.P => Encounters4DPPt.SlotsP, - GameVersion.Pt => Encounters4DPPt.SlotsPt, - GameVersion.HG => Encounters4HGSS.SlotsHG, - GameVersion.SS => Encounters4HGSS.SlotsSS, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - - private static EncounterTrade[] GetTrades(GameVersion gameSource) => gameSource switch - { - GameVersion.D => Encounters4DPPt.TradeGift_DPPt, - GameVersion.P => Encounters4DPPt.TradeGift_DPPt, - GameVersion.Pt => Encounters4DPPt.TradeGift_DPPt, - GameVersion.HG => Encounters4HGSS.TradeGift_HGSS, - GameVersion.SS => Encounters4HGSS.TradeGift_HGSS, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - private const int Generation = 4; private const EntityContext Context = EntityContext.Gen4; private const byte EggLevel = 1; - private static IEnumerable GetEggs(EvoCriteria[] chain, GameVersion version) - { - var devolved = chain[^1]; - if (!devolved.InsideLevelRange(EggLevel)) - yield break; - - // Ensure most devolved species is the same as the egg species. - var (species, form) = GetBaby(devolved); - if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; // not a split-breed. - - // Sanity Check 1 - if (!Breeding.CanHatchAsEgg(species)) - yield break; - // Sanity Check 2 - if (!Breeding.CanHatchAsEgg(species, form, Context)) - yield break; - // Sanity Check 3 - if (!PersonalTable.HGSS.IsPresentInGame(species, form)) - yield break; - - yield return CreateEggEncounter(species, form, version); - // Version is not updated when hatching an Egg in Gen4. Version is a clear indicator of the game it originated on. - - // Check for split-breed - if (species == devolved.Species) - { - if (chain.Length < 2) - yield break; // no split-breed - devolved = chain[^2]; - } - if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; - - species = devolved.Species; - form = devolved.Form; - yield return CreateEggEncounter(species, form, version); - } - private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version) { if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform) @@ -387,4 +115,50 @@ public sealed class EncounterGenerator4 : IEncounterGenerator { return EvolutionTree.Evolves4.GetBaseSpeciesForm(lowest.Species, lowest.Form); } + + public static bool TryGetEgg(ReadOnlySpan chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + var devolved = chain[^1]; + if (!devolved.InsideLevelRange(EggLevel)) + return false; + + // Ensure most devolved species is the same as the egg species. + var (species, form) = GetBaby(devolved); + if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) + return false; // not a split-breed. + + // Sanity Check 1 + if (!Breeding.CanHatchAsEgg(species)) + return false; + // Sanity Check 2 + if (!Breeding.CanHatchAsEgg(species, form, Context)) + return false; + // Sanity Check 3 + if (!PersonalTable.HGSS.IsPresentInGame(species, form)) + return false; + + result = CreateEggEncounter(species, form, version); + return true; + } + + // Version is not updated when hatching an Egg in Gen4. Version is a clear indicator of the game it originated on. + + public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan chain, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + // Check for split-breed + var devolved = chain[^1]; + if (other.Species == devolved.Species) + { + if (chain.Length < 2) + return false; // no split-breed + devolved = chain[^2]; + } + if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) + return false; + + result = other with { Species = devolved.Species, Form = devolved.Form }; + return true; + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs index 6aa0efd2b..c2f1f8122 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; namespace PKHeX.Core; @@ -21,292 +18,21 @@ public sealed class EncounterGenerator5 : IEncounterGenerator public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncounterEvent.MGDB_G5; - foreach (var enc in GetPossibleGifts(chain, table, game)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(chain, game); - foreach (var enc in eggs) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = GetStatic(game); - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = GetAreas(game); - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - var table = GetTrades(game); - foreach (var enc in GetPossibleTrades(chain, table, game)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, IReadOnlyList table, GameVersion game) - { - foreach (var enc in table) - { - if (!enc.CanBeReceivedByVersion((int)game)) - continue; - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic5[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea5[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, EncounterTrade[] table, GameVersion game) - { - foreach (var enc in table) - { - if (enc.Version < GameVersion.BW && enc.Version != game) - continue; - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible5(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - var game = (GameVersion)pk.Version; - - bool yielded = false; - if (pk.FatefulEncounter) - { - foreach (var mg in EncounterEvent.MGDB_G5) - { - foreach (var evo in chain) - { - if (evo.Species != mg.Species) - continue; - - if (mg.IsMatchExact(pk, evo)) - { - yield return mg; - yielded = true; - } - break; - } - } - if (yielded) - yield break; - } - - if (Locations.IsEggLocationBred5(pk.Egg_Location)) - { - var eggs = GetEggs(chain, game); - foreach (var egg in eggs) - { - yield return egg; - yielded = true; - } - - if (yielded) - yield break; - } - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - var encStatic = GetStatic(game); - foreach (var enc in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - switch (match) - { - case Match: yield return enc; yielded = true; break; - case Deferred: deferred ??= enc; break; - case PartialMatch: partial ??= enc; break; - } - break; - } - } - if (yielded) - yield break; - - if (CanBeWildEncounter(pk)) - { - var location = pk.Met_Location; - var areas = GetAreas(game); - foreach (var area in areas) - { - if (!area.IsMatchLocation(location)) - continue; - - var slots = area.GetMatchingSlots(pk, chain); - foreach (var enc in slots) - { - var match = enc.GetMatchRating(pk); - switch (match) - { - case Match: yield return enc; yielded = true; break; - case Deferred: deferred ??= enc; break; - case PartialMatch: partial ??= enc; break; - } - break; - } - } - if (yielded) - yield break; - } - - var trades = GetTrades(game); - foreach (var trade in trades) - { - foreach (var evo in chain) - { - if (evo.Species != trade.Species) - continue; - if (!trade.IsMatchExact(pk, evo)) - break; - - var match = trade.GetMatchRating(pk); - switch (match) - { - case Match: yield return trade; break; - case Deferred: deferred ??= trade; break; - case PartialMatch: partial ??= trade; break; - } - break; - } - } - if (deferred != null) - yield return deferred; - if (partial != null) - yield return partial; + var iterator = new EncounterEnumerator5(pk, chain, (GameVersion)pk.Version); + foreach (var enc in iterator) + yield return enc.Encounter; } - private static EncounterStatic5[] GetStatic(GameVersion gameSource) => gameSource switch - { - GameVersion.B => Encounters5BW.StaticB, - GameVersion.W => Encounters5BW.StaticW, - GameVersion.B2 => Encounters5B2W2.StaticB2, - GameVersion.W2 => Encounters5B2W2.StaticW2, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - - private static EncounterArea5[] GetAreas(GameVersion gameSource) => gameSource switch - { - GameVersion.B => Encounters5BW.SlotsB, - GameVersion.W => Encounters5BW.SlotsW, - GameVersion.B2 => Encounters5B2W2.SlotsB2, - GameVersion.W2 => Encounters5B2W2.SlotsW2, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - - private static EncounterTrade[] GetTrades(GameVersion gameSource) => gameSource switch - { - GameVersion.B => Encounters5BW.TradeGift_BW, - GameVersion.W => Encounters5BW.TradeGift_BW, - GameVersion.B2 => Encounters5B2W2.TradeGift_B2W2, - GameVersion.W2 => Encounters5B2W2.TradeGift_B2W2, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - private const int Generation = 5; private const EntityContext Context = EntityContext.Gen5; - private const byte EggLevel = 1; - - private static IEnumerable GetEggs(EvoCriteria[] chain, GameVersion version) - { - var devolved = chain[^1]; - if (!devolved.InsideLevelRange(EggLevel)) - yield break; - - // Ensure most devolved species is the same as the egg species. - var (species, form) = GetBaby(devolved); - if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; // not a split-breed. - - // Sanity Check 1 - if (!Breeding.CanHatchAsEgg(species)) - yield break; - // Sanity Check 2 - if (!Breeding.CanHatchAsEgg(species, form, Context)) - yield break; - // Sanity Check 3 - if (!PersonalTable.B2W2.IsPresentInGame(species, form)) - yield break; - - yield return CreateEggEncounter(species, form, version); - // Both B/W and B2/W2 have the same egg move sets, so there is no point generating other-game pair encounters for traded eggs. - // When hatched, the entity's Version is updated to the OT's. - - // Check for split-breed - if (species == devolved.Species) - { - if (chain.Length < 2) - yield break; // no split-breed - devolved = chain[^2]; - } - if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; - - species = devolved.Species; - form = devolved.Form; - yield return CreateEggEncounter(species, form, version); - } + private const byte EggLevel = EggStateLegality.EggMetLevel; private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version) { @@ -319,4 +45,51 @@ public sealed class EncounterGenerator5 : IEncounterGenerator { return EvolutionTree.Evolves5.GetBaseSpeciesForm(lowest.Species, lowest.Form); } + + public static bool TryGetEgg(ReadOnlySpan chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + var devolved = chain[^1]; + if (!devolved.InsideLevelRange(EggLevel)) + return false; + + // Ensure most devolved species is the same as the egg species. + var (species, form) = GetBaby(devolved); + if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) + return false; // not a split-breed. + + // Sanity Check 1 + if (!Breeding.CanHatchAsEgg(species)) + return false; + // Sanity Check 2 + if (!Breeding.CanHatchAsEgg(species, form, Context)) + return false; + // Sanity Check 3 + if (!PersonalTable.B2W2.IsPresentInGame(species, form)) + return false; + + result = CreateEggEncounter(species, form, version); + return true; + } + + // Both B/W and B2/W2 have the same egg move sets, so there is no point generating other-game pair encounters for traded eggs. + // When hatched, the entity's Version is updated to the OT's. + + public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan chain, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + // Check for split-breed + var devolved = chain[^1]; + if (other.Species == devolved.Species) + { + if (chain.Length < 2) + return false; // no split-breed + devolved = chain[^2]; + } + if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) + return false; + + result = other with { Species = devolved.Species, Form = devolved.Form }; + return true; + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs index 9dd0a9986..1b806826d 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; namespace PKHeX.Core; @@ -11,128 +8,11 @@ public sealed class EncounterGenerator6 : IEncounterGenerator { public static readonly EncounterGenerator6 Instance = new(); - public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) + public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncounterEvent.MGDB_G6; - foreach (var enc in GetPossibleGifts(chain, table, game)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(pk, chain, game); - foreach (var enc in eggs) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = GetStatic(game); - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - if (game is GameVersion.X or GameVersion.Y) - { - var areas = game == GameVersion.X ? Encounters6XY.SlotsX : Encounters6XY.SlotsY; - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - else if (game is GameVersion.AS or GameVersion.OR) - { - var areas = game == GameVersion.AS ? Encounters6AO.SlotsA : Encounters6AO.SlotsO; - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - } - if (groups.HasFlag(Trade)) - { - var table = GetTrades(game); - foreach (var enc in GetPossibleTrades(chain, table)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, IReadOnlyList table, GameVersion game) - { - foreach (var enc in table) - { - if (!enc.CanBeReceivedByVersion((int)game)) - continue; - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic6[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea6XY[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea6AO[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, EncounterTrade6[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible6(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, LegalInfo info) @@ -143,233 +23,14 @@ public sealed class EncounterGenerator6 : IEncounterGenerator public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (chain.Length == 0) - yield break; - var game = (GameVersion)pk.Version; - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - bool yielded = false; - if (pk.FatefulEncounter || pk.Met_Location == Locations.LinkGift6) - { - foreach (var z in EncounterEvent.MGDB_G6) - { - foreach (var evo in chain) - { - if (z.Species != evo.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - switch (match) - { - case Match: yield return z; yielded = true; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - break; - } - } - if (!yielded) - { - if (deferred != null) - { - yield return deferred; - yielded = true; - } - if (partial != null) - { - yield return partial; - yielded = true; - } - } - if (yielded) - yield break; - } - - if (Locations.IsEggLocationBred6(pk.Egg_Location)) - { - var eggs = GetEggs(pk, chain, game); - foreach (var egg in eggs) - { - yield return egg; - yielded = true; - } - if (yielded) - yield break; - } - - var encStatic = GetStatic(game); - foreach (var enc in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - switch (match) - { - case Match: yield return enc; yielded = true; break; - case Deferred: deferred ??= enc; break; - case PartialMatch: partial ??= enc; break; - } - break; - } - } - if (yielded) - yield break; - - if (CanBeWildEncounter(pk)) - { - var location = pk.Met_Location; - if (game is GameVersion.X or GameVersion.Y) - { - var areas = game == GameVersion.X ? Encounters6XY.SlotsX : Encounters6XY.SlotsY; - foreach (var area in areas) - { - if (!area.IsMatchLocation(location)) - continue; - - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - switch (match) - { - case Match: yield return slot; yielded = true; break; - case Deferred: deferred ??= slot; break; - case PartialMatch: partial ??= slot; break; - } - } - } - } - else if (game is GameVersion.AS or GameVersion.OR) - { - var areas = game == GameVersion.AS ? Encounters6AO.SlotsA : Encounters6AO.SlotsO; - foreach (var area in areas) - { - if (!area.IsMatchLocation(location)) - continue; - - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - switch (match) - { - case Match: yield return slot; yielded = true; break; - case Deferred: deferred ??= slot; break; - case PartialMatch: partial ??= slot; break; - } - } - } - } - if (yielded) - yield break; - } - - var trades = GetTrades(game); - foreach (var enc in trades) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - switch (match) - { - case Match: yield return enc; yielded = true; break; - case Deferred: deferred ??= enc; break; - case PartialMatch: partial ??= enc; break; - } - break; - } - } - if (yielded) - yield break; - - if (deferred != null) - yield return deferred; - if (partial != null) - yield return partial; + var iterator = new EncounterEnumerator6(pk, chain, (GameVersion)pk.Version); + foreach (var enc in iterator) + yield return enc.Encounter; } - private static EncounterStatic6[] GetStatic(GameVersion gameSource) => gameSource switch - { - GameVersion.X => Encounters6XY.StaticX, - GameVersion.Y => Encounters6XY.StaticY, - GameVersion.AS => Encounters6AO.StaticA, - GameVersion.OR => Encounters6AO.StaticO, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - - private static EncounterTrade6[] GetTrades(GameVersion gameSource) => gameSource switch - { - GameVersion.X => Encounters6XY.TradeGift_XY, - GameVersion.Y => Encounters6XY.TradeGift_XY, - GameVersion.AS => Encounters6AO.TradeGift_AO, - GameVersion.OR => Encounters6AO.TradeGift_AO, - _ => throw new ArgumentOutOfRangeException(nameof(gameSource), gameSource, null), - }; - private const int Generation = 6; private const EntityContext Context = EntityContext.Gen6; - private const byte EggLevel = 1; - - private static IEnumerable GetEggs(PKM pk, EvoCriteria[] chain, GameVersion version) - { - var devolved = chain[^1]; - if (!devolved.InsideLevelRange(EggLevel)) - yield break; - - // Ensure most devolved species is the same as the egg species. - var (species, form) = GetBaby(devolved); - if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; // not a split-breed. - - // Sanity Check 1 - if (!Breeding.CanHatchAsEgg(species)) - yield break; - // Sanity Check 2 - if (!Breeding.CanHatchAsEgg(species, form, Context)) - yield break; - // Sanity Check 3 - if (!PersonalTable.AO.IsPresentInGame(species, form)) - yield break; - - var egg = CreateEggEncounter(species, form, version); - yield return egg; - if (pk.IsEgg) - yield break; - bool otherVersion = pk is { Egg_Location: Locations.LinkTrade6 }; - if (otherVersion) - yield return egg with { Version = GetOtherGamePair(version) }; - - // Check for split-breed - if (species == devolved.Species) - { - if (chain.Length < 2) - yield break; // no split-breed - devolved = chain[^2]; - } - if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; - - species = devolved.Species; - form = devolved.Form; - egg = CreateEggEncounter(species, form, version); - yield return egg; - if (otherVersion) - yield return egg with { Version = GetOtherGamePair(version) }; - } + private const byte EggLevel = EggStateLegality.EggMetLevel; private static GameVersion GetOtherGamePair(GameVersion version) { @@ -392,4 +53,50 @@ public sealed class EncounterGenerator6 : IEncounterGenerator { return EvolutionTree.Evolves6.GetBaseSpeciesForm(lowest.Species, lowest.Form); } + + public static bool TryGetEgg(ReadOnlySpan chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + var devolved = chain[^1]; + if (!devolved.InsideLevelRange(EggLevel)) + return false; + + // Ensure most devolved species is the same as the egg species. + var (species, form) = GetBaby(devolved); + if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) + return false; // not a split-breed. + + // Sanity Check 1 + if (!Breeding.CanHatchAsEgg(species)) + return false; + // Sanity Check 2 + if (!Breeding.CanHatchAsEgg(species, form, Context)) + return false; + // Sanity Check 3 + if (!PersonalTable.AO.IsPresentInGame(species, form)) + return false; + + result = CreateEggEncounter(species, form, version); + return true; + } + + public static EncounterEgg MutateEggTrade(EncounterEgg egg) => egg with { Version = GetOtherGamePair(egg.Version) }; + + public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan chain, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + // Check for split-breed + var devolved = chain[^1]; + if (other.Species == devolved.Species) + { + if (chain.Length < 2) + return false; // no split-breed + devolved = chain[^2]; + } + if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) + return false; + + result = other with { Species = devolved.Species, Form = devolved.Form }; + return true; + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs index 96fae5c86..53a465c80 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; namespace PKHeX.Core; @@ -11,263 +8,21 @@ public sealed class EncounterGenerator7 : IEncounterGenerator { public static readonly EncounterGenerator7 Instance = new(); - public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) + public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncounterEvent.MGDB_G7; - foreach (var enc in GetPossibleGifts(chain, table, game)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(pk, chain, game); - foreach (var enc in eggs) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = GetStatic(game); - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = GetAreas(game); - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - var table = GetTrades(game); - foreach (var enc in GetPossibleTrades(chain, table)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, IReadOnlyList table, GameVersion game) - { - foreach (var enc in table) - { - if (!enc.CanBeReceivedByVersion((int)game)) - continue; - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic7[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea7[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, EncounterTrade7[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible7(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (chain.Length == 0) - yield break; - var game = (GameVersion)pk.Version; - - bool yielded = false; - IEncounterable? deferred = null; - IEncounterable? partial = null; - - if (pk.FatefulEncounter) - { - foreach (var z in EncounterEvent.MGDB_G7) - { - foreach (var evo in chain) - { - if (z.Species != evo.Species) - continue; - - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - switch (match) - { - case Match: yield return z; yielded = true; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - break; - } - } - if (!yielded) - { - if (deferred != null) - { - yield return deferred; - yielded = true; - } - if (partial != null) - { - yield return partial; - yielded = true; - } - } - if (yielded) - yield break; - } - - if (Locations.IsEggLocationBred6(pk.Egg_Location)) - { - var eggs = GetEggs(pk, chain, game); - foreach (var egg in eggs) - yield return egg; - if (chain[^1].Species != (int)Species.Eevee) // Static encounter clash (gift egg) - yield break; - } - - var table = GetStatic(game); - foreach (var z in table) - { - foreach (var evo in chain) - { - if (z.Species != evo.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - continue; - - var match = z.GetMatchRating(pk); - switch (match) - { - case Match: yield return z; yielded = true; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - } - if (yielded) - yield break; - - if (CanBeWildEncounter(pk)) - { - var location = pk.Met_Location; - var areas = GetAreas(game); - foreach (var area in areas) - { - if (!area.IsMatchLocation(location)) - continue; - - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - switch (match) - { - case Match: yield return slot; yielded = true; break; - case Deferred: deferred ??= slot; break; - case PartialMatch: partial ??= slot; break; - } - } - } - if (yielded) - yield break; - } - - var trades = GetTrades(game); - foreach (var trade in trades) - { - foreach (var evo in chain) - { - if (trade.Species != evo.Species) - continue; - if (!trade.IsMatchExact(pk, evo)) - continue; - - var match = trade.GetMatchRating(pk); - switch (match) - { - case Match: yield return trade; break; - case Deferred: deferred ??= trade; break; - case PartialMatch: partial ??= trade; break; - } - } - } - - if (deferred != null) - yield return deferred; - else if (partial != null) - yield return partial; + var iterator = new EncounterEnumerator7(pk, chain, (GameVersion)pk.Version); + foreach (var enc in iterator) + yield return enc.Encounter; } - private static EncounterStatic7[] GetStatic(GameVersion game) => game switch - { - GameVersion.SN => Encounters7SM.StaticSN, - GameVersion.MN => Encounters7SM.StaticMN, - GameVersion.US => Encounters7USUM.StaticUS, - GameVersion.UM => Encounters7USUM.StaticUM, - _ => throw new ArgumentOutOfRangeException(nameof(game), game, null), - }; - - private static EncounterArea7[] GetAreas(GameVersion game) => game switch - { - GameVersion.SN => Encounters7SM.SlotsSN, - GameVersion.MN => Encounters7SM.SlotsMN, - GameVersion.US => Encounters7USUM.SlotsUS, - GameVersion.UM => Encounters7USUM.SlotsUM, - _ => throw new ArgumentOutOfRangeException(nameof(game), game, null), - }; - - private static EncounterTrade7[] GetTrades(GameVersion game) => game switch - { - GameVersion.SN => Encounters7SM.TradeGift_SM, - GameVersion.MN => Encounters7SM.TradeGift_SM, - GameVersion.US => Encounters7USUM.TradeGift_USUM, - GameVersion.UM => Encounters7USUM.TradeGift_USUM, - _ => throw new ArgumentOutOfRangeException(nameof(game), game, null), - }; - - internal static EncounterStatic7 GetVCStaticTransferEncounter(PKM pk, ushort encSpecies, ReadOnlySpan chain) + internal static EncounterTransfer7 GetVCStaticTransferEncounter(PKM pk, ushort encSpecies, ReadOnlySpan chain) { // Obtain the lowest evolution species with matching OT friendship. Not all species chains have the same base friendship. var met = (byte)pk.Met_Level; @@ -278,12 +33,12 @@ public sealed class EncounterGenerator7 : IEncounterGenerator var species = GetVCSpecies(chain, pk, Legal.MaxSpeciesID_1); var vc1Species = species > Legal.MaxSpeciesID_1 ? encSpecies : species; if (vc1Species <= Legal.MaxSpeciesID_1) - return EncounterStatic7.GetVC1(vc1Species, met); + return EncounterTransfer7.GetVC1(vc1Species, met); } // fall through else { var species = GetVCSpecies(chain, pk, Legal.MaxSpeciesID_2); - return EncounterStatic7.GetVC2(species > Legal.MaxSpeciesID_2 ? encSpecies : species, met); + return EncounterTransfer7.GetVC2(species > Legal.MaxSpeciesID_2 ? encSpecies : species, met); } } @@ -308,53 +63,52 @@ public sealed class EncounterGenerator7 : IEncounterGenerator private const int Generation = 7; private const EntityContext Context = EntityContext.Gen7; - private const byte EggLevel = 1; + private const byte EggLevel = EggStateLegality.EggMetLevel; - private static IEnumerable GetEggs(PKM pk, EvoCriteria[] chain, GameVersion version) + public static bool TryGetEgg(ReadOnlySpan chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) { + result = null; var devolved = chain[^1]; if (!devolved.InsideLevelRange(EggLevel)) - yield break; + return false; // Ensure most devolved species is the same as the egg species. var (species, form) = GetBaby(devolved); if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; // not a split-breed. + return false; // not a split-breed. // Sanity Check 1 if (!Breeding.CanHatchAsEgg(species)) - yield break; + return false; // Sanity Check 2 if (!Breeding.CanHatchAsEgg(species, form, Context)) - yield break; + return false; // Sanity Check 3 if (!PersonalTable.USUM.IsPresentInGame(species, form)) - yield break; + return false; - var egg = CreateEggEncounter(species, form, version); - yield return egg; - if (pk.IsEgg) - yield break; - bool otherVersion = pk is { Egg_Location: Locations.LinkTrade6 }; - if (otherVersion) - yield return egg with { Version = GetOtherGamePair(version) }; + result = CreateEggEncounter(species, form, version); + return true; + } + public static EncounterEgg MutateEggTrade(EncounterEgg egg) => egg with { Version = GetOtherGamePair(egg.Version) }; + + public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan chain, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; // Check for split-breed - if (species == devolved.Species) + var devolved = chain[^1]; + if (other.Species == devolved.Species) { if (chain.Length < 2) - yield break; // no split-breed + return false; // no split-breed devolved = chain[^2]; } if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; + return false; - species = devolved.Species; - form = devolved.Form; - egg = CreateEggEncounter(species, form, version); - yield return egg; - if (otherVersion) - yield return egg with { Version = GetOtherGamePair(version) }; + result = other with { Species = devolved.Species, Form = devolved.Form }; + return true; } private static GameVersion GetOtherGamePair(GameVersion version) diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7GG.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7GG.cs index 3dc77ea7b..ee8672473 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7GG.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7GG.cs @@ -1,9 +1,5 @@ using System.Collections.Generic; -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; - namespace PKHeX.Core; /// @@ -15,207 +11,15 @@ public sealed class EncounterGenerator7GG : IEncounterGenerator public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncounterEvent.MGDB_G7GG; - foreach (var enc in GetPossibleMystery(chain, table)) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = game == GameVersion.GP ? Encounters7GG.StaticGP : Encounters7GG.StaticGE; - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var table = game == GameVersion.GP ? Encounters7GG.SlotsGP : Encounters7GG.SlotsGE; - foreach (var enc in GetPossibleSlot(chain, table)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - var table = Encounters7GG.TradeGift_GG; - foreach (var enc in GetPossibleTrade(chain, table)) - yield return enc; - } - } - - private static IEnumerable GetPossibleMystery(EvoCriteria[] chain, IReadOnlyList table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic7b[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlot(EvoCriteria[] chain, EncounterArea7b[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleTrade(EvoCriteria[] chain, EncounterTrade7b[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (enc.Species != evo.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible7GG(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (chain.Length == 0) - yield break; - bool yielded = false; - if (pk.FatefulEncounter) - { - foreach (var z in EncounterEvent.MGDB_G7GG) - { - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - - if (z.IsMatchExact(pk, evo)) - { - yield return z; - yielded = true; - } - break; - } - } - if (yielded) - yield break; - } - - var game = (GameVersion)pk.Version; - IEncounterable? deferred = null; - IEncounterable? partial = null; - - var encStatic = game == GameVersion.GP ? Encounters7GG.StaticGP : Encounters7GG.StaticGE; - foreach (var z in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - switch (match) - { - case Match: yield return z; yielded = true; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - break; - } - } - if (yielded) - yield break; - - if (CanBeWildEncounter(pk)) - { - var location = pk.Met_Location; - var areas = game == GameVersion.GP ? Encounters7GG.SlotsGP : Encounters7GG.SlotsGE; - foreach (var area in areas) - { - if (!area.IsMatchLocation(location)) - continue; - - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - - if (!slot.IsMatchExact(pk, evo)) - break; - - var match = slot.GetMatchRating(pk); - switch (match) - { - case Match: yield return slot; yielded = true; break; - case Deferred: deferred ??= slot; break; - case PartialMatch: partial ??= slot; break; - } - break; - } - } - } - if (yielded) - yield break; - } - - foreach (var z in Encounters7GG.TradeGift_GG) - { - if (z.Version != GameVersion.GG && z.Version != game) - continue; - - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - switch (match) - { - case Match: yield return z; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - break; - } - } - - if (deferred != null) - yield return deferred; - else if (partial != null) - yield return partial; + var iterator = new EncounterEnumerator7GG(pk, chain, (GameVersion)pk.Version); + foreach (var enc in iterator) + yield return enc.Encounter; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7GO.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7GO.cs index 6349b0728..06dc3b479 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7GO.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7GO.cs @@ -1,82 +1,22 @@ using System.Collections.Generic; -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; - namespace PKHeX.Core; public sealed class EncounterGenerator7GO : IEncounterGenerator { public static readonly EncounterGenerator7GO Instance = new(); - public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) + public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion __, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Slot)) - { - var areas = EncountersGO.SlotsGO_GG; - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea7g[] areas) - { - foreach (var area in areas) - { - foreach (var evo in chain) - { - if (area.Species != evo.Species) - continue; - - foreach (var slot in area.Slots) - yield return slot; - break; - } - } + var iterator = new EncounterPossible7GO(chain, groups); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (chain.Length == 0) - yield break; - if (!CanBeWildEncounter(pk)) - yield break; - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - bool yielded = false; - foreach (var area in EncountersGO.SlotsGO_GG) - { - foreach (var evo in chain) - { - if (area.Species != evo.Species) - continue; - - var slots = area.GetMatchingSlots(pk, evo); - foreach (var z in slots) - { - var match = z.GetMatchRating(pk); - switch (match) - { - case Match: yield return z; yielded = true; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - break; - } - } - if (yielded) - yield break; - - if (deferred != null) - yield return deferred; - else if (partial != null) - yield return partial; + var iterator = new EncounterEnumerator7GO(pk, chain); + foreach (var enc in iterator) + yield return enc.Encounter; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs index 96ff2a0e9..697c775b3 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs @@ -1,8 +1,6 @@ +using System; using System.Collections.Generic; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; namespace PKHeX.Core; @@ -12,280 +10,22 @@ public sealed class EncounterGenerator8 : IEncounterGenerator public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncounterEvent.MGDB_G8; - foreach (var enc in GetPossibleGifts(chain, table)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(chain, game); - foreach (var enc in eggs) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = game == GameVersion.SW ? Encounters8.StaticSW : Encounters8.StaticSH; - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = game == GameVersion.SW ? Encounters8.SlotsSW : Encounters8.SlotsSH; - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - var table = Encounters8.TradeGift_SWSH; - foreach (var enc in GetPossibleTrades(chain, game, table)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, IReadOnlyList table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea8[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, GameVersion game, EncounterTrade8[] table) - { - foreach (var enc in table) - { - if (enc.Version != GameVersion.SWSH && game != enc.Version) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible8(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (chain.Length == 0) - yield break; - if (pk.FatefulEncounter) - { - bool yielded = false; - foreach (var z in EncounterEvent.MGDB_G8) - { - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - yield return z; - yielded = true; - break; - } - } - if (yielded) - yield break; - } - - var game = (GameVersion)pk.Version; - if (Locations.IsEggLocationBred6(pk.Egg_Location)) - { - bool yielded = false; - var eggs = GetEggs(chain, game); - foreach (var egg in eggs) - { yield return egg; yielded = true; } - if (yielded) yield break; - } - - // Trades - if (pk.Met_Location == Locations.LinkTrade6NPC) - { - foreach (var enc in GetEncountersTrade(pk, chain, game)) - yield return enc; - yield break; - } - - IEncounterable? cache = null; - EncounterMatchRating rating = MaxNotMatch; - - // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. - var encStatic = game == GameVersion.SW ? Encounters8.StaticSW : Encounters8.StaticSH; - foreach (var enc in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == Match) - { - yield return enc; - } - else if (match < rating) - { - cache = enc; - rating = match; - } - break; - } - } - - // Wild Encounters - if (CanBeWildEncounter(pk)) - { - var location = pk.Met_Location; - var areas = game == GameVersion.SW ? Encounters8.SlotsSW : Encounters8.SlotsSH; - foreach (var area in areas) - { - if (!area.IsMatchLocation(location)) - continue; - - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - if (match == Match) - { - yield return slot; - } - else if (match < rating) - { - cache = slot; - rating = match; - } - break; - } - } - } - - if (cache != null) - yield return cache; - } - - private static IEnumerable GetEncountersTrade(PKM pk, EvoCriteria[] chain, GameVersion game) - { - EncounterMatchRating rating = MaxNotMatch; - IEncounterable? cache = null; - foreach (var z in Encounters8.TradeGift_SWSH) - { - if (z.Version != GameVersion.SWSH && z.Version != game) - continue; - - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - break; - } - } - - if (cache != null) - yield return cache; + var iterator = new EncounterEnumerator8(pk, chain, (GameVersion)pk.Version); + foreach (var enc in iterator) + yield return enc.Encounter; } private const int Generation = 8; private const EntityContext Context = EntityContext.Gen8; private const byte EggLevel = 1; - private static IEnumerable GetEggs(EvoCriteria[] chain, GameVersion version) - { - var devolved = chain[^1]; - if (!devolved.InsideLevelRange(EggLevel)) - yield break; - - // Ensure most devolved species is the same as the egg species. - var (species, form) = GetBaby(devolved); - if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; // not a split-breed. - - // Sanity Check 1 - if (!Breeding.CanHatchAsEgg(species)) - yield break; - // Sanity Check 2 - if (!Breeding.CanHatchAsEgg(species, form, Context)) - yield break; - // Sanity Check 3 - if (!PersonalTable.SWSH.IsPresentInGame(species, form)) - yield break; - - yield return CreateEggEncounter(species, form, version); - - // Check for split-breed - if (species == devolved.Species) - { - if (chain.Length < 2) - yield break; // no split-breed - devolved = chain[^2]; - } - if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; - - yield return CreateEggEncounter(devolved.Species, devolved.Form, version); - } - private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version) { if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform) @@ -301,4 +41,48 @@ public sealed class EncounterGenerator8 : IEncounterGenerator return default; // Something in the evolution chain prevented reaching the baby species-form. return (lowest.Species, lowest.Form); } + + public static bool TryGetEgg(ReadOnlySpan chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + var devolved = chain[^1]; + if (!devolved.InsideLevelRange(EggLevel)) + return false; + + // Ensure most devolved species is the same as the egg species. + var (species, form) = GetBaby(devolved); + if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) + return false; // not a split-breed. + + // Sanity Check 1 + if (!Breeding.CanHatchAsEgg(species)) + return false; + // Sanity Check 2 + if (!Breeding.CanHatchAsEgg(species, form, Context)) + return false; + // Sanity Check 3 + if (!PersonalTable.SWSH.IsPresentInGame(species, form)) + return false; + + result = CreateEggEncounter(species, form, version); + return true; + } + + public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan chain, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + // Check for split-breed + var devolved = chain[^1]; + if (other.Species == devolved.Species) + { + if (chain.Length < 2) + return false; // no split-breed + devolved = chain[^2]; + } + if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) + return false; + + result = other with { Species = devolved.Species, Form = devolved.Form }; + return true; + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8GO.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8GO.cs index 3b34a7206..18f5f4fea 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8GO.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8GO.cs @@ -1,85 +1,22 @@ using System.Collections.Generic; -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; - namespace PKHeX.Core; public sealed class EncounterGenerator8GO : IEncounterGenerator { public static readonly EncounterGenerator8GO Instance = new(); - public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) + public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion __, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Slot)) - { - var table = EncountersGO.SlotsGO; - foreach (var enc in GetPossibleSlots(chain, table)) - yield return enc; - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea8g[] table) - { - foreach (var area in table) - { - foreach (var evo in chain) - { - if (area.Species != evo.Species) - continue; - - foreach (var slot in area.Slots) - yield return slot; - break; - } - } + var iterator = new EncounterPossible8GO(chain, groups); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (chain.Length == 0) - yield break; - if (!CanBeWildEncounter(pk)) - yield break; - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - bool yielded = false; - foreach (var area in EncountersGO.SlotsGO) - { - foreach (var evo in chain) - { - if (area.Species != evo.Species) - continue; - - if (area.Form != evo.Form && !FormInfo.IsFormChangeable(area.Species, area.Form, evo.Form, EntityContext.Gen8, pk.Context)) - continue; - - var slots = area.GetMatchingSlots(pk, evo); - foreach (var z in slots) - { - var match = z.GetMatchRating(pk); - switch (match) - { - case Match: yield return z; yielded = true; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - break; - } - } - if (yielded) - yield break; - - if (deferred != null) - yield return deferred; - else if (partial != null) - yield return partial; + var iterator = new EncounterEnumerator8GO(pk, chain); + foreach (var enc in iterator) + yield return enc.Encounter; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs index a65863ce6..81f7ce069 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs @@ -1,177 +1,22 @@ using System.Collections.Generic; -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; - namespace PKHeX.Core; public sealed class EncounterGenerator8a : IEncounterGenerator { public static readonly EncounterGenerator8a Instance = new(); - public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) + public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion __, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncounterEvent.MGDB_G8A; - foreach (var enc in GetPossibleGifts(chain, table)) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = Encounters8a.StaticLA; - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - - if (groups.HasFlag(Slot)) - { - var areas = Encounters8a.SlotsLA; - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, IReadOnlyList table) - { - foreach (var e in table) - { - foreach (var evo in chain) - { - if (evo.Species != e.Species) - continue; - yield return e; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic8a[] table) - { - foreach (var e in table) - { - foreach (var evo in chain) - { - if (evo.Species != e.Species) - continue; - yield return e; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea8a[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } + var iterator = new EncounterPossible8a(chain, groups); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (chain.Length == 0) - yield break; - if (pk is PK8 { SWSH: false }) - yield break; - if (pk.IsEgg) - yield break; - - // Mystery Gifts - // All gifts are Fateful Encounter, but some Static Encounters are as well. - if (pk.FatefulEncounter) - { - // If we yield any Mystery Gifts, we don't need to yield any other encounters. - bool yielded = false; - foreach (var mg in EncounterEvent.MGDB_G8A) - { - foreach (var evo in chain) - { - if (evo.Species != mg.Species) - continue; - - if (mg.IsMatchExact(pk, evo)) - { - yield return mg; - yielded = true; - } - break; - } - } - if (yielded) - yield break; - } - - IEncounterable? cache = null; - EncounterMatchRating rating = MaxNotMatch; - - // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. - foreach (var e in Encounters8a.StaticLA) - { - foreach (var evo in chain) - { - if (evo.Species != e.Species) - continue; - if (!e.IsMatchExact(pk, evo)) - continue; - - var match = e.GetMatchRating(pk); - if (match == Match) - { - yield return e; - } - else if (match < rating) - { - cache = e; - rating = match; - } - } - } - - // Encounter Slots - if (CanBeWildEncounter(pk)) - { - var location = pk.Met_Location; - var remap = LocationsHOME.GetRemapState(EntityContext.Gen8a, pk.Context); - bool hasOriginalLocation = true; - if (remap.HasFlag(LocationRemapState.Remapped)) - hasOriginalLocation = location != LocationsHOME.SWLA; - foreach (var area in Encounters8a.SlotsLA) - { - if (hasOriginalLocation && !area.IsMatchLocation(location)) - continue; - - var slots = area.GetMatchingSlots(pk, chain); - foreach (var z in slots) - { - var match = z.GetMatchRating(pk); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - } - } - } - - if (cache != null) - yield return cache; + var iterator = new EncounterEnumerator8a(pk, chain); + foreach (var enc in iterator) + yield return enc.Encounter; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs index bb2410790..13d630257 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs @@ -1,8 +1,6 @@ +using System; using System.Collections.Generic; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; namespace PKHeX.Core; @@ -12,414 +10,72 @@ public sealed class EncounterGenerator8b : IEncounterGenerator public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncounterEvent.MGDB_G8B; - foreach (var enc in GetPossibleGifts(chain, table)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(chain, game); - foreach (var enc in eggs) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = game == GameVersion.BD ? Encounters8b.StaticBD : Encounters8b.StaticSP; - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = game == GameVersion.BD ? Encounters8b.SlotsBD : Encounters8b.SlotsSP; - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - foreach (var enc in GetPossibleTrades(chain, game)) - yield return enc; - } - } - - private static IEnumerable GetPossibleGifts(EvoCriteria[] chain, IReadOnlyList table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic8b[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea8b[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleTrades(EvoCriteria[] chain, GameVersion game) - { - var table = Encounters8b.TradeGift_BDSP; - foreach (var enc in table) - { - if (enc.Version != GameVersion.BDSP && enc.Version != game) - continue; - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } + var iterator = new EncounterPossible8b(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (chain.Length == 0) - yield break; - if (pk is PK8) - yield break; - - bool yielded = false; var game = (GameVersion)pk.Version; - - if (pk.FatefulEncounter) - { - foreach (var enc in EncounterEvent.MGDB_G8B) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - yield return enc; - yielded = true; - break; - } - } - if (yielded) - yield break; - } - - if (Locations.IsEggLocationBred8b(pk.Egg_Location)) - { - var egg = GetEggs(chain, game); - foreach (var enc in egg) - { - yield return enc; - yielded = true; - } - if (yielded) - yield break; - } - - IEncounterable? cache = null; - EncounterMatchRating rating = MaxNotMatch; - - // Trades - if (pk is { IsEgg: false, Met_Location: Locations.LinkTrade6NPC }) - { - foreach (var enc in GetEncountersTrade(pk, chain, game)) - yield return enc; - yield break; - } - - // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. - var encStatic = game == GameVersion.BD ? Encounters8b.StaticBD : Encounters8b.StaticSP; - foreach (var enc in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == Match) - { - yield return enc; - } - else if (match < rating) - { - cache = enc; - rating = match; - } - break; - } - } - - if (CanBeWildEncounter(pk)) - { - var location = pk.Met_Location; - var remap = LocationsHOME.GetRemapState(EntityContext.Gen8b, pk.Context); - bool hasOriginalLocation = true; - if (remap.HasFlag(LocationRemapState.Remapped)) - hasOriginalLocation = location != LocationsHOME.GetMetSWSH((ushort)location, (int)game); - var encWild = game == GameVersion.BD ? Encounters8b.SlotsBD : Encounters8b.SlotsSP; - foreach (var area in encWild) - { - if (hasOriginalLocation && !area.IsMatchLocation(location)) - continue; - - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - if (match == Match) - { - yield return slot; - } - else if (match < rating) - { - cache = slot; - rating = match; - } - } - } - } - - if (cache != null) - yield return cache; - } - - private static IEnumerable GetEncountersTrade(PKM pk, EvoCriteria[] chain, GameVersion game) - { - bool yielded = false; - EncounterMatchRating rating = MaxNotMatch; - IEncounterable? cache = null; - foreach (var enc in Encounters8b.TradeGift_BDSP) - { - if (enc.Version != GameVersion.BDSP && enc.Version != game) - continue; - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == Match) - { - yield return enc; - yielded = true; - } - else if (match < rating) - { - cache = enc; - rating = match; - } - - break; - } - } - - if (yielded) - yield break; - if (cache != null) - yield return cache; + var iterator = new EncounterEnumerator8b(pk, chain, game); + foreach (var enc in iterator) + yield return enc.Encounter; } public IEnumerable GetEncountersSWSH(PKM pk, EvoCriteria[] chain, GameVersion game) { - bool yielded = false; - if (pk.FatefulEncounter) - { - foreach (var enc in EncounterEvent.MGDB_G8B) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - yield return enc; - yielded = true; - break; - } - } - if (yielded) - yield break; - } - - bool wasEgg = pk.Egg_Location switch - { - LocationsHOME.SWSHEgg => true, // Regular hatch location (not link trade) - LocationsHOME.SWBD => pk.Met_Location == LocationsHOME.SWBD, // Link Trade transferred over must match Met Location - LocationsHOME.SHSP => pk.Met_Location == LocationsHOME.SHSP, // Link Trade transferred over must match Met Location - _ => false, - }; - if (wasEgg && pk.Met_Level == 1) - { - var egg = GetEggs(chain, game); - foreach (var enc in egg) - { - yield return enc; - yielded = true; - } - if (yielded) - yield break; - } - - IEncounterable? cache = null; - EncounterMatchRating rating = MaxNotMatch; - - // Trades - { - foreach (var enc in Encounters8b.TradeGift_BDSP) - { - if (enc.Version != GameVersion.BDSP && enc.Version != game) - continue; - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == Match) - { - yield return enc; - } - else if (match < rating) - { - cache = enc; - rating = match; - } - break; - } - } - } - - // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. - var encStatic = game == GameVersion.BD ? Encounters8b.StaticBD : Encounters8b.StaticSP; - foreach (var enc in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (!enc.IsMatchExact(pk, evo)) - break; - - var match = enc.GetMatchRating(pk); - if (match == Match) - { - yield return enc; - } - else if (match < rating) - { - cache = enc; - rating = match; - } - break; - } - } - - // Only yield if Safari and Marsh encounters match. - bool safari = pk is PK8 { Ball: (int)Ball.Safari }; - var encWild = game == GameVersion.BD ? Encounters8b.SlotsBD : Encounters8b.SlotsSP; - foreach (var area in encWild) - { - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - var marsh = Locations.IsSafariZoneLocation8b(area.Location); - if (safari != marsh) - match = DeferredErrors; - if (match == Match) - { - yield return slot; - } - else if (match < rating) - { - cache = slot; - rating = match; - } - } - } - - if (cache != null) - yield return cache; + var iterator = new EncounterEnumerator8bSWSH(pk, chain, game); + foreach (var enc in iterator) + yield return enc.Encounter; } private const int Generation = 8; private const EntityContext Context = EntityContext.Gen8b; private const byte EggLevel = 1; - private static IEnumerable GetEggs(EvoCriteria[] chain, GameVersion version) + public static bool TryGetEgg(ReadOnlySpan chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) { + result = null; var devolved = chain[^1]; if (!devolved.InsideLevelRange(EggLevel)) - yield break; + return false; // Ensure most devolved species is the same as the egg species. var (species, form) = GetBaby(devolved); if (species != devolved.Species && !Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; // not a split-breed. + return false; // not a split-breed. // Sanity Check 1 if (!Breeding.CanHatchAsEgg(species)) - yield break; + return false; // Sanity Check 2 if (!Breeding.CanHatchAsEgg(species, form, Context)) - yield break; + return false; // Sanity Check 3 if (!PersonalTable.BDSP.IsPresentInGame(species, form)) - yield break; + return false; - yield return CreateEggEncounter(species, form, version); + result = CreateEggEncounter(species, form, version); + return true; + } + public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan chain, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; // Check for split-breed - if (species == devolved.Species) + var devolved = chain[^1]; + if (other.Species == devolved.Species) { if (chain.Length < 2) - yield break; // no split-breed + return false; // no split-breed devolved = chain[^2]; } if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species)) - yield break; + return false; - yield return CreateEggEncounter(devolved.Species, devolved.Form, version); + result = other with { Species = devolved.Species, Form = devolved.Form }; + return true; } private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version) diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator9.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator9.cs index d1e727595..b311d2381 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator9.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator9.cs @@ -1,8 +1,6 @@ +using System; using System.Collections.Generic; - -using static PKHeX.Core.EncounterStateUtil; -using static PKHeX.Core.EncounterTypeGroup; -using static PKHeX.Core.EncounterMatchRating; +using System.Diagnostics.CodeAnalysis; using static PKHeX.Core.GameVersion; namespace PKHeX.Core; @@ -15,7 +13,7 @@ public sealed class EncounterGenerator9 : IEncounterGenerator { var chain = EncounterOrigin.GetOriginChain(pk, 9); if (chain.Length == 0) - return System.Array.Empty(); + return Array.Empty(); return (GameVersion)pk.Version switch { @@ -25,400 +23,63 @@ public sealed class EncounterGenerator9 : IEncounterGenerator }; } - public IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) + public IEnumerable GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups) { - if (chain.Length == 0) - yield break; - - if (groups.HasFlag(Mystery)) - { - var table = EncounterEvent.MGDB_G9; - foreach (var enc in GetPossibleEvents(chain, table)) - yield return enc; - } - if (groups.HasFlag(Egg)) - { - var eggs = GetEggs(pk, chain, game); - foreach (var enc in eggs) - yield return enc; - } - if (groups.HasFlag(Static)) - { - var table = game == SL ? Encounters9.StaticSL : Encounters9.StaticVL; - foreach (var enc in GetPossibleStatic(chain, table)) - yield return enc; - } - if (groups.HasFlag(Slot)) - { - var areas = Encounters9.Slots; - foreach (var enc in GetPossibleSlots(chain, areas)) - yield return enc; - } - if (groups.HasFlag(Trade)) - { - var table = Encounters9.TradeGift_SV; - foreach (var enc in GetPossibleTrade(chain, table, game)) - yield return enc; - } + var iterator = new EncounterPossible9(chain, groups, game); + foreach (var enc in iterator) + yield return enc; } - private static IEnumerable GetPossibleEvents(EvoCriteria[] chain, IReadOnlyList table) + public IEnumerable GetEncountersSWSH(PKM pk, EvoCriteria[] chain, GameVersion game) { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleStatic(EvoCriteria[] chain, EncounterStatic[] table) - { - foreach (var enc in table) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - private static IEnumerable GetPossibleSlots(EvoCriteria[] chain, EncounterArea9[] areas) - { - foreach (var area in areas) - { - foreach (var slot in area.Slots) - { - foreach (var evo in chain) - { - if (evo.Species != slot.Species) - continue; - yield return slot; - break; - } - } - } - } - - private static IEnumerable GetPossibleTrade(EvoCriteria[] chain, EncounterTrade9[] table, GameVersion game) - { - foreach (var enc in table) - { - if (enc.Version != SV && enc.Version != game) - continue; - - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - yield return enc; - break; - } - } - } - - public IEnumerable GetEncountersSWSH(PKM pk, EvoCriteria[] chain, GameVersion game) - { - if (pk.FatefulEncounter) - { - bool yielded = false; - foreach (var mg in EncounterEvent.MGDB_G9) - { - foreach (var evo in chain) - { - if (evo.Species != mg.Species) - continue; - - if (mg.IsMatchExact(pk, evo)) - { - yield return mg; - yielded = true; - } - break; - } - } - if (yielded) - yield break; - } - - bool wasEgg = pk.Egg_Location switch - { - LocationsHOME.SWSHEgg => true, // Regular hatch location (not link trade) - LocationsHOME.SWSL => pk.Met_Location == LocationsHOME.SWSL, // Link Trade transferred over must match Met Location - LocationsHOME.SHVL => pk.Met_Location == LocationsHOME.SHVL, // Link Trade transferred over must match Met Location - _ => false, - }; - if (wasEgg && pk.Met_Level == 1) - { - bool yielded = false; - var eggs = GetEggs(pk, chain, game); - foreach (var egg in eggs) - { - yield return egg; - yielded = true; - } - if (yielded) - yield break; - } - - IEncounterable? cache = null; - EncounterMatchRating rating = MaxNotMatch; - - // Trades - { - foreach (var z in Encounters9.TradeGift_SV) - { - foreach (var evo in chain) - { - if (z.Version != SV && z.Version != game) - continue; - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - break; - } - } - if (cache != null) - yield return cache; - } - - if (pk is not IRibbonIndex r || !r.HasEncounterMark()) - { - var encStatic = game == SL ? Encounters9.StaticSL : Encounters9.StaticVL; - foreach (var z in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - break; - } - } - } - - // Wild encounters are more permissive than static encounters. - // Can have encounter marks, can have varied scales/shiny states. - if (CanBeWildEncounter(pk)) - { - var areas = Encounters9.Slots; - foreach (var area in areas) - { - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - if (match == Match) - { - yield return slot; - } - else if (match < rating) - { - cache = slot; - rating = match; - } - } - } - } - - if (cache != null) - yield return cache; + var iterator = new EncounterEnumerator9SWSH(pk, chain, game); + foreach (var enc in iterator) + yield return enc.Encounter; } public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) { - if (pk.FatefulEncounter) - { - bool yielded = false; - foreach (var mg in EncounterEvent.MGDB_G9) - { - foreach (var evo in chain) - { - if (evo.Species != mg.Species) - continue; - - if (mg.IsMatchExact(pk, evo)) - { - yield return mg; - yielded = true; - } - break; - } - } - if (yielded) - yield break; - } - - var game = (GameVersion)pk.Version; - - // While an unhatched picnic egg, the Version remains 0. - if (Locations.IsEggLocationBred9(pk.Egg_Location) && !(pk.IsEgg && game != 0)) - { - bool yielded = false; - var eggs = GetEggs(pk, chain, game); - foreach (var egg in eggs) - { - yield return egg; - yielded = true; - } - if (yielded) - yield break; - } - - IEncounterable? cache = null; - EncounterMatchRating rating = MaxNotMatch; - - // Trades - if (pk.Met_Location == Locations.LinkTrade6NPC) - { - foreach (var z in Encounters9.TradeGift_SV) - { - foreach (var evo in chain) - { - if (z.Version != SV && z.Version != game) - continue; - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - break; - } - } - if (cache != null) - yield return cache; - yield break; - } - - if (pk is not IRibbonIndex r || !r.HasEncounterMark()) - { - var encStatic = game == SL ? Encounters9.StaticSL : Encounters9.StaticVL; - foreach (var z in encStatic) - { - foreach (var evo in chain) - { - if (evo.Species != z.Species) - continue; - if (!z.IsMatchExact(pk, evo)) - break; - - var match = z.GetMatchRating(pk); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - break; - } - } - } - - // Wild encounters are more permissive than static encounters. - // Can have encounter marks, can have varied scales/shiny states. - if (CanBeWildEncounter(pk)) - { - var location = pk.Met_Location; - var areas = Encounters9.Slots; - foreach (var area in areas) - { - if (!area.IsMatchLocation(location)) - continue; - - var slots = area.GetMatchingSlots(pk, chain); - foreach (var slot in slots) - { - var match = slot.GetMatchRating(pk); - if (match == Match) - { - yield return slot; - } - else if (match < rating) - { - cache = slot; - rating = match; - } - } - } - } - - if (cache != null) - yield return cache; + var iterator = new EncounterEnumerator9(pk, chain, (GameVersion)pk.Version); + foreach (var enc in iterator) + yield return enc.Encounter; } private const int Generation = 9; private const EntityContext Context = EntityContext.Gen9; private const byte EggLevel = 1; - private static IEnumerable GetEggs(PKM pk, EvoCriteria[] chain, GameVersion version) + public static bool TryGetEgg(PKM pk, EvoCriteria[] chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) { - var devolved = chain[^1]; - if (!devolved.InsideLevelRange(EggLevel)) - yield break; if (version == 0 && pk.IsEgg) version = SL; + return TryGetEgg(chain, version, out result); + } + + public static bool TryGetEgg(EvoCriteria[] chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result) + { + result = null; + var devolved = chain[^1]; + if (!devolved.InsideLevelRange(EggLevel)) + return false; // Ensure most devolved species is the same as the egg species. // No split-breed to consider. var (species, form) = GetBaby(devolved); if (species != devolved.Species) - yield break; // no split-breed. + return false; // no split-breed. // Sanity Check 1 if (!Breeding.CanHatchAsEgg(species)) - yield break; + return false; // Sanity Check 2 if (!Breeding.CanHatchAsEgg(species, form, Context)) - yield break; + return false; // Sanity Check 3 if (!PersonalTable.SV.IsPresentInGame(species, form)) - yield break; + return false; - yield return CreateEggEncounter(species, form, version); + result = CreateEggEncounter(species, form, version); + return true; } private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version) diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs index 8b42e6e6a..6433e547b 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs @@ -22,11 +22,11 @@ public sealed record EncounterCriteria public AbilityPermission AbilityNumber { get; init; } = Any12H; /// End result's nature. - /// Leave as to not restrict nature. + /// Leave as to not restrict nature. public Nature Nature { get; init; } = Nature.Random; /// End result's shininess. - /// Leave as to not restrict shininess. + /// Leave as to not restrict shininess. public Shiny Shiny { get; init; } public int IV_HP { get; init; } = RandomIV; @@ -187,4 +187,71 @@ public sealed record EncounterCriteria pk.IV_SPD = IV_SPD != RandomIV ? IV_SPD : Util.Rand.Next(32); pk.IV_SPE = IV_SPE != RandomIV ? IV_SPE : Util.Rand.Next(32); } + + public void SetRandomIVs(PKM pk, int flawless) + { + Span ivs = stackalloc[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + flawless -= ivs.Count(31); + int remain = ivs.Count(RandomIV); + if (flawless > remain) + { + // Overwrite specified IVs until we have enough remaining slots. + while (flawless > remain) + { + int index = Util.Rand.Next(6); + if (ivs[index] is RandomIV or 31) + continue; + ivs[index] = RandomIV; + remain++; + } + } + + // Sprinkle in remaining flawless IVs + while (flawless > 0) + { + int index = Util.Rand.Next(6); + if (ivs[index] != RandomIV) + continue; + ivs[index] = 31; + flawless--; + } + // Fill in the rest + for (int i = 0; i < ivs.Length; i++) + { + if (ivs[i] == RandomIV) + ivs[i] = Util.Rand.Next(32); + } + // Done. + pk.SetIVs(ivs); + } + + /// + /// Applies random IVs without any correlation. + /// + /// Entity to mutate. + /// Template to populate from + public void SetRandomIVs(PKM pk, IndividualValueSet template) + { + if (!template.IsSpecified) + { + SetRandomIVs(pk); + return; + } + + pk.IV_HP = Get(template.HP, IV_HP); + pk.IV_ATK = Get(template.ATK, IV_ATK); + pk.IV_DEF = Get(template.DEF, IV_DEF); + pk.IV_SPE = Get(template.SPE, IV_SPE); + pk.IV_SPA = Get(template.SPA, IV_SPA); + pk.IV_SPD = Get(template.SPD, IV_SPD); + + static int Get(sbyte template, int request) + { + if (template != -1) + return template; + if (request != RandomIV) + return request; + return Util.Rand.Next(32); + } + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs index 27f7930f5..76397e375 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs @@ -54,7 +54,7 @@ public static class EncounterFinder break; var match = mx.GetMatchRating(pk); - if (match != EncounterMatchRating.PartialMatch) + if (match < EncounterMatchRating.PartialMatch) break; // Reaching here implies the encounter wasn't valid. Try stepping to the next encounter. @@ -66,7 +66,7 @@ public static class EncounterFinder break; } - if (info is { FrameMatches: false, EncounterMatch: EncounterSlot }) // if false, all valid RNG frame matches have already been consumed + if (info is { FrameMatches: false }) // if false, all valid RNG frame matches have already been consumed info.Parse.Add(new CheckResult(ParseSettings.RNGFrameNotFound, CheckIdentifier.PID, LEncConditionBadRNGFrame)); // todo for further confirmation if (!info.PIDIVMatches) // if false, all valid PIDIV matches have already been consumed info.Parse.Add(new CheckResult(Severity.Invalid, CheckIdentifier.PID, LPIDTypeMismatch)); diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible1.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible1.cs new file mode 100644 index 000000000..d98a0dfab --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible1.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible1(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + TradeStart, + TradeBU, + TradeRB, + TradeYW, + + SlotStart, + SlotBU, + SlotRD, + SlotGN, + SlotYW, + + StaticStart, + StaticBU, + StaticRB, + StaticYW, + StaticShared, + + EventStart, + EventVC, + EventGB, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + if (Version is GameVersion.BU or GameVersion.RBY) + { State = YieldState.TradeBU; goto case YieldState.TradeBU; } + if (Version is GameVersion.YW) + { State = YieldState.TradeYW; goto case YieldState.TradeYW; } + State = YieldState.TradeRB; goto case YieldState.TradeRB; + case YieldState.TradeBU: + if (TryGetNext(Encounters1.TradeGift_BU)) + return true; + Index = 0; + if (Version == GameVersion.BU) + goto case YieldState.StaticStart; + State = YieldState.TradeYW; goto case YieldState.TradeYW; + case YieldState.TradeYW: + if (TryGetNext(Encounters1.TradeGift_YW)) + return true; + Index = 0; + if (Version == GameVersion.YW) + goto case YieldState.StaticStart; + State = YieldState.TradeRB; goto case YieldState.TradeRB; + case YieldState.TradeRB: + if (TryGetNext(Encounters1.TradeGift_RB)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + if (Version is GameVersion.BU or GameVersion.RBY) + { State = YieldState.StaticBU; goto case YieldState.StaticBU; } + if (Version is GameVersion.YW) + { State = YieldState.StaticYW; goto case YieldState.StaticYW; } + State = YieldState.StaticRB; goto case YieldState.StaticRB; + + case YieldState.StaticBU: + if (TryGetNext(Encounters1.StaticBU)) + return true; + Index = 0; + if (Version == GameVersion.BU) + { State = YieldState.StaticShared; goto case YieldState.StaticShared; } + State = YieldState.StaticYW; goto case YieldState.StaticYW; + case YieldState.StaticYW: + if (TryGetNext(Encounters1.StaticYW)) + return true; + if (Version == GameVersion.YW) + { State = YieldState.StaticShared; goto case YieldState.StaticShared; } + State = YieldState.StaticRB; goto case YieldState.StaticRB; + case YieldState.StaticRB: + if (TryGetNext(Encounters1.StaticRB)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticShared: + if (TryGetNext(Encounters1.StaticRBY)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.EventStart; + if (Version is GameVersion.BU or GameVersion.RBY) + { State = YieldState.SlotBU; goto case YieldState.SlotBU; } + if (Version == GameVersion.YW) + { State = YieldState.SlotYW; goto case YieldState.SlotYW; } + if (Version is GameVersion.RD or GameVersion.RB) + { State = YieldState.SlotRD; goto case YieldState.SlotRD; } + if (Version == GameVersion.GN) + { State = YieldState.SlotGN; goto case YieldState.SlotGN; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotBU: + if (TryGetNext(Encounters1.SlotsBU)) + return true; + Index = 0; + if (Version == GameVersion.BU) + goto case YieldState.EventStart; + State = YieldState.SlotYW; goto case YieldState.SlotYW; + case YieldState.SlotYW: + if (TryGetNext(Encounters1.SlotsYW)) + return true; + Index = 0; + if (Version == GameVersion.YW) + goto case YieldState.EventStart; + State = YieldState.SlotRD; goto case YieldState.SlotRD; + case YieldState.SlotRD: + if (TryGetNext(Encounters1.SlotsRD)) + return true; + Index = 0; + if (Version == GameVersion.RD) + goto case YieldState.EventStart; + State = YieldState.SlotGN; goto case YieldState.SlotGN; + case YieldState.SlotGN: + if (TryGetNext(Encounters1.SlotsGN)) + return true; + Index = 0; goto case YieldState.EventStart; + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + break; + if (ParseSettings.AllowGBVirtualConsole3DS) + { State = YieldState.EventVC; goto case YieldState.EventVC; } + State = YieldState.EventGB; goto case YieldState.EventGB; + case YieldState.EventVC: + if (TryGetNext(Encounters1VC.Gifts)) + return true; + break; + case YieldState.EventGB: + if (TryGetNext(Encounters1GBEra.Gifts)) + return true; + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible2.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible2.cs new file mode 100644 index 000000000..32aee365a --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible2.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible2(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version, PKM Entity) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + Bred, + BredCrystal, + + TradeStart, + Trade, + + EventStart, + EventVC, + EventGB, + + StaticStart, + StaticCOdd, + StaticC, + StaticGD, + StaticSI, + StaticGS, + StaticShared, + + SlotStart, + SlotC, + SlotGD, + SlotSI, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.Bred; + + case YieldState.Bred: + if (!Flags.HasFlag(EncounterTypeGroup.Egg)) + goto case YieldState.TradeStart; + // try with specific version, for yielded metadata purposes. + var ver = Version is GameVersion.GD or GameVersion.SI ? Version : GameVersion.GS; + if (!EncounterGenerator2.TryGetEgg(Chain, ver, out var egg)) + goto case YieldState.TradeStart; + State = ParseSettings.AllowGen2Crystal(Entity) ? YieldState.BredCrystal : YieldState.TradeStart; + return SetCurrent(egg); + case YieldState.BredCrystal: + State = YieldState.TradeStart; + if (!EncounterGenerator2.TryGetEgg(Chain, GameVersion.C, out egg)) + goto case YieldState.TradeStart; + return SetCurrent(egg); + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + State = YieldState.Trade; goto case YieldState.Trade; + case YieldState.Trade: + if (TryGetNext(Encounters2.TradeGift_GSC)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + if (Version is GameVersion.C or GameVersion.GSC) + { + if (ParseSettings.AllowGen2OddEgg(Entity)) + { State = YieldState.StaticCOdd; goto case YieldState.StaticCOdd; } + State = YieldState.StaticC; goto case YieldState.StaticC; + } + if (Version is GameVersion.GD or GameVersion.GS) + { State = YieldState.StaticGD; goto case YieldState.StaticGD; } + if (Version == GameVersion.SI) + { State = YieldState.StaticSI; goto case YieldState.StaticSI; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.StaticCOdd: + if (TryGetNext(Encounters2.StaticOddEggC)) + return true; + Index = 0; State = YieldState.StaticC; goto case YieldState.StaticC; + case YieldState.StaticC: + if (TryGetNext(Encounters2.StaticC)) + return true; + Index = 0; + if (Version == GameVersion.C) + { State = YieldState.StaticShared; goto case YieldState.StaticShared; } + State = YieldState.StaticGD; goto case YieldState.StaticGD; + case YieldState.StaticGD: + if (TryGetNext(Encounters2.StaticGD)) + return true; + Index = 0; + if (Version == GameVersion.GD) + { State = YieldState.StaticGS; goto case YieldState.StaticGS; } + State = YieldState.StaticSI; goto case YieldState.StaticSI; + case YieldState.StaticSI: + if (TryGetNext(Encounters2.StaticSI)) + return true; + Index = 0; State = YieldState.StaticGS; goto case YieldState.StaticGS; + case YieldState.StaticGS: + if (TryGetNext(Encounters2.StaticGS)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticShared: + if (TryGetNext(Encounters2.StaticGSC)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.EventStart; + if (Version is GameVersion.C or GameVersion.GSC) + { State = YieldState.SlotC; goto case YieldState.SlotC; } + if (Version is GameVersion.GD or GameVersion.GS) + { State = YieldState.SlotGD; goto case YieldState.SlotGD; } + if (Version == GameVersion.SI) + { State = YieldState.SlotSI; goto case YieldState.SlotSI; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotC: + if (TryGetNextSlot(Encounters2.SlotsC)) + return true; + Index = 0; + if (Version == GameVersion.C) + goto case YieldState.EventStart; + State = YieldState.SlotGD; goto case YieldState.SlotGD; + case YieldState.SlotGD: + if (TryGetNextSlot(Encounters2.SlotsGD)) + return true; + Index = 0; + if (Version == GameVersion.GD) + goto case YieldState.EventStart; + State = YieldState.SlotSI; goto case YieldState.SlotSI; + case YieldState.SlotSI: + if (TryGetNextSlot(Encounters2.SlotsSI)) + return true; + Index = 0; goto case YieldState.EventStart; + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + break; + if (ParseSettings.AllowGBVirtualConsole3DS) + { State = YieldState.EventVC; goto case YieldState.EventVC; } + if (ParseSettings.AllowGBEraEvents) + { State = YieldState.EventGB; goto case YieldState.EventGB; } + throw new InvalidOperationException("No events allowed"); + case YieldState.EventVC: + State = YieldState.End; + if (Chain[^1].Species == (int)Species.Celebi && Version == GameVersion.C) + return SetCurrent(Encounters2.CelebiVC); + break; + case YieldState.EventGB: + if (TryGetNext(Encounters2GBEra.StaticEventsGB)) + return true; + break; + } + return false; + } + + private bool TryGetNextSlot(TArea[] areas) + where TArea : class, IEncounterArea + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(EncounterSlot2[] slots) + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + + if (enc.IsHeadbutt && !enc.IsTreeAvailable(Entity.TID16)) + break; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible3.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible3.cs new file mode 100644 index 000000000..1cabe1613 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible3.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible3(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + Bred, + BredSplit, + + EventStart, + EventColoR, + EventColoS, + Event, + + TradeStart, + TradeRS, + TradeE, + TradeFR, + TradeLG, + TradeFRLG, + + SlotStart, + SlotR, + SlotS, + SlotE, + SlotFR, + SlotLG, + SlotEnd, + + StaticStart, + StaticR, + StaticS, + StaticE, + StaticSharedRSE, + + StaticFR, + StaticLG, + StaticSharedFRLG, + StaticEnd, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.Bred; + + case YieldState.Bred: + if (!EncounterGenerator3.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.EventStart; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + if (!EncounterGenerator3.TryGetSplit((EncounterEgg)Current, Chain, out egg)) + goto case YieldState.EventStart; + State = YieldState.EventStart; + return SetCurrent(egg); + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.EventColoR; goto case YieldState.EventColoR; + case YieldState.EventColoR: + if (TryGetNext(Encounters3RSE.ColoGiftsR)) + return true; + Index = 0; State = YieldState.EventColoS; goto case YieldState.EventColoS; + case YieldState.EventColoS: + if (TryGetNext(Encounters3RSE.ColoGiftsS)) + return true; + Index = 0; State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNextEvent(EncountersWC3.Encounter_WC3)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + if (Version == GameVersion.E) + { State = YieldState.TradeE; goto case YieldState.TradeE; } + if (Version is GameVersion.FR) + { State = YieldState.TradeFR; goto case YieldState.TradeFR; } + if (Version is GameVersion.LG) + { State = YieldState.TradeLG; goto case YieldState.TradeLG; } + State = YieldState.TradeRS; goto case YieldState.TradeRS; + case YieldState.TradeRS: + if (TryGetNext(Encounters3RSE.TradeGift_RS)) + return true; + Index = 0; State = YieldState.TradeE; goto case YieldState.TradeE; + case YieldState.TradeE: + if (TryGetNext(Encounters3RSE.TradeGift_E)) + return true; + Index = 0; goto case YieldState.StaticStart; + case YieldState.TradeFR: + if (TryGetNext(Encounters3FRLG.TradeGift_FR)) + return true; + Index = 0; State = YieldState.TradeFRLG; goto case YieldState.TradeFRLG; + case YieldState.TradeLG: + if (TryGetNext(Encounters3FRLG.TradeGift_LG)) + return true; + Index = 0; State = YieldState.TradeFRLG; goto case YieldState.TradeFRLG; + case YieldState.TradeFRLG: + if (TryGetNext(Encounters3FRLG.TradeGift_FRLG)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + if (Version == GameVersion.R) + { State = YieldState.StaticR; goto case YieldState.StaticR; } + if (Version == GameVersion.S) + { State = YieldState.StaticS; goto case YieldState.StaticS; } + if (Version == GameVersion.E) + { State = YieldState.StaticE; goto case YieldState.StaticE; } + if (Version == GameVersion.FR) + { State = YieldState.StaticFR; goto case YieldState.StaticFR; } + if (Version == GameVersion.LG) + { State = YieldState.StaticLG; goto case YieldState.StaticLG; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticR: + if (TryGetNext(Encounters3RSE.StaticR)) + return true; + Index = 0; State = YieldState.StaticSharedRSE; goto case YieldState.StaticSharedRSE; + case YieldState.StaticS: + if (TryGetNext(Encounters3RSE.StaticS)) + return true; + Index = 0; State = YieldState.StaticSharedRSE; goto case YieldState.StaticSharedRSE; + case YieldState.StaticE: + if (TryGetNext(Encounters3RSE.StaticE)) + return true; + Index = 0; State = YieldState.StaticSharedRSE; goto case YieldState.StaticSharedRSE; + case YieldState.StaticSharedRSE: + if (TryGetNext(Encounters3RSE.StaticRSE)) + return true; + Index = 0; goto case YieldState.StaticEnd; + case YieldState.StaticFR: + if (TryGetNext(Encounters3FRLG.StaticFR)) + return true; + Index = 0; State = YieldState.StaticSharedFRLG; goto case YieldState.StaticSharedFRLG; + case YieldState.StaticLG: + if (TryGetNext(Encounters3FRLG.StaticLG)) + return true; + Index = 0; State = YieldState.StaticSharedFRLG; goto case YieldState.StaticSharedFRLG; + case YieldState.StaticSharedFRLG: + if (TryGetNext(Encounters3FRLG.StaticFRLG)) + return true; + Index = 0; goto case YieldState.StaticEnd; + case YieldState.StaticEnd: + goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.R) + { State = YieldState.SlotR; goto case YieldState.SlotR; } + if (Version == GameVersion.S) + { State = YieldState.SlotS; goto case YieldState.SlotS; } + if (Version == GameVersion.E) + { State = YieldState.SlotE; goto case YieldState.SlotE; } + if (Version == GameVersion.FR) + { State = YieldState.SlotFR; goto case YieldState.SlotFR; } + if (Version == GameVersion.LG) + { State = YieldState.SlotLG; goto case YieldState.SlotLG; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotR: + if (TryGetNext(Encounters3RSE.SlotsR)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotS: + if (TryGetNext(Encounters3RSE.SlotsS)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotE: + if (TryGetNext(Encounters3RSE.SlotsE)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotFR: + if (TryGetNext(Encounters3FRLG.SlotsFR)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotLG: + if (TryGetNext(Encounters3FRLG.SlotsLG)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNextEvent(WC3[] db) + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + if (!enc.Version.Contains(Version)) + continue; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (enc.NotDistributed) + break; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible3GC.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible3GC.cs new file mode 100644 index 000000000..8f2a4a91d --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible3GC.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible3GC(EvoCriteria[] Chain, EncounterTypeGroup Flags) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + TradeStart, + Trade, + + StaticStart, + StaticColo, + StaticColoStarters, + StaticColoGift, + StaticXDShadow, + StaticXDGift, + StaticEReader, + + SlotStart, + SlotXD, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + State = YieldState.Trade; goto case YieldState.Trade; + case YieldState.Trade: + if (TryGetNext(Encounters3XD.Trades)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + { State = YieldState.StaticColo; goto case YieldState.StaticColo; } + case YieldState.StaticColo: + if (TryGetNext(Encounters3Colo.Shadow)) + return true; + Index = 0; State = YieldState.StaticColoStarters; goto case YieldState.StaticColoStarters; + case YieldState.StaticColoStarters: + if (TryGetNext(Encounters3Colo.Starters)) + Index = 0; State = YieldState.StaticColoGift; goto case YieldState.StaticColoGift; + case YieldState.StaticColoGift: + if (TryGetNext(Encounters3Colo.Gifts)) + return true; + Index = 0; State = YieldState.StaticXDShadow; goto case YieldState.StaticXDShadow; + case YieldState.StaticXDShadow: + if (TryGetNext(Encounters3XD.Shadow)) + return true; + Index = 0; State = YieldState.StaticXDGift; goto case YieldState.StaticXDGift; + case YieldState.StaticXDGift: + if (TryGetNext(Encounters3XD.Gifts)) + return true; + Index = 0; State = YieldState.StaticEReader; goto case YieldState.StaticEReader; + case YieldState.StaticEReader: + if (TryGetNext(Encounters3Colo.EReader)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + break; + State = YieldState.SlotXD; goto case YieldState.SlotXD; + case YieldState.SlotXD: + if (TryGetNext(Encounters3XD.Slots)) + return true; + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible4.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible4.cs new file mode 100644 index 000000000..2d4802669 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible4.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible4(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + EventStart, + Event, + EventLocal, + Bred, + BredSplit, + + TradeStart, + TradeRanch, + TradeDPPt, + TradeHGSS, + + StaticStart, + StaticD, + StaticP, + StaticSharedDP, + StaticPt, + StaticSharedDPPt, + + StaticHG, + StaticSS, + StaticSharedHGSS, + StaticPokewalker, + + SlotStart, + SlotD, + SlotP, + SlotPt, + SlotHG, + SlotSS, + SlotEnd, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (Flags.HasFlag(EncounterTypeGroup.Egg)) + goto case YieldState.Bred; + goto case YieldState.EventStart; + + case YieldState.Bred: + if (!EncounterGenerator4.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.EventStart; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + State = YieldState.EventStart; + if (EncounterGenerator4.TryGetSplit((EncounterEgg)Current, Chain, out egg)) + return SetCurrent(egg); + goto case YieldState.EventStart; + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.Event; + if (Chain[^1].Species == (int)Species.Manaphy) + return SetCurrent(EncounterGenerator4.RangerManaphy); + goto case YieldState.Event; + case YieldState.Event: + if (TryGetNextEvent(EncounterEvent.MGDB_G4)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNextEvent(EncounterEvent.EGDB_G4)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + if (Version == GameVersion.D) + { State = YieldState.TradeRanch; goto case YieldState.TradeRanch; } + if (Version is GameVersion.HG or GameVersion.SS) + { State = YieldState.TradeHGSS; goto case YieldState.TradeHGSS; } + State = YieldState.TradeDPPt; goto case YieldState.TradeDPPt; + case YieldState.TradeRanch: + if (TryGetNext(Encounters4DPPt.RanchGifts)) + return true; + Index = 0; State = YieldState.TradeDPPt; goto case YieldState.TradeDPPt; + case YieldState.TradeDPPt: + if (TryGetNext(Encounters4DPPt.TradeGift_DPPtIngame)) + return true; + Index = 0; goto case YieldState.StaticStart; + case YieldState.TradeHGSS: + if (TryGetNext(Encounters4HGSS.TradeGift_HGSS)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + if (Version == GameVersion.D) + { State = YieldState.StaticD; goto case YieldState.StaticD; } + if (Version == GameVersion.P) + { State = YieldState.StaticP; goto case YieldState.StaticP; } + if (Version == GameVersion.Pt) + { State = YieldState.StaticPt; goto case YieldState.StaticPt; } + if (Version == GameVersion.HG) + { State = YieldState.StaticHG; goto case YieldState.StaticHG; } + if (Version == GameVersion.SS) + { State = YieldState.StaticSS; goto case YieldState.StaticSS; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticD: + if (TryGetNext(Encounters4DPPt.StaticD)) + return true; + Index = 0; State = YieldState.StaticSharedDP; goto case YieldState.StaticSharedDP; + case YieldState.StaticP: + if (TryGetNext(Encounters4DPPt.StaticP)) + return true; + Index = 0; State = YieldState.StaticSharedDP; goto case YieldState.StaticSharedDP; + case YieldState.StaticSharedDP: + if (TryGetNext(Encounters4DPPt.StaticDP)) + return true; + Index = 0; State = YieldState.StaticSharedDPPt; goto case YieldState.StaticSharedDPPt; + case YieldState.StaticPt: + if (TryGetNext(Encounters4DPPt.StaticPt)) + return true; + Index = 0; State = YieldState.StaticSharedDPPt; goto case YieldState.StaticSharedDPPt; + case YieldState.StaticSharedDPPt: + if (TryGetNext(Encounters4DPPt.StaticDPPt)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.StaticHG: + if (TryGetNext(Encounters4HGSS.StaticHG)) + return true; + Index = 0; State = YieldState.StaticSharedHGSS; goto case YieldState.StaticSharedHGSS; + case YieldState.StaticSS: + if (TryGetNext(Encounters4HGSS.StaticSS)) + return true; + Index = 0; State = YieldState.StaticSharedHGSS; goto case YieldState.StaticSharedHGSS; + case YieldState.StaticSharedHGSS: + if (TryGetNext(Encounters4HGSS.Encounter_HGSS)) + return true; + Index = 0; State = YieldState.StaticPokewalker; goto case YieldState.StaticPokewalker; + case YieldState.StaticPokewalker: + if (TryGetNext(Encounters4HGSS.Encounter_PokeWalker)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.D) + { State = YieldState.SlotD; goto case YieldState.SlotD; } + if (Version == GameVersion.P) + { State = YieldState.SlotP; goto case YieldState.SlotP; } + if (Version == GameVersion.Pt) + { State = YieldState.SlotPt; goto case YieldState.SlotPt; } + if (Version == GameVersion.HG) + { State = YieldState.SlotHG; goto case YieldState.SlotHG; } + if (Version == GameVersion.SS) + { State = YieldState.SlotSS; goto case YieldState.SlotSS; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotHG: + if (TryGetNext(Encounters4HGSS.SlotsHG)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotSS: + if (TryGetNext(Encounters4HGSS.SlotsSS)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotD: + if (TryGetNext(Encounters4DPPt.SlotsD)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotP: + if (TryGetNext(Encounters4DPPt.SlotsP)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotPt: + if (TryGetNext(Encounters4DPPt.SlotsPt)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextEvent(T[] db) where T : class, IEncounterable, IRestrictVersion + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + if (!enc.CanBeReceivedByVersion((int)Version)) + continue; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible5.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible5.cs new file mode 100644 index 000000000..d9fe477c5 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible5.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible5(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + Event, + EventLocal, + Bred, + BredSplit, + + TradeStart, + TradeW, + TradeB, + TradeBW, + TradeW2, + TradeB2, + TradeB2W2, + + StaticStart, + StaticW, + StaticB, + StaticSharedBW, + StaticEntreeBW, + StaticW2, + StaticB2, + StaticN, + StaticSharedB2W2, + StaticEntreeB2W2, + StaticRadar, + StaticEntreeShared, + + SlotStart, + SlotW, + SlotB, + SlotW2, + SlotB2, + SlotEnd, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (Flags.HasFlag(EncounterTypeGroup.Egg)) + goto case YieldState.Bred; + goto case YieldState.EventStart; + + case YieldState.Bred: + if (!EncounterGenerator5.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.EventStart; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + if (!EncounterGenerator5.TryGetSplit((EncounterEgg)Current, Chain, out egg)) + goto case YieldState.EventStart; + State = YieldState.EventStart; + return SetCurrent(egg); + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNextEvent(EncounterEvent.MGDB_G5)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNextEvent(EncounterEvent.EGDB_G5)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + if (Version == GameVersion.W) + { State = YieldState.TradeW; goto case YieldState.TradeW; } + if (Version == GameVersion.B) + { State = YieldState.TradeB; goto case YieldState.TradeB; } + if (Version == GameVersion.W2) + { State = YieldState.TradeW2; goto case YieldState.TradeW2; } + if (Version == GameVersion.B2) + { State = YieldState.TradeB2; goto case YieldState.TradeB2; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.TradeW: + if (TryGetNext(Encounters5BW.TradeGift_W)) + return true; + Index = 0; State = YieldState.TradeBW; goto case YieldState.TradeBW; + case YieldState.TradeB: + if (TryGetNext(Encounters5BW.TradeGift_B)) + return true; + Index = 0; State = YieldState.TradeBW; goto case YieldState.TradeBW; + case YieldState.TradeW2: + if (TryGetNext(Encounters5B2W2.TradeGift_W2)) + return true; + Index = 0; State = YieldState.TradeB2W2; goto case YieldState.TradeB2W2; + case YieldState.TradeB2: + if (TryGetNext(Encounters5B2W2.TradeGift_B2)) + return true; + Index = 0; State = YieldState.TradeB2W2; goto case YieldState.TradeB2W2; + + case YieldState.TradeBW: + if (TryGetNext(Encounters5BW.TradeGift_BW)) + return true; + Index = 0; goto case YieldState.StaticStart; + case YieldState.TradeB2W2: + if (TryGetNext(Encounters5B2W2.TradeGift_B2W2)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + if (Version == GameVersion.W) + { State = YieldState.StaticW; goto case YieldState.StaticW; } + if (Version == GameVersion.B) + { State = YieldState.StaticB; goto case YieldState.StaticB; } + if (Version == GameVersion.W2) + { State = YieldState.StaticW2; goto case YieldState.StaticW2; } + if (Version == GameVersion.B2) + { State = YieldState.StaticB2; goto case YieldState.StaticB2; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticW: + if (TryGetNext(Encounters5BW.StaticW)) + return true; + Index = 0; State = YieldState.StaticSharedBW; goto case YieldState.StaticSharedBW; + case YieldState.StaticB: + if (TryGetNext(Encounters5BW.StaticB)) + return true; + Index = 0; State = YieldState.StaticSharedBW; goto case YieldState.StaticSharedBW; + case YieldState.StaticSharedBW: + if (TryGetNext(Encounters5BW.Encounter_BW)) + return true; + Index = 0; State = YieldState.StaticEntreeBW; goto case YieldState.StaticEntreeBW; + case YieldState.StaticEntreeBW: + if (TryGetNext(Encounters5BW.DreamWorld_BW)) + return true; + Index = 0; State = YieldState.StaticEntreeShared; goto case YieldState.StaticEntreeShared; + + case YieldState.StaticW2: + if (TryGetNext(Encounters5B2W2.StaticW2)) + return true; + Index = 0; State = YieldState.StaticSharedB2W2; goto case YieldState.StaticSharedB2W2; + case YieldState.StaticB2: + if (TryGetNext(Encounters5B2W2.StaticB2)) + return true; + Index = 0; State = YieldState.StaticSharedB2W2; goto case YieldState.StaticSharedB2W2; + case YieldState.StaticSharedB2W2: + if (TryGetNext(Encounters5B2W2.Encounter_B2W2_Regular)) + return true; + Index = 0; State = YieldState.StaticN; goto case YieldState.StaticN; + case YieldState.StaticN: + if (TryGetNext(Encounters5B2W2.Encounter_B2W2_N)) + return true; + Index = 0; State = YieldState.StaticEntreeB2W2; goto case YieldState.StaticEntreeB2W2; + case YieldState.StaticEntreeB2W2: + if (TryGetNext(Encounters5B2W2.DreamWorld_B2W2)) + return true; + Index = 0; State = YieldState.StaticRadar; goto case YieldState.StaticRadar; + case YieldState.StaticRadar: + if (TryGetNext(Encounters5DR.Encounter_DreamRadar)) + return true; + Index = 0; State = YieldState.StaticEntreeShared; goto case YieldState.StaticEntreeShared; + + case YieldState.StaticEntreeShared: + if (TryGetNext(Encounters5DR.DreamWorld_Common)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.W) + { State = YieldState.SlotW; goto case YieldState.SlotW; } + if (Version == GameVersion.B) + { State = YieldState.SlotB; goto case YieldState.SlotB; } + if (Version == GameVersion.W2) + { State = YieldState.SlotW2; goto case YieldState.SlotW2; } + if (Version == GameVersion.B2) + { State = YieldState.SlotB2; goto case YieldState.SlotB2; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotW2: + if (TryGetNext(Encounters5B2W2.SlotsW2)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotB2: + if (TryGetNext(Encounters5B2W2.SlotsB2)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotW: + if (TryGetNext(Encounters5BW.SlotsW)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotB: + if (TryGetNext(Encounters5BW.SlotsB)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNextEvent(T[] db) where T : class, IEncounterable, IRestrictVersion + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + if (!enc.CanBeReceivedByVersion((int)Version)) + continue; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible6.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible6.cs new file mode 100644 index 000000000..7ea0b1b0e --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible6.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible6(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + Event, + EventLocal, + Bred, + BredTrade, + BredSplit, + BredSplitTrade, + + TradeStart, + TradeXY, + TradeAO, + + StaticStart, + StaticAS, + StaticOR, + StaticSharedAO, + StaticX, + StaticY, + StaticSharedXY, + + SlotStart, + SlotX, + SlotY, + SlotAS, + SlotOR, + SlotEnd, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (Flags.HasFlag(EncounterTypeGroup.Egg)) + goto case YieldState.Bred; + goto case YieldState.EventStart; + + case YieldState.Bred: + if (!EncounterGenerator6.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.EventStart; + State = YieldState.BredTrade; + return SetCurrent(egg); + case YieldState.BredTrade: + State = YieldState.BredSplit; + egg = EncounterGenerator6.MutateEggTrade((EncounterEgg)Current); + return SetCurrent(egg); + case YieldState.BredSplit: + if (!EncounterGenerator6.TryGetSplit((EncounterEgg)Current, Chain, out egg)) + goto case YieldState.EventStart; + State = YieldState.BredSplitTrade; + return SetCurrent(egg); + case YieldState.BredSplitTrade: + State = YieldState.EventStart; + egg = EncounterGenerator6.MutateEggTrade((EncounterEgg)Current); + return SetCurrent(egg); + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNextEvent(EncounterEvent.MGDB_G6)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNextEvent(EncounterEvent.EGDB_G6)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + if (Version is GameVersion.X or GameVersion.Y) + { State = YieldState.TradeXY; goto case YieldState.TradeXY; } + if (Version is GameVersion.AS or GameVersion.OR) + { State = YieldState.TradeAO; goto case YieldState.TradeAO; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.TradeXY: + if (TryGetNext(Encounters6XY.TradeGift_XY)) + return true; + Index = 0; goto case YieldState.StaticStart; + case YieldState.TradeAO: + if (TryGetNext(Encounters6AO.TradeGift_AO)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + if (Version == GameVersion.AS) + { State = YieldState.StaticAS; goto case YieldState.StaticAS; } + if (Version == GameVersion.OR) + { State = YieldState.StaticOR; goto case YieldState.StaticOR; } + if (Version == GameVersion.X) + { State = YieldState.StaticX; goto case YieldState.StaticX; } + if (Version == GameVersion.Y) + { State = YieldState.StaticY; goto case YieldState.StaticY; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticAS: + if (TryGetNext(Encounters6AO.StaticA)) + return true; + Index = 0; State = YieldState.StaticSharedAO; goto case YieldState.StaticSharedAO; + case YieldState.StaticOR: + if (TryGetNext(Encounters6AO.StaticO)) + return true; + Index = 0; State = YieldState.StaticSharedAO; goto case YieldState.StaticSharedAO; + case YieldState.StaticSharedAO: + if (TryGetNext(Encounters6AO.Encounter_AO)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.StaticX: + if (TryGetNext(Encounters6XY.StaticX)) + return true; + Index = 0; State = YieldState.StaticSharedXY; goto case YieldState.StaticSharedXY; + case YieldState.StaticY: + if (TryGetNext(Encounters6XY.StaticY)) + return true; + Index = 0; State = YieldState.StaticSharedXY; goto case YieldState.StaticSharedXY; + case YieldState.StaticSharedXY: + if (TryGetNext(Encounters6XY.Encounter_XY)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.AS) + { State = YieldState.SlotAS; goto case YieldState.SlotAS; } + if (Version == GameVersion.OR) + { State = YieldState.SlotOR; goto case YieldState.SlotOR; } + if (Version == GameVersion.X) + { State = YieldState.SlotX; goto case YieldState.SlotX; } + if (Version == GameVersion.Y) + { State = YieldState.SlotY; goto case YieldState.SlotY; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotAS: + if (TryGetNext(Encounters6AO.SlotsA)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotOR: + if (TryGetNext(Encounters6AO.SlotsO)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotX: + if (TryGetNext(Encounters6XY.SlotsX)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotY: + if (TryGetNext(Encounters6XY.SlotsY)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNextEvent(T[] db) where T : class, IEncounterable, IRestrictVersion + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + if (!enc.CanBeReceivedByVersion((int)Version)) + continue; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7.cs new file mode 100644 index 000000000..c875d799b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible7(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + Event, + EventLocal, + Bred, + BredTrade, + BredSplit, + BredSplitTrade, + + TradeStart, + TradeSM, + TradeUSUM, + + StartCaptures, + + SlotStart, + SlotSN, + SlotMN, + SlotUS, + SlotUM, + SlotEnd, + + StaticStart, + StaticUS, + StaticUM, + StaticSharedUSUM, + StaticSN, + StaticMN, + StaticSharedSM, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (Flags.HasFlag(EncounterTypeGroup.Egg)) + goto case YieldState.Bred; + goto case YieldState.EventStart; + + case YieldState.Bred: + if (!EncounterGenerator7.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.EventStart; + State = YieldState.BredTrade; + return SetCurrent(egg); + case YieldState.BredTrade: + State = YieldState.BredSplit; + egg = EncounterGenerator7.MutateEggTrade((EncounterEgg)Current); + return SetCurrent(egg); + case YieldState.BredSplit: + if (!EncounterGenerator7.TryGetSplit((EncounterEgg)Current, Chain, out egg)) + goto case YieldState.EventStart; + State = YieldState.BredSplitTrade; + return SetCurrent(egg); + case YieldState.BredSplitTrade: + State = YieldState.EventStart; + egg = EncounterGenerator7.MutateEggTrade((EncounterEgg)Current); + return SetCurrent(egg); + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNextEvent(EncounterEvent.MGDB_G7)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNextEvent(EncounterEvent.EGDB_G7)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (Version is GameVersion.SN or GameVersion.MN) + { State = YieldState.TradeSM; goto case YieldState.TradeSM; } + if (Version is GameVersion.US or GameVersion.UM) + { State = YieldState.TradeUSUM; goto case YieldState.TradeUSUM; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.TradeSM: + if (TryGetNext(Encounters7SM.TradeGift_SM)) + return true; + Index = 0; goto case YieldState.StartCaptures; + case YieldState.TradeUSUM: + if (TryGetNext(Encounters7USUM.TradeGift_USUM)) + return true; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (Version == GameVersion.US) + { State = YieldState.StaticUS; goto case YieldState.StaticUS; } + if (Version == GameVersion.UM) + { State = YieldState.StaticUM; goto case YieldState.StaticUM; } + if (Version == GameVersion.SN) + { State = YieldState.StaticSN; goto case YieldState.StaticSN; } + if (Version == GameVersion.MN) + { State = YieldState.StaticMN; goto case YieldState.StaticMN; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticUS: + if (TryGetNext(Encounters7USUM.StaticUS)) + return true; + Index = 0; State = YieldState.StaticSharedUSUM; goto case YieldState.StaticSharedUSUM; + case YieldState.StaticUM: + if (TryGetNext(Encounters7USUM.StaticUM)) + return true; + Index = 0; State = YieldState.StaticSharedUSUM; goto case YieldState.StaticSharedUSUM; + case YieldState.StaticSharedUSUM: + if (TryGetNext(Encounters7USUM.StaticUSUM)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.StaticSN: + if (TryGetNext(Encounters7SM.StaticSN)) + return true; + Index = 0; State = YieldState.StaticSharedSM; goto case YieldState.StaticSharedSM; + case YieldState.StaticMN: + if (TryGetNext(Encounters7SM.StaticMN)) + return true; + Index = 0; State = YieldState.StaticSharedSM; goto case YieldState.StaticSharedSM; + case YieldState.StaticSharedSM: + if (TryGetNext(Encounters7SM.StaticSM)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (Version == GameVersion.US) + { State = YieldState.SlotUS; goto case YieldState.SlotUS; } + if (Version == GameVersion.UM) + { State = YieldState.SlotUM; goto case YieldState.SlotUM; } + if (Version == GameVersion.SN) + { State = YieldState.SlotSN; goto case YieldState.SlotSN; } + if (Version == GameVersion.MN) + { State = YieldState.SlotMN; goto case YieldState.SlotMN; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotUS: + if (TryGetNext(Encounters7USUM.SlotsUS)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotUM: + if (TryGetNext(Encounters7USUM.SlotsUM)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotSN: + if (TryGetNext(Encounters7SM.SlotsSN)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotMN: + if (TryGetNext(Encounters7SM.SlotsMN)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNextEvent(T[] db) where T : class, IEncounterable, IRestrictVersion + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + if (!enc.CanBeReceivedByVersion((int)Version)) + continue; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7GG.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7GG.cs new file mode 100644 index 000000000..844acc38a --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7GG.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible7GG(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + Event, + EventLocal, + + TradeStart, + TradeGP, + TradeGE, + TradeShared, + + StaticStart, + StaticGP, + StaticGE, + StaticShared, + + SlotStart, + SlotGP, + SlotGE, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.EventStart; + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNextEvent(EncounterEvent.MGDB_G7GG)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNextEvent(EncounterEvent.EGDB_G7GG)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + if (Version == GameVersion.GP) + { State = YieldState.TradeGP; goto case YieldState.TradeGP; } + if (Version == GameVersion.GE) + { State = YieldState.TradeGE; goto case YieldState.TradeGE; } + break; + case YieldState.TradeGP: + if (TryGetNext(Encounters7GG.TradeGift_GP)) + return true; + Index = 0; State = YieldState.TradeShared; goto case YieldState.TradeShared; + case YieldState.TradeGE: + if (TryGetNext(Encounters7GG.TradeGift_GE)) + return true; + Index = 0; State = YieldState.TradeShared; goto case YieldState.TradeShared; + case YieldState.TradeShared: + if (TryGetNext(Encounters7GG.TradeGift_GG)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + if (Version == GameVersion.GP) + { State = YieldState.StaticGP; goto case YieldState.StaticGP; } + if (Version == GameVersion.GE) + { State = YieldState.StaticGE; goto case YieldState.StaticGE; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticGP: + if (TryGetNext(Encounters7GG.StaticGP)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticGE: + if (TryGetNext(Encounters7GG.StaticGE)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticShared: + if (TryGetNext(Encounters7GG.Encounter_GG)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + break; + if (Version is GameVersion.GP) + { State = YieldState.SlotGP; goto case YieldState.SlotGP; } + if (Version is GameVersion.GE) + { State = YieldState.SlotGE; goto case YieldState.SlotGE; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotGP: + if (TryGetNext(Encounters7GG.SlotsGP)) + return true; + break; + case YieldState.SlotGE: + if (TryGetNext(Encounters7GG.SlotsGE)) + return true; + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNextEvent(T[] db) where T : class, IEncounterable, IRestrictVersion + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + if (!enc.CanBeReceivedByVersion((int)Version)) + continue; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7GO.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7GO.cs new file mode 100644 index 000000000..2f4fef9d2 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible7GO.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible7GO(EvoCriteria[] Chain, EncounterTypeGroup Flags) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + private int EvoIndex; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Seek, + Slot, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + break; + State = YieldState.Slot; + goto case YieldState.Seek; + case YieldState.Seek: + if (!SeekNextArea(EncountersGO.SlotsGO_GG)) + break; + goto case YieldState.Slot; + case YieldState.Slot: + var group = EncountersGO.SlotsGO_GG[Index]; + if (TryGetNext(group.Slots)) + return true; + goto case YieldState.Seek; + } + return false; + } + + private bool SeekNextArea(TArea[] areas) where TArea : ISpeciesForm + { + for (; Index < areas.Length; Index++, EvoIndex = 0) + { + var area = areas[Index]; + do + { + if (area.Species != Chain[EvoIndex].Species) + continue; + return true; + } + while (++EvoIndex < Chain.Length); + } + return false; + } + + private bool TryGetNext(TSlot[] slots) where TSlot : IEncounterable, IEncounterMatch + { + var evo = Chain[EvoIndex]; + for (; SubIndex < slots.Length;) + { + var enc = slots[SubIndex++]; + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + EvoIndex = 0; SubIndex = 0; Index++; + return false; + } + + private bool SetCurrent(in IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8.cs new file mode 100644 index 000000000..47eaddeda --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible8(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + Event, + EventLocal, + Bred, + BredSplit, + + TradeStart, + TradeSW, + TradeSH, + TradeShared, + + StaticStart, + + SlotStart, + SlotSW, + SlotSH, + SlotSWHidden, + SlotSHHidden, + SlotEnd, + + StaticVersion, + StaticVersionSW, + StaticVersionSH, + StaticShared, + NestSW, NestSH, DistSW, DistSH, DynamaxAdv, Crystal, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (Flags.HasFlag(EncounterTypeGroup.Egg)) + goto case YieldState.Bred; + goto case YieldState.EventStart; + + case YieldState.Bred: + if (!EncounterGenerator8.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.EventStart; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + if (!EncounterGenerator8.TryGetSplit((EncounterEgg)Current, Chain, out egg)) + goto case YieldState.EventStart; + State = YieldState.EventStart; + return SetCurrent(egg); + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G8)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G8)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + if (Version == GameVersion.SW) + { State = YieldState.TradeSW; goto case YieldState.TradeSW; } + if (Version == GameVersion.SH) + { State = YieldState.TradeSH; goto case YieldState.TradeSH; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.TradeSW: + if (TryGetNext(Encounters8.TradeSW)) + return true; + Index = 0; State = YieldState.TradeShared; goto case YieldState.TradeShared; + case YieldState.TradeSH: + if (TryGetNext(Encounters8.TradeSH)) + return true; + Index = 0; State = YieldState.TradeShared; goto case YieldState.TradeShared; + case YieldState.TradeShared: + if (TryGetNext(Encounters8.TradeSWSH)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + goto case YieldState.StaticVersion; + + case YieldState.StaticVersion: + if (Version == GameVersion.SW) + { State = YieldState.StaticVersionSW; goto case YieldState.StaticVersionSW; } + if (Version == GameVersion.SH) + { State = YieldState.StaticVersionSH; goto case YieldState.StaticVersionSH; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticVersionSW: + if (TryGetNext(Encounters8.StaticSW)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticVersionSH: + if (TryGetNext(Encounters8.StaticSH)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + + case YieldState.StaticShared: + if (TryGetNext(Encounters8.StaticSWSH)) + return true; + Index = 0; State = YieldState.NestSW; goto case YieldState.NestSW; + + case YieldState.NestSW: + if (TryGetNext(Encounters8Nest.Nest_SW)) + return true; + Index = 0; State = YieldState.NestSH; goto case YieldState.NestSH; + case YieldState.NestSH: + if (TryGetNext(Encounters8Nest.Nest_SH)) + return true; + Index = 0; State = YieldState.DistSW; goto case YieldState.DistSW; + case YieldState.DistSW: + if (TryGetNext(Encounters8Nest.Dist_SW)) + return true; + Index = 0; State = YieldState.DistSH; goto case YieldState.DistSH; + case YieldState.DistSH: + if (TryGetNext(Encounters8Nest.Dist_SH)) + return true; + Index = 0; State = YieldState.DynamaxAdv; goto case YieldState.DynamaxAdv; + case YieldState.DynamaxAdv: + if (TryGetNext(Encounters8Nest.DynAdv_SWSH)) + return true; + Index = 0; State = YieldState.Crystal; goto case YieldState.Crystal; + case YieldState.Crystal: + if (TryGetNext(Encounters8Nest.Crystal_SWSH)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.SW) + { State = YieldState.SlotSW; goto case YieldState.SlotSW; } + if (Version == GameVersion.SH) + { State = YieldState.SlotSH; goto case YieldState.SlotSH; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotSW: + if (TryGetNext(Encounters8.SlotsSW_Symbol)) + return true; + Index = 0; State = YieldState.SlotSWHidden; goto case YieldState.SlotSWHidden; + case YieldState.SlotSH: + if (TryGetNext(Encounters8.SlotsSH_Symbol)) + return true; + Index = 0; State = YieldState.SlotSHHidden; goto case YieldState.SlotSHHidden; + case YieldState.SlotSWHidden: + if (TryGetNext(Encounters8.SlotsSW_Hidden)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotSHHidden: + if (TryGetNext(Encounters8.SlotsSH_Hidden)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8GO.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8GO.cs new file mode 100644 index 000000000..0341fcf78 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8GO.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible8GO(EvoCriteria[] Chain, EncounterTypeGroup Flags) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + private int EvoIndex; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Seek, + Slot, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + break; + State = YieldState.Slot; + goto case YieldState.Seek; + case YieldState.Seek: + if (!SeekNextArea(EncountersGO.SlotsGO)) + break; + goto case YieldState.Slot; + case YieldState.Slot: + var group = EncountersGO.SlotsGO[Index]; + if (TryGetNext(group.Slots)) + return true; + goto case YieldState.Seek; + } + return false; + } + + private bool SeekNextArea(TArea[] areas) where TArea : ISpeciesForm + { + for (; Index < areas.Length; Index++, EvoIndex = 0) + { + var area = areas[Index]; + do + { + if (area.Species != Chain[EvoIndex].Species) + continue; + return true; + } + while (++EvoIndex < Chain.Length); + } + return false; + } + + private bool TryGetNext(TSlot[] slots) where TSlot : IEncounterable, IEncounterMatch + { + var evo = Chain[EvoIndex]; + for (; SubIndex < slots.Length;) + { + var enc = slots[SubIndex++]; + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + EvoIndex = 0; SubIndex = 0; Index++; + return false; + } + + private bool SetCurrent(in IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8a.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8a.cs new file mode 100644 index 000000000..5eddd1330 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8a.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible8a(EvoCriteria[] Chain, EncounterTypeGroup Flags) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + Event, + EventLocal, + + StaticStart, + Static, + + SlotStart, + Slot, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.EventStart; + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.StaticStart; + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G8A)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G8A)) + return true; + Index = 0; State = YieldState.Static; goto case YieldState.Static; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + State = YieldState.Static; goto case YieldState.Static; + case YieldState.Static: + if (TryGetNextSub(Encounters8a.StaticLA)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + break; + State = YieldState.Slot; goto case YieldState.Slot; + case YieldState.Slot: + if (TryGetNext(Encounters8a.SlotsLA)) + return true; + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8b.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8b.cs new file mode 100644 index 000000000..64bfa767a --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible8b.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible8b(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + Event, + EventLocal, + Bred, + BredSplit, + + TradeStart, + Trade, + + StaticVersion, + StaticVersionBD, + StaticVersionSP, + StaticShared, + + SlotStart, + SlotBD, + SlotSP, + SlotEnd, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (Flags.HasFlag(EncounterTypeGroup.Egg)) + goto case YieldState.Bred; + goto case YieldState.EventStart; + + case YieldState.Bred: + if (!EncounterGenerator8b.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.EventStart; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + if (!EncounterGenerator8b.TryGetSplit((EncounterEgg)Current, Chain, out egg)) + goto case YieldState.EventStart; + State = YieldState.EventStart; + return SetCurrent(egg); + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G8B)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G8B)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticVersion; + State = YieldState.Trade; goto case YieldState.Trade; + case YieldState.Trade: + if (TryGetNext(Encounters8b.TradeGift_BDSP)) + return true; + { Index = 0; goto case YieldState.StaticVersion; } + + case YieldState.StaticVersion: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + if (Version == GameVersion.BD) + { State = YieldState.StaticVersionBD; goto case YieldState.StaticVersionBD; } + if (Version == GameVersion.SP) + { State = YieldState.StaticVersionSP; goto case YieldState.StaticVersionSP; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticVersionBD: + if (TryGetNext(Encounters8b.StaticBD)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticVersionSP: + if (TryGetNext(Encounters8b.StaticSP)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + + case YieldState.StaticShared: + if (TryGetNext(Encounters8b.Encounter_BDSP)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.SlotEnd; + if (Version is GameVersion.BD) + { State = YieldState.SlotBD; goto case YieldState.SlotBD; } + if (Version is GameVersion.SP) + { State = YieldState.SlotSP; goto case YieldState.SlotSP; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotBD: + if (TryGetNext(Encounters8b.SlotsBD)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotSP: + if (TryGetNext(Encounters8b.SlotsSP)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible9.cs b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible9.cs new file mode 100644 index 000000000..4b6c59033 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Possible/EncounterPossible9.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterPossible9(EvoCriteria[] Chain, EncounterTypeGroup Flags, GameVersion Version) : IEnumerator +{ + public IEncounterable Current { get; private set; } + + private int Index; + private int SubIndex; + private YieldState State; + readonly object IEnumerator.Current => Current; + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + Event, + EventLocal, + Bred, + TradeStart, + Trade, + + StaticStart, + + SlotStart, + Slot, + SlotEnd, + + StaticVersion, + StaticVersionSL, + StaticVersionVL, + StaticShared, + StaticFixed, + StaticTera, + StaticDist, + StaticMight, + StaticEnd, + + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.EventStart; + + case YieldState.EventStart: + if (!Flags.HasFlag(EncounterTypeGroup.Mystery)) + goto case YieldState.TradeStart; + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G9)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G9)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (!Flags.HasFlag(EncounterTypeGroup.Trade)) + goto case YieldState.StaticStart; + State = YieldState.Trade; goto case YieldState.Trade; + case YieldState.Trade: + if (TryGetNext(Encounters9.TradeGift_SV)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (!Flags.HasFlag(EncounterTypeGroup.Static)) + goto case YieldState.SlotStart; + goto case YieldState.StaticVersion; + case YieldState.StaticVersion: + if (Version == GameVersion.SL) + { State = YieldState.StaticVersionSL; goto case YieldState.StaticVersionSL; } + if (Version == GameVersion.VL) + { State = YieldState.StaticVersionVL; goto case YieldState.StaticVersionVL; } + break; + + case YieldState.StaticVersionSL: + if (TryGetNext(Encounters9.StaticSL)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticVersionVL: + if (TryGetNext(Encounters9.StaticVL)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticShared: + if (TryGetNext(Encounters9.Encounter_SV)) + return true; + Index = 0; State = YieldState.StaticFixed; goto case YieldState.StaticFixed; + + case YieldState.StaticFixed: + if (TryGetNext(Encounters9.Fixed)) + return true; + Index = 0; State = YieldState.StaticTera; goto case YieldState.StaticTera; + case YieldState.StaticTera: + if (TryGetNext(Encounters9.Tera)) + return true; + Index = 0; State = YieldState.StaticDist; goto case YieldState.StaticDist; + case YieldState.StaticDist: + if (TryGetNext(Encounters9.Dist)) + return true; + Index = 0; State = YieldState.StaticMight; goto case YieldState.StaticMight; + case YieldState.StaticMight: + if (TryGetNext(Encounters9.Might)) + return true; + Index = 0; goto case YieldState.StaticEnd; + case YieldState.StaticEnd: + goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!Flags.HasFlag(EncounterTypeGroup.Slot)) + goto case YieldState.Bred; + goto case YieldState.Slot; + case YieldState.Slot: + if (TryGetNext(Encounters9.Slots)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + goto case YieldState.Bred; + + case YieldState.Bred: + if (!Flags.HasFlag(EncounterTypeGroup.Egg)) + break; + State = YieldState.End; + if (EncounterGenerator9.TryGetEgg(Chain, Version, out var egg)) + return SetCurrent(egg); + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + return SetCurrent(enc); + } + } + return false; + } + + private bool SetCurrent(IEncounterable match) + { + Current = match; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/Dirtied/EncounterEnumerator8bSWSH.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/Dirtied/EncounterEnumerator8bSWSH.cs new file mode 100644 index 000000000..22c715f5d --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/Dirtied/EncounterEnumerator8bSWSH.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator8bSWSH(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + private bool hasOriginalLocation; + private bool mustBeWild; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + Bred, + BredSplit, + TradeStart, + Trade, + + StartCaptures, + + SlotStart, + SlotBD, + SlotSP, + SlotEnd, + + StaticVersion, + StaticVersionBD, + StaticVersionSP, + StaticShared, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + Debug.Assert(Entity is PK8); + if (Chain.Length == 0) + break; + + if (!Entity.FatefulEncounter) + goto case YieldState.Bred; + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G8B)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + if (!WasBredEggBDSP()) + goto case YieldState.TradeStart; + if (!EncounterGenerator8b.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.TradeStart; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + if (!EncounterGenerator8b.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg)) + goto case YieldState.TradeStart; + State = YieldState.End; + return SetCurrent(egg); + + case YieldState.TradeStart: + goto case YieldState.Trade; + case YieldState.Trade: + if (TryGetNext(Encounters8b.TradeGift_BDSP)) + { State = YieldState.End; return true; } + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + if (mustBeWild) + goto case YieldState.SlotStart; + goto case YieldState.StaticVersion; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + if (Version is GameVersion.BD) + { State = YieldState.SlotBD; goto case YieldState.SlotBD; } + if (Version is GameVersion.SP) + { State = YieldState.SlotSP; goto case YieldState.SlotSP; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotBD: + if (TryGetNext(Encounters8b.SlotsBD)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotSP: + if (TryGetNext(Encounters8b.SlotsSP)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + if (!mustBeWild) + goto case YieldState.Fallback; // already checked everything else + goto case YieldState.StaticVersion; + + case YieldState.StaticVersion: + if (Version == GameVersion.BD) + { State = YieldState.StaticVersionBD; goto case YieldState.StaticVersionBD; } + if (Version == GameVersion.SP) + { State = YieldState.StaticVersionSP; goto case YieldState.StaticVersionSP; } + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticVersionBD: + if (TryGetNext(Encounters8b.StaticBD)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticVersionSP: + if (TryGetNext(Encounters8b.StaticSP)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + + case YieldState.StaticShared: + if (TryGetNext(Encounters8b.Encounter_BDSP)) + return true; + if (mustBeWild) + goto case YieldState.Fallback; // already checked everything else + Index = 0; goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + case YieldState.End: + return false; + } + return false; + } + + private readonly bool WasBredEggBDSP() => Entity.Met_Level == EggStateLegality.EggMetLevel && Entity.Egg_Location switch + { + LocationsHOME.SWSHEgg => true, // Regular hatch location (not link trade) + LocationsHOME.SWBD => Entity.Met_Location == LocationsHOME.SWBD, // Link Trade transferred over must match Met Location + LocationsHOME.SHSP => Entity.Met_Location == LocationsHOME.SHSP, // Link Trade transferred over must match Met Location + _ => false, + }; + + private void InitializeWildLocationInfo() + { + mustBeWild = Entity.Ball == (byte)Ball.Safari; + met = Entity.Met_Location; + var location = met; + var remap = LocationsHOME.GetRemapState(EntityContext.Gen8b, Entity.Context); + hasOriginalLocation = true; + if (remap.HasFlag(LocationRemapState.Remapped)) + hasOriginalLocation = location != LocationsHOME.GetMetSWSH((ushort)location, (int)Version); + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (hasOriginalLocation && !area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/Dirtied/EncounterEnumerator9SWSH.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/Dirtied/EncounterEnumerator9SWSH.cs new file mode 100644 index 000000000..7987c69cb --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/Dirtied/EncounterEnumerator9SWSH.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator9SWSH(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + //private int met; + private bool mustBeSlot; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + Bred, + TradeStart, + Trade, + + StartCaptures, + + SlotStart, + Slot, + SlotEnd, + + StaticVersion, + StaticVersionSL, + StaticVersionVL, + StaticShared, + StaticFixed, + StaticTera, + StaticDist, + StaticMight, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + + if (!Entity.FatefulEncounter) + goto case YieldState.Bred; + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G9)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + State = YieldState.TradeStart; + if (WasBredEggSWSH() && EncounterGenerator9.TryGetEgg(Entity, Chain, Version, out var egg)) + return SetCurrent(egg); + goto case YieldState.TradeStart; + + case YieldState.TradeStart: + //if (Entity.Met_Location == Locations.LinkTrade6NPC) + // goto case YieldState.Trade; + goto case YieldState.StartCaptures; + case YieldState.Trade: + if (TryGetNext(Encounters9.TradeGift_SV)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + if (mustBeSlot) + goto case YieldState.SlotStart; + goto case YieldState.StaticVersion; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + State = YieldState.Slot; goto case YieldState.Slot; + case YieldState.Slot: + if (TryGetNext(Encounters9.Slots)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + if (!mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + goto case YieldState.StaticVersion; + + case YieldState.StaticVersion: + if (Version == GameVersion.SL) + { State = YieldState.StaticVersionSL; goto case YieldState.StaticVersionSL; } + if (Version == GameVersion.VL) + { State = YieldState.StaticVersionVL; goto case YieldState.StaticVersionVL; } + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticVersionSL: + if (TryGetNext(Encounters9.StaticSL)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticVersionVL: + if (TryGetNext(Encounters9.StaticVL)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + + case YieldState.StaticShared: + if (TryGetNext(Encounters9.Encounter_SV)) + return true; + Index = 0; State = YieldState.StaticFixed; goto case YieldState.StaticFixed; + + case YieldState.StaticFixed: + if (TryGetNext(Encounters9.Fixed)) + return true; + Index = 0; State = YieldState.StaticTera; goto case YieldState.StaticTera; + case YieldState.StaticTera: + if (TryGetNext(Encounters9.Tera)) + return true; + Index = 0; State = YieldState.StaticDist; goto case YieldState.StaticDist; + case YieldState.StaticDist: + if (TryGetNext(Encounters9.Dist)) + return true; + Index = 0; State = YieldState.StaticMight; goto case YieldState.StaticMight; + case YieldState.StaticMight: + if (TryGetNext(Encounters9.Might)) + return true; + if (mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + Index = 0; goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private readonly bool WasBredEggSWSH() => Entity.Met_Level == EggStateLegality.EggMetLevel && Entity.Egg_Location switch + { + LocationsHOME.SWSHEgg => true, // Regular hatch location (not link trade) + LocationsHOME.SWSL => Entity.Met_Location == LocationsHOME.SWSL, // Link Trade transferred over must match Met Location + LocationsHOME.SHVL => Entity.Met_Location == LocationsHOME.SHVL, // Link Trade transferred over must match Met Location + _ => false, + }; + + private void InitializeWildLocationInfo() + { + mustBeSlot = Entity is IRibbonIndex r && r.HasEncounterMark(); + //met = Entity.Met_Location; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + //if (!area.IsMatchLocation(met)) + // continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator1.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator1.cs new file mode 100644 index 000000000..7569a326b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator1.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator1(PKM Entity, EvoCriteria[] Chain) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + public MatchedEncounter Current { get; private set; } + private YieldState State; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + TradeStart, + TradeBU, + TradeRB, + TradeYW, + + SlotStart, + SlotBU, + SlotRD, + SlotGN, + SlotYW, + + StaticStart, + StaticBU, + StaticRB, + StaticYW, + StaticShared, + + EventStart, + EventVC, + EventGB, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (Entity.Japanese) + { State = YieldState.TradeBU; goto case YieldState.TradeBU; } + State = YieldState.TradeRB; goto case YieldState.TradeRB; + case YieldState.TradeBU: + if (TryGetNext(Encounters1.TradeGift_BU)) + return true; + Index = 0; State = YieldState.TradeRB; goto case YieldState.TradeRB; + case YieldState.TradeRB: + if (TryGetNext(Encounters1.TradeGift_RB)) + return true; + Index = 0; State = YieldState.TradeYW; goto case YieldState.TradeYW; + case YieldState.TradeYW: + if (TryGetNext(Encounters1.TradeGift_YW)) + return true; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (Entity.Japanese) + { State = YieldState.StaticBU; goto case YieldState.StaticBU; } + State = YieldState.StaticRB; goto case YieldState.StaticRB; + + case YieldState.StaticBU: + if (TryGetNext(Encounters1.StaticBU)) + return true; + Index = 0; State = YieldState.StaticRB; goto case YieldState.StaticRB; + case YieldState.StaticRB: + if (TryGetNext(Encounters1.StaticRB)) + return true; + Index = 0; State = YieldState.StaticYW; goto case YieldState.StaticYW; + case YieldState.StaticYW: + if (TryGetNext(Encounters1.StaticYW)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticShared: + if (TryGetNext(Encounters1.StaticRBY)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (Entity.Japanese) + { State = YieldState.SlotBU; goto case YieldState.SlotBU; } + State = YieldState.SlotRD; goto case YieldState.SlotRD; + case YieldState.SlotBU: + if (TryGetNext(Encounters1.SlotsBU)) + return true; + Index = 0; State = YieldState.SlotRD; goto case YieldState.SlotRD; + case YieldState.SlotRD: + if (TryGetNext(Encounters1.SlotsRD)) + return true; + Index = 0; State = YieldState.SlotGN; goto case YieldState.SlotGN; + case YieldState.SlotGN: + if (TryGetNext(Encounters1.SlotsGN)) + return true; + Index = 0; State = YieldState.SlotYW; goto case YieldState.SlotYW; + case YieldState.SlotYW: + if (TryGetNext(Encounters1.SlotsYW)) + return true; + Index = 0; goto case YieldState.EventStart; + + case YieldState.EventStart: + if (ParseSettings.AllowGBVirtualConsole3DS) + { State = YieldState.EventVC; goto case YieldState.EventVC; } + State = YieldState.EventGB; goto case YieldState.EventGB; + case YieldState.EventVC: + if (TryGetNext(Encounters1VC.Gifts)) + return true; + goto case YieldState.Fallback; + case YieldState.EventGB: + if (TryGetNext(Encounters1GBEra.Gifts)) + return true; + goto case YieldState.Fallback; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : IEncounterArea + where TSlot : IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + //if (!area.IsMatchLocation(met)) + // continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) + where T : IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator2.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator2.cs new file mode 100644 index 000000000..fb5324969 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator2.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator2 : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + public MatchedEncounter Current { get; private set; } + + private YieldState State; + private readonly PKM Entity; + private readonly EvoCriteria[] Chain; + private readonly int met; + private readonly bool hasOriginalMet; + private readonly bool canOriginateCrystal; + + public EncounterEnumerator2(PKM pk, EvoCriteria[] chain) + { + Entity = pk; + Chain = chain; + + if (pk is ICaughtData2 { CaughtData: not 0 } c2) + { + canOriginateCrystal = true; + hasOriginalMet = true; + met = c2.Met_Location; + } + else + { + canOriginateCrystal = pk is { Format: >= 7, Korean: false } || pk.CanInhabitGen1(); + } + } + + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + Trade, + Bred, + BredCrystal, // different egg moves + + EventStart, + EventVC, + EventGB, + + StaticStart, + StaticCOdd, + StaticC, + StaticGD, + StaticSI, + StaticGS, + StaticShared, + + SlotStart, + SlotC, + SlotGD, + SlotSI, + SlotEnd, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.EventStart; + + case YieldState.EventStart: + if (Entity.Korean) + { State = YieldState.Trade; goto case YieldState.Trade; } + if (ParseSettings.AllowGBVirtualConsole3DS) + { State = YieldState.EventVC; goto case YieldState.EventVC; } + if (ParseSettings.AllowGBEraEvents) + { State = YieldState.EventGB; goto case YieldState.EventGB; } + throw new InvalidOperationException("No events allowed"); + case YieldState.EventVC: + State = YieldState.Trade; + if (IsMatch(Encounters2.CelebiVC)) + return SetCurrent(Encounters2.CelebiVC); + goto case YieldState.Trade; + case YieldState.EventGB: + if (TryGetNext(Encounters2GBEra.StaticEventsGB)) + return true; + Index = 0; State = YieldState.Trade; goto case YieldState.Trade; + + case YieldState.Trade: + if (TryGetNext(Encounters2.TradeGift_GSC)) + return true; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + State = Entity.Korean ? YieldState.StaticStart : YieldState.BredCrystal; // next state + if (EncounterGenerator2.TryGetEgg(Chain, GameVersion.GS, out var egg)) + return SetCurrent(egg); + goto case YieldState.StaticStart; + case YieldState.BredCrystal: + State = YieldState.StaticStart; + if (EncounterGenerator2.TryGetEggCrystal(Entity, (EncounterEgg)Current.Encounter, out egg)) + return SetCurrent(egg); + goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (ParseSettings.AllowGen2OddEgg(Entity)) + { State = YieldState.StaticCOdd; goto case YieldState.StaticCOdd; } + if (canOriginateCrystal) + { State = YieldState.StaticC; goto case YieldState.StaticC; } + State = YieldState.SlotGD; goto case YieldState.SlotGD; + case YieldState.StaticCOdd: + if (TryGetNext(Encounters2.StaticOddEggC)) + return true; + Index = 0; + if (canOriginateCrystal) + { State = YieldState.StaticC; goto case YieldState.StaticC; } + State = YieldState.StaticGD; goto case YieldState.StaticGD; + case YieldState.StaticC: + if (TryGetNext(Encounters2.StaticC)) + return true; + if (hasOriginalMet || Entity.OT_Gender == 1) + { Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; } + Index = 0; State = YieldState.StaticGD; goto case YieldState.StaticGD; + case YieldState.StaticGD: + if (TryGetNext(Encounters2.StaticGD)) + return true; + Index = 0; State = YieldState.StaticSI; goto case YieldState.StaticSI; + case YieldState.StaticSI: + if (TryGetNext(Encounters2.StaticSI)) + return true; + Index = 0; State = YieldState.StaticGS; goto case YieldState.StaticGS; + case YieldState.StaticGS: + if (TryGetNext(Encounters2.StaticGS)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticShared: + if (TryGetNext(Encounters2.StaticGSC)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (canOriginateCrystal) + { State = YieldState.SlotC; goto case YieldState.SlotC; } + State = YieldState.SlotGD; goto case YieldState.SlotGD; + case YieldState.SlotC: + if (TryGetNextLocation(Encounters2.SlotsC)) + return true; + if (hasOriginalMet || Entity.OT_Gender == 1) + { Index = 0; goto case YieldState.SlotEnd; } + Index = 0; State = YieldState.SlotGD; goto case YieldState.SlotGD; + case YieldState.SlotGD: + if (TryGetNext(Encounters2.SlotsGD)) + return true; + Index = 0; State = YieldState.SlotSI; goto case YieldState.SlotSI; + case YieldState.SlotSI: + if (TryGetNext(Encounters2.SlotsSI)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + goto case YieldState.Fallback; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private bool TryGetNextLocation(TArea[] areas) + where TArea : IEncounterArea, IAreaLocation + where TSlot : IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (hasOriginalMet && !area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNext(TArea[] areas) + where TArea : IEncounterArea + where TSlot : IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + //if (!area.IsMatchLocation(met)) + // continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool IsMatch(T enc) where T : class, IEncounterable, IEncounterMatch + { + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return true; + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator3.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator3.cs new file mode 100644 index 000000000..73356a183 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator3.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator3(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + private bool mustBeSlot; + private bool hasOriginalLocation; + + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + EventStart, + EventColoR, + EventColoS, + Event, + + Bred, + BredSplit, + + TradeStart, + TradeRS, + TradeE, + TradeFR, + TradeLG, + TradeFRLG, + + StartCaptures, + + SlotStart, + SlotR, + SlotS, + SlotE, + SlotFR, + SlotLG, + SlotEnd, + + StaticStart, + StaticR, + StaticS, + StaticE, + StaticSharedRSE, + + StaticFR, + StaticLG, + StaticSharedFRLG, + + Fallback, + End, + StaticEnd, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + goto case YieldState.EventStart; + + case YieldState.EventStart: + State = YieldState.EventColoR; goto case YieldState.EventColoR; + case YieldState.EventColoR: + if (TryGetNext(Encounters3RSE.ColoGiftsR)) + return true; + Index = 0; State = YieldState.EventColoS; goto case YieldState.EventColoS; + case YieldState.EventColoS: + if (TryGetNext(Encounters3RSE.ColoGiftsS)) + return true; + Index = 0; State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNext(EncountersWC3.Encounter_WC3)) + return true; + Index = 0; goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (Version == GameVersion.E) + { State = YieldState.TradeE; goto case YieldState.TradeE; } + if (Version is GameVersion.FR) + { State = YieldState.TradeFR; goto case YieldState.TradeFR; } + if (Version is GameVersion.LG) + { State = YieldState.TradeLG; goto case YieldState.TradeLG; } + State = YieldState.TradeRS; goto case YieldState.TradeRS; + case YieldState.TradeRS: + if (TryGetNext(Encounters3RSE.TradeGift_RS)) + return true; + Index = 0; State = YieldState.TradeE; goto case YieldState.TradeE; + case YieldState.TradeE: + if (TryGetNext(Encounters3RSE.TradeGift_E)) + return true; + Index = 0; goto case YieldState.StartCaptures; + case YieldState.TradeFR: + if (TryGetNext(Encounters3FRLG.TradeGift_FR)) + return true; + Index = 0; State = YieldState.TradeFRLG; goto case YieldState.TradeFRLG; + case YieldState.TradeLG: + if (TryGetNext(Encounters3FRLG.TradeGift_LG)) + return true; + Index = 0; State = YieldState.TradeFRLG; goto case YieldState.TradeFRLG; + case YieldState.TradeFRLG: + if (TryGetNext(Encounters3FRLG.TradeGift_FRLG)) + return true; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + if (mustBeSlot) + goto case YieldState.SlotStart; + goto case YieldState.StaticStart; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.R) + { State = YieldState.SlotR; goto case YieldState.SlotR; } + if (Version == GameVersion.S) + { State = YieldState.SlotS; goto case YieldState.SlotS; } + if (Version == GameVersion.E) + { State = YieldState.SlotE; goto case YieldState.SlotE; } + if (Version == GameVersion.FR) + { State = YieldState.SlotFR; goto case YieldState.SlotFR; } + if (Version == GameVersion.LG) + { State = YieldState.SlotLG; goto case YieldState.SlotLG; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotR: + if (TryGetNext(Encounters3RSE.SlotsR)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotS: + if (TryGetNext(Encounters3RSE.SlotsS)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotE: + if (TryGetNext(Encounters3RSE.SlotsE)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotFR: + if (TryGetNext(Encounters3FRLG.SlotsFR)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotLG: + if (TryGetNext(Encounters3FRLG.SlotsLG)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + if (mustBeSlot) + goto case YieldState.StaticStart; // be generous with bad balls + goto case YieldState.Bred; // already checked everything else + + case YieldState.StaticStart: + if (Version == GameVersion.R) + { State = YieldState.StaticR; goto case YieldState.StaticR; } + if (Version == GameVersion.S) + { State = YieldState.StaticS; goto case YieldState.StaticS; } + if (Version == GameVersion.E) + { State = YieldState.StaticE; goto case YieldState.StaticE; } + if (Version == GameVersion.FR) + { State = YieldState.StaticFR; goto case YieldState.StaticFR; } + if (Version == GameVersion.LG) + { State = YieldState.StaticLG; goto case YieldState.StaticLG; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticR: + if (TryGetNext(Encounters3RSE.StaticR)) + return true; + Index = 0; State = YieldState.StaticSharedRSE; goto case YieldState.StaticSharedRSE; + case YieldState.StaticS: + if (TryGetNext(Encounters3RSE.StaticS)) + return true; + Index = 0; State = YieldState.StaticSharedRSE; goto case YieldState.StaticSharedRSE; + case YieldState.StaticE: + if (TryGetNext(Encounters3RSE.StaticE)) + return true; + Index = 0; State = YieldState.StaticSharedRSE; goto case YieldState.StaticSharedRSE; + case YieldState.StaticSharedRSE: + if (TryGetNext(Encounters3RSE.StaticRSE)) + return true; + Index = 0; goto case YieldState.StaticEnd; + case YieldState.StaticFR: + if (TryGetNext(Encounters3FRLG.StaticFR)) + return true; + Index = 0; State = YieldState.StaticSharedFRLG; goto case YieldState.StaticSharedFRLG; + case YieldState.StaticLG: + if (TryGetNext(Encounters3FRLG.StaticLG)) + return true; + Index = 0; State = YieldState.StaticSharedFRLG; goto case YieldState.StaticSharedFRLG; + case YieldState.StaticSharedFRLG: + if (TryGetNext(Encounters3FRLG.StaticFRLG)) + return true; + Index = 0; goto case YieldState.StaticEnd; + + case YieldState.StaticEnd: + if (mustBeSlot) + goto case YieldState.Bred; // already checked slots + goto case YieldState.SlotStart; + + case YieldState.Bred: + if (!EncounterGenerator3.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.Fallback; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + State = YieldState.Fallback; + if (!EncounterGenerator3.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg)) + goto case YieldState.Fallback; + return SetCurrent(egg); + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + met = Entity.Met_Location; + mustBeSlot = Entity.Ball is (int)Ball.Safari; // never static + hasOriginalLocation = Entity.Format == 3; + } + + private bool TryGetNext(TArea[] areas) + where TArea : IEncounterArea, IAreaLocation + where TSlot : IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (hasOriginalLocation && !area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) + where T : IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator3GC.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator3GC.cs new file mode 100644 index 000000000..fa9a01c6b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator3GC.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator3GC(PKM Entity, EvoCriteria[] Chain) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + private bool hasOriginalLocation; + + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + + Trade, + + StaticColo, + StaticColoStarters, + StaticColoGift, + StaticXDShadow, + StaticXDGift, + + SlotXD, + StaticEReader, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + break; + State = YieldState.Trade; goto case YieldState.Trade; + + case YieldState.Trade: + if (TryGetNext(Encounters3XD.Trades)) + return true; + Index = 0; State = YieldState.StaticColo; goto case YieldState.StaticColo; + + case YieldState.StaticColo: + if (TryGetNext(Encounters3Colo.Shadow)) + return true; + Index = 0; State = YieldState.StaticColoStarters; goto case YieldState.StaticColoStarters; + case YieldState.StaticColoStarters: + if (TryGetNext(Encounters3Colo.Starters)) + return true; + Index = 0; State = YieldState.StaticColoGift; goto case YieldState.StaticColoGift; + case YieldState.StaticColoGift: + if (TryGetNext(Encounters3Colo.Gifts)) + return true; + Index = 0; State = YieldState.StaticXDShadow; goto case YieldState.StaticXDShadow; + + case YieldState.StaticXDShadow: + if (TryGetNext(Encounters3XD.Shadow)) + return true; + Index = 0; State = YieldState.StaticXDGift; goto case YieldState.StaticXDGift; + case YieldState.StaticXDGift: + if (TryGetNext(Encounters3XD.Gifts)) + return true; + Index = 0; State = YieldState.StaticEReader; goto case YieldState.StaticEReader; + case YieldState.StaticEReader: + State = YieldState.Fallback; + if (Entity.Japanese && TryGetNext(Encounters3Colo.EReader)) + return true; + Index = 0; State = YieldState.SlotXD; goto case YieldState.SlotXD; + + case YieldState.SlotXD: + InitializeWildLocationInfo(); + if (TryGetNext(Encounters3XD.Slots)) + return true; + goto case YieldState.Fallback; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + met = Entity.Met_Location; + hasOriginalLocation = Entity.Format == 3; + } + + private bool TryGetNext(TArea[] areas) + where TArea : IEncounterArea, IAreaLocation + where TSlot : IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (hasOriginalLocation && !area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) + where T : IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator4.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator4.cs new file mode 100644 index 000000000..093336005 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator4.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator4(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + private bool mustBeSlot; + private bool hasOriginalLocation; + + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + EventStart, + Event, + EventLocal, + Bred, + BredSplit, + + TradeStart, + TradeRanch, + TradeDPPt, + TradeHGSS, + + StartCaptures, + + SlotStart, + SlotD, + SlotP, + SlotPt, + SlotHG, + SlotSS, + SlotEnd, + + StaticStart, + StaticD, + StaticP, + StaticSharedDP, + StaticPt, + StaticSharedDPPt, + + StaticHG, + StaticSS, + StaticSharedHGSS, + StaticPokewalker, + + Fallback, + End, + StaticEnd, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + + if (Entity.FatefulEncounter) + goto case YieldState.EventStart; + goto case YieldState.Bred; + + case YieldState.EventStart: + if (PGT.IsRangerManaphy(Entity)) + { + State = YieldState.End; + return SetCurrent(EncounterGenerator4.RangerManaphy); + } + State = YieldState.Event; goto case YieldState.Event; + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G4)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G4)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + if (!Locations.IsEggLocationBred4(Entity.Egg_Location, Version)) + goto case YieldState.TradeStart; + if (!EncounterGenerator4.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.TradeStart; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + if (!EncounterGenerator4.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg)) + goto case YieldState.TradeStart; + State = YieldState.TradeStart; + return SetCurrent(egg); + + case YieldState.TradeStart: + if (Version == GameVersion.D) + { State = YieldState.TradeRanch; goto case YieldState.TradeRanch; } + if (Version is GameVersion.HG or GameVersion.SS) + { State = YieldState.TradeHGSS; goto case YieldState.TradeHGSS; } + State = YieldState.TradeDPPt; goto case YieldState.TradeDPPt; + case YieldState.TradeRanch: + if (TryGetNext(Encounters4DPPt.RanchGifts)) + return true; + Index = 0; State = YieldState.TradeDPPt; goto case YieldState.TradeDPPt; + case YieldState.TradeDPPt: + if (TryGetNext(Encounters4DPPt.TradeGift_DPPtIngame)) + return true; + Index = 0; goto case YieldState.StartCaptures; + case YieldState.TradeHGSS: + if (TryGetNext(Encounters4HGSS.TradeGift_HGSS)) + return true; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + if (mustBeSlot) + goto case YieldState.SlotStart; + goto case YieldState.StaticStart; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.D) + { State = YieldState.SlotD; goto case YieldState.SlotD; } + if (Version == GameVersion.P) + { State = YieldState.SlotP; goto case YieldState.SlotP; } + if (Version == GameVersion.Pt) + { State = YieldState.SlotPt; goto case YieldState.SlotPt; } + if (Version == GameVersion.HG) + { State = YieldState.SlotHG; goto case YieldState.SlotHG; } + if (Version == GameVersion.SS) + { State = YieldState.SlotSS; goto case YieldState.SlotSS; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotHG: + if (TryGetNext(Encounters4HGSS.SlotsHG)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotSS: + if (TryGetNext(Encounters4HGSS.SlotsSS)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotD: + if (TryGetNext(Encounters4DPPt.SlotsD)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotP: + if (TryGetNext(Encounters4DPPt.SlotsP)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotPt: + if (TryGetNext(Encounters4DPPt.SlotsPt)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + if (!mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + Index = 0; goto case YieldState.StaticStart; // be generous with bad balls + + case YieldState.StaticStart: + if (Version == GameVersion.D) + { State = YieldState.StaticD; goto case YieldState.StaticD; } + if (Version == GameVersion.P) + { State = YieldState.StaticP; goto case YieldState.StaticP; } + if (Version == GameVersion.Pt) + { State = YieldState.StaticPt; goto case YieldState.StaticPt; } + if (Version == GameVersion.HG) + { State = YieldState.StaticHG; goto case YieldState.StaticHG; } + if (Version == GameVersion.SS) + { State = YieldState.StaticSS; goto case YieldState.StaticSS; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticD: + if (TryGetNext(Encounters4DPPt.StaticD)) + return true; + Index = 0; State = YieldState.StaticSharedDP; goto case YieldState.StaticSharedDP; + case YieldState.StaticP: + if (TryGetNext(Encounters4DPPt.StaticP)) + return true; + Index = 0; State = YieldState.StaticSharedDP; goto case YieldState.StaticSharedDP; + case YieldState.StaticSharedDP: + if (TryGetNext(Encounters4DPPt.StaticDP)) + return true; + Index = 0; State = YieldState.StaticSharedDPPt; goto case YieldState.StaticSharedDPPt; + case YieldState.StaticPt: + if (TryGetNext(Encounters4DPPt.StaticPt)) + return true; + Index = 0; State = YieldState.StaticSharedDPPt; goto case YieldState.StaticSharedDPPt; + case YieldState.StaticSharedDPPt: + if (TryGetNext(Encounters4DPPt.StaticDPPt)) + return true; + Index = 0; goto case YieldState.StaticEnd; + + case YieldState.StaticHG: + if (TryGetNext(Encounters4HGSS.StaticHG)) + return true; + Index = 0; State = YieldState.StaticSharedHGSS; goto case YieldState.StaticSharedHGSS; + case YieldState.StaticSS: + if (TryGetNext(Encounters4HGSS.StaticSS)) + return true; + Index = 0; State = YieldState.StaticSharedHGSS; goto case YieldState.StaticSharedHGSS; + case YieldState.StaticSharedHGSS: + if (TryGetNext(Encounters4HGSS.Encounter_HGSS)) + return true; + Index = 0; State = YieldState.StaticPokewalker; goto case YieldState.StaticPokewalker; + case YieldState.StaticPokewalker: + if (TryGetNext(Encounters4HGSS.Encounter_PokeWalker)) + return true; + Index = 0; goto case YieldState.StaticEnd; + + case YieldState.StaticEnd: + if (mustBeSlot) + break; + goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + met = Entity.Met_Location; + mustBeSlot = Entity is { Egg_Location: 0, Ball: (int)Ball.Sport or (int)Ball.Safari }; // never static + hasOriginalLocation = Entity.Format == 4; + } + + private bool TryGetNext(TArea[] areas) + where TArea : IEncounterArea, IAreaLocation + where TSlot : IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (hasOriginalLocation && !area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) + where T : IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator5.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator5.cs new file mode 100644 index 000000000..d6f0b621d --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator5.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator5(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + EventLocal, + Bred, + BredSplit, + + TradeStart, + TradeW, + TradeB, + TradeBW, + TradeW2, + TradeB2, + TradeB2W2, + + StartCaptures, + + SlotStart, + SlotW, + SlotB, + SlotW2, + SlotB2, + SlotEnd, + + StaticStart, + StaticW, + StaticB, + StaticSharedBW, + StaticEntreeBW, + StaticW2, + StaticB2, + StaticN, + StaticSharedB2W2, + StaticEntreeB2W2, + StaticRadar, + StaticEntreeShared, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + + if (Entity.Met_Location == Locations.LinkTrade5NPC) + goto case YieldState.TradeStart; + if (!Entity.FatefulEncounter) + goto case YieldState.Bred; + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G5)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G5)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + if (!Locations.IsEggLocationBred5(Entity.Egg_Location)) + goto case YieldState.StartCaptures; + if (!EncounterGenerator5.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.StartCaptures; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + State = Entity.Egg_Location == Locations.Daycare5 ? YieldState.End : YieldState.StartCaptures; + if (!EncounterGenerator5.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg)) + return MoveNext(); + return SetCurrent(egg); + + case YieldState.TradeStart: + if (Version == GameVersion.W) + { State = YieldState.TradeW; goto case YieldState.TradeW; } + if (Version == GameVersion.B) + { State = YieldState.TradeB; goto case YieldState.TradeB; } + if (Version == GameVersion.W2) + { State = YieldState.TradeW2; goto case YieldState.TradeW2; } + if (Version == GameVersion.B2) + { State = YieldState.TradeB2; goto case YieldState.TradeB2; } + + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.TradeW: + if (TryGetNext(Encounters5BW.TradeGift_W)) + return true; + if (Yielded) + break; + Index = 0; State = YieldState.TradeBW; goto case YieldState.TradeBW; + case YieldState.TradeB: + if (TryGetNext(Encounters5BW.TradeGift_B)) + return true; + if (Yielded) + break; + Index = 0; State = YieldState.TradeBW; goto case YieldState.TradeBW; + case YieldState.TradeW2: + if (TryGetNext(Encounters5B2W2.TradeGift_W2)) + return true; + if (Yielded) + break; + Index = 0; State = YieldState.TradeB2W2; goto case YieldState.TradeB2W2; + case YieldState.TradeB2: + if (TryGetNext(Encounters5B2W2.TradeGift_B2)) + return true; + if (Yielded) + break; + Index = 0; State = YieldState.TradeB2W2; goto case YieldState.TradeB2W2; + + case YieldState.TradeBW: + if (TryGetNext(Encounters5BW.TradeGift_BW)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + case YieldState.TradeB2W2: + if (TryGetNext(Encounters5B2W2.TradeGift_B2W2)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + goto case YieldState.StaticStart; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.W) + { State = YieldState.SlotW; goto case YieldState.SlotW; } + if (Version == GameVersion.B) + { State = YieldState.SlotB; goto case YieldState.SlotB; } + if (Version == GameVersion.W2) + { State = YieldState.SlotW2; goto case YieldState.SlotW2; } + if (Version == GameVersion.B2) + { State = YieldState.SlotB2; goto case YieldState.SlotB2; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotW2: + if (TryGetNext(Encounters5B2W2.SlotsW2)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotB2: + if (TryGetNext(Encounters5B2W2.SlotsB2)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotW: + if (TryGetNext(Encounters5BW.SlotsW)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotB: + if (TryGetNext(Encounters5BW.SlotsB)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticStart: + if (Version == GameVersion.W) + { State = YieldState.StaticW; goto case YieldState.StaticW; } + if (Version == GameVersion.B) + { State = YieldState.StaticB; goto case YieldState.StaticB; } + if (Version == GameVersion.W2) + { State = YieldState.StaticW2; goto case YieldState.StaticW2; } + if (Version == GameVersion.B2) + { State = YieldState.StaticB2; goto case YieldState.StaticB2; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticW: + if (TryGetNext(Encounters5BW.StaticW)) + return true; + Index = 0; State = YieldState.StaticSharedBW; goto case YieldState.StaticSharedBW; + case YieldState.StaticB: + if (TryGetNext(Encounters5BW.StaticB)) + return true; + Index = 0; State = YieldState.StaticSharedBW; goto case YieldState.StaticSharedBW; + case YieldState.StaticSharedBW: + if (TryGetNext(Encounters5BW.Encounter_BW)) + return true; + Index = 0; State = YieldState.StaticEntreeBW; goto case YieldState.StaticEntreeBW; + case YieldState.StaticEntreeBW: + if (TryGetNext(Encounters5BW.DreamWorld_BW)) + return true; + Index = 0; State = YieldState.StaticEntreeShared; goto case YieldState.StaticEntreeShared; + + case YieldState.StaticW2: + if (TryGetNext(Encounters5B2W2.StaticW2)) + return true; + Index = 0; State = YieldState.StaticSharedB2W2; goto case YieldState.StaticSharedB2W2; + case YieldState.StaticB2: + if (TryGetNext(Encounters5B2W2.StaticB2)) + return true; + Index = 0; State = YieldState.StaticSharedB2W2; goto case YieldState.StaticSharedB2W2; + case YieldState.StaticSharedB2W2: + if (TryGetNext(Encounters5B2W2.Encounter_B2W2_Regular)) + return true; + Index = 0; State = YieldState.StaticN; goto case YieldState.StaticN; + case YieldState.StaticN: + if (TryGetNext(Encounters5B2W2.Encounter_B2W2_N)) + return true; + Index = 0; State = YieldState.StaticEntreeB2W2; goto case YieldState.StaticEntreeB2W2; + case YieldState.StaticEntreeB2W2: + if (TryGetNext(Encounters5B2W2.DreamWorld_B2W2)) + return true; + Index = 0; State = YieldState.StaticRadar; goto case YieldState.StaticRadar; + case YieldState.StaticRadar: + if (TryGetNext(Encounters5DR.Encounter_DreamRadar)) + return true; + Index = 0; State = YieldState.StaticEntreeShared; goto case YieldState.StaticEntreeShared; + + case YieldState.StaticEntreeShared: + if (TryGetNext(Encounters5DR.DreamWorld_Common)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + met = Entity.Met_Location; + } + + private bool TryGetNext(TArea[] areas) + where TArea : IEncounterArea, IAreaLocation + where TSlot : IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (!area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) + where T : IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator6.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator6.cs new file mode 100644 index 000000000..c4eae6633 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator6.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator6(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + EventLocal, + Bred, + BredTrade, + BredSplit, + BredSplitTrade, + + TradeStart, + TradeXY, + TradeAO, + + StartCaptures, + + SlotStart, + SlotX, + SlotY, + SlotAS, + SlotOR, + SlotEnd, + + StaticStart, + StaticAS, + StaticOR, + StaticSharedAO, + StaticX, + StaticY, + StaticSharedXY, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + + if (Entity.Met_Location == Locations.LinkTrade6NPC) + goto case YieldState.TradeStart; + + if (Entity.FatefulEncounter || Entity.Met_Location == Locations.LinkGift6) + { State = YieldState.Event; goto case YieldState.Event; } + goto case YieldState.Bred; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G6)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G6)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + if (!Locations.IsEggLocationBred6(Entity.Egg_Location)) + goto case YieldState.StartCaptures; + if (!EncounterGenerator6.TryGetEgg(Chain, Version, out var egg)) + break; + State = YieldState.BredTrade; + return SetCurrent(egg); + case YieldState.BredTrade: + State = YieldState.BredSplit; + if (Entity.Egg_Location != Locations.LinkTrade6) + goto case YieldState.BredSplit; + egg = EncounterGenerator6.MutateEggTrade((EncounterEgg)Current.Encounter); + return SetCurrent(egg); + case YieldState.BredSplit: + if (Chain[^1].Species is (int)Species.Togepi or (int)Species.Wynaut) + goto case YieldState.StartCaptures; + State = YieldState.BredSplitTrade; + if (!EncounterGenerator6.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg)) + break; + return SetCurrent(egg); + case YieldState.BredSplitTrade: + State = YieldState.StartCaptures; + if (Entity.Egg_Location != Locations.LinkTrade6) + goto case YieldState.StartCaptures; + egg = EncounterGenerator6.MutateEggTrade((EncounterEgg)Current.Encounter); + return SetCurrent(egg); + + case YieldState.TradeStart: + if (Version is GameVersion.X or GameVersion.Y) + { State = YieldState.TradeXY; goto case YieldState.TradeXY; } + if (Version is GameVersion.AS or GameVersion.OR) + { State = YieldState.TradeAO; goto case YieldState.TradeAO; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.TradeXY: + if (TryGetNext(Encounters6XY.TradeGift_XY)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + case YieldState.TradeAO: + if (TryGetNext(Encounters6AO.TradeGift_AO)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + goto case YieldState.StaticStart; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.AS) + { State = YieldState.SlotAS; goto case YieldState.SlotAS; } + if (Version == GameVersion.OR) + { State = YieldState.SlotOR; goto case YieldState.SlotOR; } + if (Version == GameVersion.X) + { State = YieldState.SlotX; goto case YieldState.SlotX; } + if (Version == GameVersion.Y) + { State = YieldState.SlotY; goto case YieldState.SlotY; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotAS: + if (TryGetNext(Encounters6AO.SlotsA)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotOR: + if (TryGetNext(Encounters6AO.SlotsO)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotX: + if (TryGetNext(Encounters6XY.SlotsX)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotY: + if (TryGetNext(Encounters6XY.SlotsY)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticStart: + if (Version == GameVersion.AS) + { State = YieldState.StaticAS; goto case YieldState.StaticAS; } + if (Version == GameVersion.OR) + { State = YieldState.StaticOR; goto case YieldState.StaticOR; } + if (Version == GameVersion.X) + { State = YieldState.StaticX; goto case YieldState.StaticX; } + if (Version == GameVersion.Y) + { State = YieldState.StaticY; goto case YieldState.StaticY; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticAS: + if (TryGetNext(Encounters6AO.StaticA)) + return true; + Index = 0; State = YieldState.StaticSharedAO; goto case YieldState.StaticSharedAO; + case YieldState.StaticOR: + if (TryGetNext(Encounters6AO.StaticO)) + return true; + Index = 0; State = YieldState.StaticSharedAO; goto case YieldState.StaticSharedAO; + case YieldState.StaticSharedAO: + if (TryGetNext(Encounters6AO.Encounter_AO)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.StaticX: + if (TryGetNext(Encounters6XY.StaticX)) + return true; + Index = 0; State = YieldState.StaticSharedXY; goto case YieldState.StaticSharedXY; + case YieldState.StaticY: + if (TryGetNext(Encounters6XY.StaticY)) + return true; + Index = 0; State = YieldState.StaticSharedXY; goto case YieldState.StaticSharedXY; + case YieldState.StaticSharedXY: + if (TryGetNext(Encounters6XY.Encounter_XY)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + met = Entity.Met_Location; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (!area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7.cs new file mode 100644 index 000000000..039660b05 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator7(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + EventLocal, + Bred, + BredTrade, + BredSplit, + BredSplitTrade, + + TradeStart, + TradeSM, + TradeUSUM, + + StartCaptures, + + SlotStart, + SlotSN, + SlotMN, + SlotUS, + SlotUM, + SlotEnd, + + StaticStart, + StaticUS, + StaticUM, + StaticSharedUSUM, + StaticSN, + StaticMN, + StaticSharedSM, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + + if (Entity.Met_Location == Locations.LinkTrade6NPC) + goto case YieldState.TradeStart; + if (!Entity.FatefulEncounter) + goto case YieldState.Bred; + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G7)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G7)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + if (!Locations.IsEggLocationBred6(Entity.Egg_Location)) + goto case YieldState.StartCaptures; + if (!EncounterGenerator7.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.StartCaptures; + State = YieldState.BredTrade; + return SetCurrent(egg); + case YieldState.BredTrade: + State = YieldState.BredSplit; + if (Entity.Egg_Location != Locations.LinkTrade6) + goto case YieldState.BredSplit; + egg = EncounterGenerator7.MutateEggTrade((EncounterEgg)Current.Encounter); + return SetCurrent(egg); + case YieldState.BredSplit: + if (Chain[^1].Species == (int)Species.Eevee) + { State = YieldState.StaticSharedUSUM; goto case YieldState.StaticSharedUSUM; } + State = YieldState.BredSplitTrade; + if (!EncounterGenerator7.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg)) + break; + return SetCurrent(egg); + case YieldState.BredSplitTrade: + State = YieldState.End; + if (Entity.Egg_Location != Locations.LinkTrade6) + break; + egg = EncounterGenerator7.MutateEggTrade((EncounterEgg)Current.Encounter); + return SetCurrent(egg); + + case YieldState.TradeStart: + if (Version is GameVersion.SN or GameVersion.MN) + { State = YieldState.TradeSM; goto case YieldState.TradeSM; } + if (Version is GameVersion.US or GameVersion.UM) + { State = YieldState.TradeUSUM; goto case YieldState.TradeUSUM; } + break; + case YieldState.TradeSM: + if (TryGetNext(Encounters7SM.TradeGift_SM)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + case YieldState.TradeUSUM: + if (TryGetNext(Encounters7USUM.TradeGift_USUM)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + goto case YieldState.StaticStart; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.US) + { State = YieldState.SlotUS; goto case YieldState.SlotUS; } + if (Version == GameVersion.UM) + { State = YieldState.SlotUM; goto case YieldState.SlotUM; } + if (Version == GameVersion.SN) + { State = YieldState.SlotSN; goto case YieldState.SlotSN; } + if (Version == GameVersion.MN) + { State = YieldState.SlotMN; goto case YieldState.SlotMN; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotUS: + if (TryGetNext(Encounters7USUM.SlotsUS)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotUM: + if (TryGetNext(Encounters7USUM.SlotsUM)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotSN: + if (TryGetNext(Encounters7SM.SlotsSN)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotMN: + if (TryGetNext(Encounters7SM.SlotsMN)) + return true; + goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticStart: + if (Version == GameVersion.US) + { State = YieldState.StaticUS; goto case YieldState.StaticUS; } + if (Version == GameVersion.UM) + { State = YieldState.StaticUM; goto case YieldState.StaticUM; } + if (Version == GameVersion.SN) + { State = YieldState.StaticSN; goto case YieldState.StaticSN; } + if (Version == GameVersion.MN) + { State = YieldState.StaticMN; goto case YieldState.StaticMN; } + throw new ArgumentOutOfRangeException(nameof(Version)); + + case YieldState.StaticUS: + if (TryGetNext(Encounters7USUM.StaticUS)) + return true; + Index = 0; State = YieldState.StaticSharedUSUM; goto case YieldState.StaticSharedUSUM; + case YieldState.StaticUM: + if (TryGetNext(Encounters7USUM.StaticUM)) + return true; + Index = 0; State = YieldState.StaticSharedUSUM; goto case YieldState.StaticSharedUSUM; + case YieldState.StaticSharedUSUM: + if (TryGetNext(Encounters7USUM.StaticUSUM)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.StaticSN: + if (TryGetNext(Encounters7SM.StaticSN)) + return true; + Index = 0; State = YieldState.StaticSharedSM; goto case YieldState.StaticSharedSM; + case YieldState.StaticMN: + if (TryGetNext(Encounters7SM.StaticMN)) + return true; + Index = 0; State = YieldState.StaticSharedSM; goto case YieldState.StaticSharedSM; + case YieldState.StaticSharedSM: + if (TryGetNext(Encounters7SM.StaticSM)) + return true; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + met = Entity.Met_Location; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (!area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7GG.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7GG.cs new file mode 100644 index 000000000..d420b5226 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7GG.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator7GG(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + EventLocal, + + TradeStart, + TradeGP, + TradeGE, + TradeShared, + + StaticStart, + StaticGP, + StaticGE, + StaticShared, + + SlotStart, + SlotGP, + SlotGE, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (Entity.IsEgg) + break; + InitializeWildLocationInfo(); + if (met == Locations.LinkTrade6NPC) + goto case YieldState.TradeStart; + if (!Entity.FatefulEncounter) + goto case YieldState.StaticStart; + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.TradeStart: + if (Version == GameVersion.GP) + { State = YieldState.TradeGP; goto case YieldState.TradeGP; } + if (Version == GameVersion.GE) + { State = YieldState.TradeGE; goto case YieldState.TradeGE; } + break; + case YieldState.TradeGP: + if (TryGetNext(Encounters7GG.TradeGift_GP)) + return true; + Index = 0; State = YieldState.TradeShared; goto case YieldState.TradeShared; + case YieldState.TradeGE: + if (TryGetNext(Encounters7GG.TradeGift_GE)) + return true; + Index = 0; State = YieldState.TradeShared; goto case YieldState.TradeShared; + case YieldState.TradeShared: + if (TryGetNext(Encounters7GG.TradeGift_GG)) + return true; + break; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G7GG)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G7GG)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StaticStart; + + case YieldState.StaticStart: + if (Version == GameVersion.GP) + { State = YieldState.StaticGP; goto case YieldState.StaticGP; } + if (Version == GameVersion.GE) + { State = YieldState.StaticGE; goto case YieldState.StaticGE; } + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticGP: + if (TryGetNext(Encounters7GG.StaticGP)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticGE: + if (TryGetNext(Encounters7GG.StaticGE)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticShared: + if (TryGetNext(Encounters7GG.Encounter_GG)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.SlotStart; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + break; + if (Version is GameVersion.GP) + { State = YieldState.SlotGP; goto case YieldState.SlotGP; } + if (Version is GameVersion.GE) + { State = YieldState.SlotGE; goto case YieldState.SlotGE; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotGP: + if (TryGetNext(Encounters7GG.SlotsGP)) + return true; + goto case YieldState.Fallback; // already checked everything else + case YieldState.SlotGE: + if (TryGetNext(Encounters7GG.SlotsGE)) + return true; + if (Yielded) + break; + goto case YieldState.Fallback; // already checked everything else + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + met = Entity.Met_Location; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (!area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7GO.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7GO.cs new file mode 100644 index 000000000..f788d6e6a --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator7GO.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator7GO(PKM Entity, EvoCriteria[] Chain) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int EvoIndex; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + public MatchedEncounter Current { get; private set; } + private YieldState State; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Seek, + Slot, + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + break; + State = YieldState.Seek; goto case YieldState.Seek; + + case YieldState.Seek: + if (!SeekNextArea(EncountersGO.SlotsGO_GG)) + goto case YieldState.Fallback; + State = YieldState.Slot; goto case YieldState.Slot; + case YieldState.Slot: + var group = EncountersGO.SlotsGO_GG[Index]; + if (TryGetNext(group.Slots)) + return true; + State = YieldState.Seek; goto case YieldState.Seek; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private bool SeekNextArea(TArea[] areas) + where TArea : ISpeciesForm + { + for (; Index < areas.Length; Index++, EvoIndex = 0) + { + var area = areas[Index]; + do + { + if (IsCompatible(area, Chain[EvoIndex])) + return true; + } + while (++EvoIndex < Chain.Length); + } + return false; + } + + private readonly bool IsCompatible(TArea area, EvoCriteria evo) where TArea : ISpeciesForm + { + if (area.Species != evo.Species) + return false; + if (area.Form != evo.Form && !FormInfo.IsFormChangeable(area.Species, area.Form, evo.Form, EntityContext.Gen8, Entity.Context)) + return false; + return true; + } + + private bool TryGetNext(TSlot[] slots) + where TSlot : IEncounterable, IEncounterMatch + { + var evo = Chain[EvoIndex]; + for (; SubIndex < slots.Length;) + { + var enc = slots[SubIndex++]; + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + continue; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating >= Rating) + continue; + Deferred = enc; + Rating = rating; + } + EvoIndex = 0; SubIndex = 0; Index++; + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8.cs new file mode 100644 index 000000000..61ef76a41 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator8(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + private bool mustBeSlot; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + EventLocal, + Bred, + BredSplit, + + TradeStart, + TradeSW, + TradeSH, + TradeShared, + + StartCaptures, + + SlotStart, + SlotSW, + SlotSH, + SlotSWHidden, + SlotSHHidden, + SlotEnd, + + StaticStart, + NestSW, NestSH, DistSW, DistSH, DynamaxAdv, Crystal, + StaticVersion, + StaticVersionSW, + StaticVersionSH, + StaticShared, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + + if (Entity.Met_Location == Locations.LinkTrade6NPC) + goto case YieldState.TradeStart; + if (!Entity.FatefulEncounter) + goto case YieldState.Bred; + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G8)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G8)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + if (!Locations.IsEggLocationBred6(Entity.Egg_Location)) + goto case YieldState.StartCaptures; + if (!EncounterGenerator8.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.StartCaptures; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + State = YieldState.End; + if (EncounterGenerator8.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg)) + return SetCurrent(egg); + break; + + case YieldState.TradeStart: + if (Version == GameVersion.SW) + { State = YieldState.TradeSW; goto case YieldState.TradeSW; } + if (Version == GameVersion.SH) + { State = YieldState.TradeSH; goto case YieldState.TradeSH; } + break; + case YieldState.TradeSW: + if (TryGetNext(Encounters8.TradeSW)) + return true; + Index = 0; State = YieldState.TradeShared; goto case YieldState.TradeShared; + case YieldState.TradeSH: + if (TryGetNext(Encounters8.TradeSH)) + return true; + Index = 0; State = YieldState.TradeShared; goto case YieldState.TradeShared; + case YieldState.TradeShared: + if (TryGetNext(Encounters8.TradeSWSH)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + if (mustBeSlot) + goto case YieldState.SlotStart; + goto case YieldState.StaticStart; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + if (Version == GameVersion.SW) + { State = YieldState.SlotSW; goto case YieldState.SlotSW; } + if (Version == GameVersion.SH) + { State = YieldState.SlotSH; goto case YieldState.SlotSH; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotSW: + if (TryGetNext(Encounters8.SlotsSW_Symbol)) + return true; + Index = 0; State = YieldState.SlotSWHidden; goto case YieldState.SlotSWHidden; + case YieldState.SlotSH: + if (TryGetNext(Encounters8.SlotsSH_Symbol)) + return true; + Index = 0; State = YieldState.SlotSHHidden; goto case YieldState.SlotSHHidden; + case YieldState.SlotSWHidden: + if (TryGetNext(Encounters8.SlotsSW_Hidden)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotSHHidden: + if (TryGetNext(Encounters8.SlotsSH_Hidden)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + if (!mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + goto case YieldState.StaticStart; + + case YieldState.StaticStart: + goto case YieldState.NestSW; + + case YieldState.NestSW: + if (TryGetNext(Encounters8Nest.Nest_SW)) + return true; + Index = 0; State = YieldState.NestSH; goto case YieldState.NestSH; + case YieldState.NestSH: + if (TryGetNext(Encounters8Nest.Nest_SH)) + return true; + Index = 0; State = YieldState.DistSW; goto case YieldState.DistSW; + case YieldState.DistSW: + if (TryGetNext(Encounters8Nest.Dist_SW)) + return true; + Index = 0; State = YieldState.DistSH; goto case YieldState.DistSH; + case YieldState.DistSH: + if (TryGetNext(Encounters8Nest.Dist_SH)) + return true; + Index = 0; State = YieldState.DynamaxAdv; goto case YieldState.DynamaxAdv; + case YieldState.DynamaxAdv: + if (TryGetNext(Encounters8Nest.DynAdv_SWSH)) + return true; + Index = 0; State = YieldState.Crystal; goto case YieldState.Crystal; + case YieldState.Crystal: + if (TryGetNext(Encounters8Nest.Crystal_SWSH)) + return true; + Index = 0; State = YieldState.StaticVersion; goto case YieldState.StaticVersion; + + case YieldState.StaticVersion: + if (Version == GameVersion.SW) + { State = YieldState.StaticVersionSW; goto case YieldState.StaticVersionSW; } + if (Version == GameVersion.SH) + { State = YieldState.StaticVersionSH; goto case YieldState.StaticVersionSH; } + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticVersionSW: + if (TryGetNext(Encounters8.StaticSW)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticVersionSH: + if (TryGetNext(Encounters8.StaticSH)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticShared: + if (TryGetNext(Encounters8.StaticSWSH)) + return true; + if (mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + Index = 0; goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + mustBeSlot = Entity is IRibbonIndex r && r.HasEncounterMark(); + met = Entity.Met_Location; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (!area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) + where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8GO.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8GO.cs new file mode 100644 index 000000000..fa34b9d94 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8GO.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator8GO(PKM Entity, EvoCriteria[] Chain) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int EvoIndex; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + public MatchedEncounter Current { get; private set; } + private YieldState State; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Seek, + Slot, + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + break; + State = YieldState.Seek; goto case YieldState.Seek; + + case YieldState.Seek: + if (!SeekNextArea(EncountersGO.SlotsGO)) + goto case YieldState.Fallback; + State = YieldState.Slot; goto case YieldState.Slot; + case YieldState.Slot: + var group = EncountersGO.SlotsGO[Index]; + if (TryGetNext(group.Slots)) + return true; + State = YieldState.Seek; goto case YieldState.Seek; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private bool SeekNextArea(TArea[] areas) + where TArea : ISpeciesForm + { + for (; Index < areas.Length; Index++, EvoIndex = 0) + { + var area = areas[Index]; + do + { + if (IsCompatible(area, Chain[EvoIndex])) + return true; + } + while (++EvoIndex < Chain.Length); + } + return false; + } + + private readonly bool IsCompatible(TArea area, EvoCriteria evo) where TArea : ISpeciesForm + { + if (area.Species != evo.Species) + return false; + if (area.Form != evo.Form && !FormInfo.IsFormChangeable(area.Species, area.Form, evo.Form, EntityContext.Gen8, Entity.Context)) + return false; + return true; + } + + private bool TryGetNext(TSlot[] slots) + where TSlot : IEncounterable, IEncounterMatch + { + var evo = Chain[EvoIndex]; + for (; SubIndex < slots.Length;) + { + var enc = slots[SubIndex++]; + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + continue; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + } + EvoIndex = 0; SubIndex = 0; Index++; + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8a.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8a.cs new file mode 100644 index 000000000..7259edf76 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8a.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator8a(PKM Entity, EvoCriteria[] Chain) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + private bool hasOriginalMet; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + EventLocal, + Static, + Slot, + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + if (Entity is PK8 { SWSH: false }) + break; + if (Entity.IsEgg) + break; + + if (!Entity.FatefulEncounter) + { State = YieldState.Static; goto case YieldState.Static; } + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G8A)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G8A)) + return true; + if (Yielded) + break; + Index = 0; State = YieldState.Static; goto case YieldState.Static; + + case YieldState.Static: + if (TryGetNextSub(Encounters8a.StaticLA)) + return true; + + // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.Fallback; + + InitializeWildLocationInfo(); + Index = 0; State = YieldState.Slot; goto case YieldState.Slot; + + case YieldState.Slot: + if (TryGetNext(Encounters8a.SlotsLA)) + return true; + goto case YieldState.Fallback; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + met = Entity.Met_Location; + var remap = LocationsHOME.GetRemapState(EntityContext.Gen8a, Entity.Context); + hasOriginalMet = true; + if (remap.HasFlag(LocationRemapState.Remapped)) + hasOriginalMet = met != LocationsHOME.SWLA; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (hasOriginalMet && !area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8b.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8b.cs new file mode 100644 index 000000000..895b0c2a2 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8b.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator8b(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + private bool hasOriginalLocation; + private bool mustBeSlot; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + EventLocal, + Bred, + BredSplit, + TradeStart, + Trade, + + StartCaptures, + + SlotStart, + SlotBD, + SlotSP, + SlotEnd, + + StaticVersion, + StaticVersionBD, + StaticVersionSP, + StaticShared, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + Debug.Assert(Entity is not PK8); + if (Chain.Length == 0) + break; + + if (!Entity.FatefulEncounter) + goto case YieldState.Bred; + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G8B)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G8B)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + if (!Locations.IsEggLocationBred8b(Entity.Egg_Location)) + goto case YieldState.TradeStart; + if (!EncounterGenerator8b.TryGetEgg(Chain, Version, out var egg)) + goto case YieldState.TradeStart; + State = YieldState.BredSplit; + return SetCurrent(egg); + case YieldState.BredSplit: + State = Entity.Egg_Location == Locations.Daycare8b ? YieldState.End : YieldState.StartCaptures; + if (EncounterGenerator8b.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg)) + return SetCurrent(egg); + break; + + case YieldState.TradeStart: + if (Entity.Met_Location != Locations.LinkTrade6NPC) + goto case YieldState.StartCaptures; + State = YieldState.Trade; goto case YieldState.Trade; + case YieldState.Trade: + if (TryGetNext(Encounters8b.TradeGift_BDSP)) + { State = YieldState.End; return true; } + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + if (mustBeSlot) + goto case YieldState.SlotStart; + goto case YieldState.StaticVersion; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + if (Version is GameVersion.BD) + { State = YieldState.SlotBD; goto case YieldState.SlotBD; } + if (Version is GameVersion.SP) + { State = YieldState.SlotSP; goto case YieldState.SlotSP; } + throw new ArgumentOutOfRangeException(nameof(Version)); + case YieldState.SlotBD: + if (TryGetNext(Encounters8b.SlotsBD)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotSP: + if (TryGetNext(Encounters8b.SlotsSP)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + if (!mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + goto case YieldState.StaticVersion; + + case YieldState.StaticVersion: + if (Version == GameVersion.BD) + { State = YieldState.StaticVersionBD; goto case YieldState.StaticVersionBD; } + if (Version == GameVersion.SP) + { State = YieldState.StaticVersionSP; goto case YieldState.StaticVersionSP; } + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticVersionBD: + if (TryGetNext(Encounters8b.StaticBD)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticVersionSP: + if (TryGetNext(Encounters8b.StaticSP)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + + case YieldState.StaticShared: + if (TryGetNext(Encounters8b.Encounter_BDSP)) + return true; + if (mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + Index = 0; goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + mustBeSlot = Entity.Ball == (byte)Ball.Safari; + met = Entity.Met_Location; + var location = met; + var remap = LocationsHOME.GetRemapState(EntityContext.Gen8b, Entity.Context); + hasOriginalLocation = true; + if (remap.HasFlag(LocationRemapState.Remapped)) + hasOriginalLocation = location != LocationsHOME.GetMetSWSH((ushort)location, (int)Version); + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (hasOriginalLocation && !area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator9.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator9.cs new file mode 100644 index 000000000..d362747db --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator9.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public record struct EncounterEnumerator9(PKM Entity, EvoCriteria[] Chain, GameVersion Version) : IEnumerator> +{ + private IEncounterable? Deferred; + private int Index; + private int SubIndex; + private EncounterMatchRating Rating = EncounterMatchRating.MaxNotMatch; + private bool Yielded; + public MatchedEncounter Current { get; private set; } + private YieldState State; + private int met; + private bool mustBeSlot; + readonly object IEnumerator.Current => Current; + + public readonly void Reset() => throw new NotSupportedException(); + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + + private enum YieldState : byte + { + Start, + Event, + EventLocal, + Bred, + TradeStart, + Trade, + + StartCaptures, + + SlotStart, + Slot, + SlotEnd, + + StaticVersion, + StaticVersionSL, + StaticVersionVL, + StaticShared, + StaticFixed, + StaticTera, + StaticDist, + StaticMight, + + Fallback, + End, + } + + public bool MoveNext() + { + switch (State) + { + case YieldState.Start: + if (Chain.Length == 0) + break; + + if (!Entity.FatefulEncounter) + goto case YieldState.Bred; + State = YieldState.Event; goto case YieldState.Event; + + case YieldState.Event: + if (TryGetNext(EncounterEvent.MGDB_G9)) + return true; + Index = 0; State = YieldState.EventLocal; goto case YieldState.EventLocal; + case YieldState.EventLocal: + if (TryGetNext(EncounterEvent.EGDB_G9)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.Bred; + + case YieldState.Bred: + State = YieldState.TradeStart; + if (EncounterGenerator9.TryGetEgg(Entity, Chain, Version, out var egg)) + return SetCurrent(egg); + goto case YieldState.TradeStart; + + case YieldState.TradeStart: + if (Entity.Met_Location != Locations.LinkTrade6NPC) + goto case YieldState.StartCaptures; + State = YieldState.Trade; goto case YieldState.Trade; + case YieldState.Trade: + if (TryGetNext(Encounters9.TradeGift_SV)) + return true; + if (Yielded) + break; + Index = 0; goto case YieldState.StartCaptures; + + case YieldState.StartCaptures: + InitializeWildLocationInfo(); + if (mustBeSlot) + goto case YieldState.SlotStart; + goto case YieldState.StaticVersion; + + case YieldState.SlotStart: + if (!EncounterStateUtil.CanBeWildEncounter(Entity)) + goto case YieldState.SlotEnd; + State = YieldState.Slot; goto case YieldState.Slot; + case YieldState.Slot: + if (TryGetNext(Encounters9.Slots)) + return true; + Index = 0; goto case YieldState.SlotEnd; + case YieldState.SlotEnd: + if (!mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + goto case YieldState.StaticVersion; + + case YieldState.StaticVersion: + if (Version == GameVersion.SL) + { State = YieldState.StaticVersionSL; goto case YieldState.StaticVersionSL; } + if (Version == GameVersion.VL) + { State = YieldState.StaticVersionVL; goto case YieldState.StaticVersionVL; } + goto case YieldState.Fallback; // already checked everything else + + case YieldState.StaticVersionSL: + if (TryGetNext(Encounters9.StaticSL)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + case YieldState.StaticVersionVL: + if (TryGetNext(Encounters9.StaticVL)) + return true; + Index = 0; State = YieldState.StaticShared; goto case YieldState.StaticShared; + + case YieldState.StaticShared: + if (TryGetNext(Encounters9.Encounter_SV)) + return true; + Index = 0; State = YieldState.StaticFixed; goto case YieldState.StaticFixed; + + case YieldState.StaticFixed: + if (TryGetNext(Encounters9.Fixed)) + return true; + Index = 0; State = YieldState.StaticTera; goto case YieldState.StaticTera; + case YieldState.StaticTera: + if (TryGetNext(Encounters9.Tera)) + return true; + Index = 0; State = YieldState.StaticDist; goto case YieldState.StaticDist; + case YieldState.StaticDist: + if (TryGetNext(Encounters9.Dist)) + return true; + Index = 0; State = YieldState.StaticMight; goto case YieldState.StaticMight; + case YieldState.StaticMight: + if (TryGetNext(Encounters9.Might)) + return true; + if (mustBeSlot) + goto case YieldState.Fallback; // already checked everything else + Index = 0; goto case YieldState.SlotStart; + + case YieldState.Fallback: + State = YieldState.End; + if (Deferred != null) + return SetCurrent(Deferred, Rating); + break; + } + return false; + } + + private void InitializeWildLocationInfo() + { + mustBeSlot = Entity is IRibbonIndex r && r.HasEncounterMark(); + met = Entity.Met_Location; + } + + private bool TryGetNext(TArea[] areas) + where TArea : class, IEncounterArea, IAreaLocation + where TSlot : class, IEncounterable, IEncounterMatch + { + for (; Index < areas.Length; Index++, SubIndex = 0) + { + var area = areas[Index]; + if (!area.IsMatchLocation(met)) + continue; + if (TryGetNextSub(area.Slots)) + return true; + } + return false; + } + + private bool TryGetNextSub(T[] slots) where T : class, IEncounterable, IEncounterMatch + { + while (SubIndex < slots.Length) + { + var enc = slots[SubIndex++]; + foreach (var evo in Chain) + { + if (enc.Species != evo.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool TryGetNext(T[] db) where T : class, IEncounterable, IEncounterMatch + { + for (; Index < db.Length;) + { + var enc = db[Index++]; + foreach (var evo in Chain) + { + if (evo.Species != enc.Species) + continue; + if (!enc.IsMatchExact(Entity, evo)) + break; + var rating = enc.GetMatchRating(Entity); + if (rating == EncounterMatchRating.Match) + return SetCurrent(enc); + + if (rating < Rating) + { + Deferred = enc; + Rating = rating; + } + break; + } + } + return false; + } + + private bool SetCurrent(T enc, EncounterMatchRating rating = EncounterMatchRating.Match) where T : IEncounterable + { + Current = new MatchedEncounter(enc, rating); + Yielded = true; + return true; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterDate.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterDate.cs new file mode 100644 index 000000000..f578fdfc8 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterDate.cs @@ -0,0 +1,76 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Logic for console specific date validity. +/// +public static class EncounterDate +{ + /// + /// Time provider to use for date fetching. + /// + public static ITimeProvider TimeProvider { get; set; } = DefaultTimeProvider.Instance; + + private static DateTime Now => TimeProvider.Now; + + /// + /// Fetches a valid date for the Nintendo DS. + /// + public static DateOnly GetDateNDS() => DateOnly.FromDateTime(Now); + + /// + /// Fetches a valid date for the Nintendo 3DS. + /// + public static DateOnly GetDate3DS() => DateOnly.FromDateTime(Now); + + /// + /// Fetches a valid date for the Nintendo Switch. + /// + public static DateOnly GetDateSwitch() => DateOnly.FromDateTime(Now); + + public static bool IsValidDateNDS(DateOnly date) + { + if (date.Year is < 2000 or > 2099) + return false; + return true; + } + + public static bool IsValidDate3DS(DateOnly date) + { + if (date.Year is < 2000 or > 2050) + return false; + return true; + } + + public static bool IsValidDateSwitch(DateOnly date) + { + if (date.Year is < 2000 or > 2050) + return false; + return true; + } +} + +/// +/// Default time provider that uses . +/// +public sealed class DefaultTimeProvider : ITimeProvider +{ + /// + /// Singleton instance of the default time provider. + /// + public static readonly DefaultTimeProvider Instance = new(); + + public DateTime Now => DateTime.Now; +} + +/// +/// Interface for fetching the current time. +/// +public interface ITimeProvider +{ + /// + /// Fetches the current time. + /// + DateTime Now { get; } +} diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs index 7722d51c4..0da2b336e 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs @@ -9,12 +9,6 @@ namespace PKHeX.Core; /// public static class EncounterLearn { - static EncounterLearn() - { - if (!EncounterEvent.Initialized) - EncounterEvent.RefreshMGDB(); - } - /// /// Default response if there are no matches. /// diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestionData.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestionData.cs index ae0747a3b..da9e436c5 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestionData.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestionData.cs @@ -3,9 +3,9 @@ namespace PKHeX.Core; /// /// Wrapper result to store suggestion data related to encounters. /// -public sealed class EncounterSuggestionData : ISpeciesForm, IRelearn +public sealed class EncounterSuggestionData : ISpeciesForm, IRelearn, ILevelRange { - private readonly IEncounterable? Encounter; + public readonly IEncounterable? Encounter; public ushort Species { get; } public byte Form { get; } diff --git a/PKHeX.Core/Legality/Encounters/Generator/AbilityPermission.cs b/PKHeX.Core/Legality/Encounters/Templates/Enums/AbilityPermission.cs similarity index 60% rename from PKHeX.Core/Legality/Encounters/Generator/AbilityPermission.cs rename to PKHeX.Core/Legality/Encounters/Templates/Enums/AbilityPermission.cs index 7533072b7..64155825d 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/AbilityPermission.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Enums/AbilityPermission.cs @@ -20,6 +20,11 @@ public enum AbilityPermission : sbyte /// public static class AbilityPermissionExtensions { + /// + /// Returns the value for the given index. + /// + /// Value to fetch the index for + /// public static byte GetSingleValue(this AbilityPermission value) => value switch { OnlyFirst => 0, @@ -28,6 +33,12 @@ public static class AbilityPermissionExtensions _ => throw new ArgumentOutOfRangeException(nameof(value)), }; + /// + /// Returns the value for the given index. + /// + /// Value to fetch the index for + /// Index to use + /// True if single index. public static bool IsSingleValue(this AbilityPermission value, out int index) { switch (value) @@ -39,6 +50,11 @@ public static class AbilityPermissionExtensions } } + /// + /// Indicates if the given value can be initially obtained with a hidden ability. + /// + /// Value to check + /// True if can be hidden. public static bool CanBeHidden(this AbilityPermission value) => value switch { Any12H => true, diff --git a/PKHeX.Core/Legality/Encounters/Templates/Enums/EncounterMatchRating.cs b/PKHeX.Core/Legality/Encounters/Templates/Enums/EncounterMatchRating.cs new file mode 100644 index 000000000..4a17f8754 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Enums/EncounterMatchRating.cs @@ -0,0 +1,80 @@ +using System.Collections; +using System.Collections.Generic; + +namespace PKHeX.Core; + +/// +/// Enumerates encounter match quality. +/// +public enum EncounterMatchRating : ushort +{ + /// Matches all data, no other matches will be better. + Match, + + /// Matches most data, might have a better match later. + Deferred, + + /// Matches most data, might have a better match later. Less preferred than due to small errors in secondary data. + DeferredErrors, + + /// Matches some data, but will likely have a better match later. + PartialMatch, + + /// Unused -- only used as an initial "max" value that anything else will be more suitable of a match. + MaxNotMatch, +} + +public readonly record struct MatchedEncounter(T Encounter, EncounterMatchRating Rating); + +public static class EncounterMatchUtil +{ + public static EncounterEnumerator Enumerate(this IReadOnlyList encounters, EvoCriteria[] chain, PKM pk) + where T : IEncounterMatch, IEncounterable + { + return new EncounterEnumerator(encounters, chain, pk); + } + + public struct EncounterEnumerator : IEnumerator> where T : IEncounterMatch, IEncounterable + { + private readonly IReadOnlyList _encounters; + private readonly EvoCriteria[] _chain; + private int _index = 0; + private readonly PKM _pk; + + public EncounterEnumerator(IReadOnlyList encounters, EvoCriteria[] chain, PKM pk) + { + _encounters = encounters; + _chain = chain; + _pk = pk; + } + + public MatchedEncounter Current { get; private set; } = default!; + readonly object IEnumerator.Current => Current; + + public bool MoveNext() + { + for (; _index < _encounters.Count;) + { + var enc = _encounters[_index++]; + foreach (var evo in _chain) + { + if (enc.Species != evo.Species) + continue; + + var exact = enc.IsMatchExact(_pk, evo); + if (!exact) + break; + + Current = new(enc, enc.GetMatchRating(_pk)); + return true; + } + } + + return false; + } + + public void Reset() => _index = 0; + public readonly void Dispose() { } + public readonly IEnumerator> GetEnumerator() => this; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Enums/HiddenAbilityPermission.cs b/PKHeX.Core/Legality/Encounters/Templates/Enums/HiddenAbilityPermission.cs new file mode 100644 index 000000000..c959373f3 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Enums/HiddenAbilityPermission.cs @@ -0,0 +1,8 @@ +namespace PKHeX.Core; + +public enum HiddenAbilityPermission : byte +{ + Never, + Always, + Possible, +} diff --git a/PKHeX.Core/Legality/Structures/Shiny.cs b/PKHeX.Core/Legality/Encounters/Templates/Enums/Shiny.cs similarity index 87% rename from PKHeX.Core/Legality/Structures/Shiny.cs rename to PKHeX.Core/Legality/Encounters/Templates/Enums/Shiny.cs index 03b753dd7..0941ede89 100644 --- a/PKHeX.Core/Legality/Structures/Shiny.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Enums/Shiny.cs @@ -29,6 +29,8 @@ public enum Shiny : byte /// public static class ShinyExtensions { + public static bool ShowSquareBeforeGen8 { get; set; } + public static bool IsValid(this Shiny s, PKM pk) => s switch { Shiny.Always => pk.IsShiny, @@ -46,8 +48,6 @@ public static class ShinyExtensions _ => false, }; - public static bool ShowSquareBeforeGen8 { get; set; } - public static Shiny GetType(PKM pk) { bool shiny = pk.IsShiny; @@ -59,6 +59,11 @@ public static class ShinyExtensions return Shiny.AlwaysStar; } + /// + /// Indicates if square shiny exists and is logical to show to the user. + /// + /// Entity to check + /// True if square shiny exists and is logical to show to the user. public static bool IsSquareShinyExist(PKM pk) { if (pk.Format < 8 && !ShowSquareBeforeGen8) diff --git a/PKHeX.Core/Legality/Enums/SlotType.cs b/PKHeX.Core/Legality/Encounters/Templates/Enums/SlotType.cs similarity index 97% rename from PKHeX.Core/Legality/Enums/SlotType.cs rename to PKHeX.Core/Legality/Encounters/Templates/Enums/SlotType.cs index 1fc33db02..462e9b8bf 100644 --- a/PKHeX.Core/Legality/Enums/SlotType.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Enums/SlotType.cs @@ -3,7 +3,7 @@ using System; namespace PKHeX.Core; /// -/// Wild Encounter data Type +/// Wild Encounter data Type /// /// /// Different from , this corresponds to the method that the may be encountered. diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea7g.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea7g.cs new file mode 100644 index 000000000..bbb35410b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea7g.cs @@ -0,0 +1,65 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// +/// encounter area for +/// +public sealed record EncounterArea7g : ISpeciesForm +{ + /// Species for the area + /// Due to how the encounter data is packaged by PKHeX, each species-form is grouped together. + public ushort Species { get; } + /// Form of the Species + public byte Form { get; } + public readonly EncounterSlot7GO[] Slots; + + private EncounterArea7g(ushort species, byte form, EncounterSlot7GO[] slots) + { + Species = species; + Form = form; + Slots = slots; + } + + internal static EncounterArea7g[] GetArea(BinLinkerAccessor data) + { + var areas = new EncounterArea7g[data.Length]; + for (int i = 0; i < areas.Length; i++) + areas[i] = GetArea(data[i]); + return areas; + } + + private const int meta = 4; + private const int entrySize = (2 * sizeof(int)) + 2; + + private static EncounterArea7g GetArea(ReadOnlySpan data) + { + var species = ReadUInt16LittleEndian(data); + var form = data[2]; + //var import = (EntityFormatDetected)data[3]; + + data = data[meta..]; + var result = new EncounterSlot7GO[data.Length / entrySize]; + var area = new EncounterArea7g(species, form, result); + for (int i = 0; i < result.Length; i++) + { + var offset = i * entrySize; + var entry = data.Slice(offset, entrySize); + result[i] = ReadSlot(entry, species, form); + } + + return area; + } + + private static EncounterSlot7GO ReadSlot(ReadOnlySpan entry, ushort species, byte form) + { + int start = ReadInt32LittleEndian(entry); + int end = ReadInt32LittleEndian(entry[4..]); + var sg = entry[8]; + var shiny = (Shiny)(sg & 0x3F); + var gender = (Gender)(sg >> 6); + var type = (PogoType)entry[9]; + return new EncounterSlot7GO(start, end, species, form, type.GetMinLevel(), EncountersGO.MAX_LEVEL, shiny, gender, type); + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea8g.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea8g.cs new file mode 100644 index 000000000..8907db751 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea8g.cs @@ -0,0 +1,65 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// +/// encounter area for direct-to-HOME transfers. +/// +public sealed record EncounterArea8g : ISpeciesForm +{ + /// Species for the area + /// Due to how the encounter data is packaged by PKHeX, each species-form is grouped together. + public ushort Species { get; } + /// Form of the Species + public byte Form { get; } + public readonly EncounterSlot8GO[] Slots; + + private EncounterArea8g(ushort species, byte form, EncounterSlot8GO[] slots) + { + Species = species; + Form = form; + Slots = slots; + } + + internal static EncounterArea8g[] GetArea(BinLinkerAccessor data) + { + var areas = new EncounterArea8g[data.Length]; + for (int i = 0; i < areas.Length; i++) + areas[i] = GetArea(data[i]); + return areas; + } + + private const int meta = 4; + private const int entrySize = (2 * sizeof(int)) + 2; + + private static EncounterArea8g GetArea(ReadOnlySpan data) + { + var species = ReadUInt16LittleEndian(data); + var form = data[2]; + var import = (PogoImportFormat)data[3]; + + data = data[meta..]; + var result = new EncounterSlot8GO[data.Length / entrySize]; + var area = new EncounterArea8g(species, form, result); + for (int i = 0; i < result.Length; i++) + { + var offset = i * entrySize; + var entry = data.Slice(offset, entrySize); + result[i] = ReadSlot(entry, species, form, import); + } + + return area; + } + + private static EncounterSlot8GO ReadSlot(ReadOnlySpan entry, ushort species, byte form, PogoImportFormat format) + { + int start = ReadInt32LittleEndian(entry); + int end = ReadInt32LittleEndian(entry[4..]); + var sg = entry[8]; + var shiny = (Shiny)(sg & 0x3F); + var gender = (Gender)(sg >> 6); + var type = (PogoType)entry[9]; + return new EncounterSlot8GO(start, end, species, form, type.GetMinLevel(), EncountersGO.MAX_LEVEL, shiny, gender, type, format); + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot7GO.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot7GO.cs new file mode 100644 index 000000000..8c6a86c6f --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot7GO.cs @@ -0,0 +1,142 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Encounter Slot found in (GO Park, ). +/// +/// +public sealed record EncounterSlot7GO(int StartDate, int EndDate, ushort Species, byte Form, byte LevelMin, byte LevelMax, Shiny Shiny, Gender Gender, PogoType Type) + : IEncounterable, IEncounterMatch, IPogoSlot, IEncounterConvertible +{ + public int Generation => 7; + public EntityContext Context => EntityContext.Gen7b; + public Ball FixedBall => Ball.None; // GO Park can override the ball; obey capture rules for LGP/E + public bool EggEncounter => false; + public AbilityPermission Ability => AbilityPermission.Any12; + public bool IsShiny => Shiny.IsShiny(); + public int EggLocation => 0; + + public GameVersion Version => GameVersion.GO; + public int Location => Locations.GO7; + public string Name => $"Wild Encounter ({Version})"; + public string LongName + { + get + { + var init = $"{Name} ({Type})"; + if (StartDate == 0 && EndDate == 0) + return init; + var start = PogoDateRangeExtensions.GetDateString(StartDate); + var end = PogoDateRangeExtensions.GetDateString(EndDate); + return $"{init}: {start}-{end}"; + } + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PB7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PB7 + { + PID = Util.Rand32(), + EncryptionConstant = Util.Rand32(), + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.GG[Species].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + Ball = (byte)Ball.Poke, + MetDate = this.GetRandomValidDate(), + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + }; + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation); + SetEncounterMoves(pk, LevelMin); + pk.AwakeningSetAllTo(2); + pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.ResetHeight(); + pk.ResetWeight(); + pk.ResetCP(); + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PB7 pk, EncounterCriteria criteria) + { + var g = Gender == Gender.Random ? -1 : (int)Gender; + int gender = criteria.GetGender(g, PersonalTable.GG[Species]); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + + pk.SetRandomIVsGO(Type.GetMinIV()); + pk.Nature = pk.StatNature = nature; + pk.Gender = gender; + pk.RefreshAbility(ability); + + switch (Shiny) + { + case Shiny.Random when !pk.IsShiny && criteria.Shiny.IsShiny(): + case Shiny.Always when !pk.IsShiny: // Force Square + var low = pk.PID & 0xFFFF; + pk.PID = ((low ^ pk.TID16 ^ pk.SID16 ^ 0) << 16) | low; + break; + + case Shiny.Random when pk.IsShiny && !criteria.Shiny.IsShiny(): + case Shiny.Never when pk.IsShiny: // Force Not Shiny + pk.PID ^= 0x1000_0000; + break; + } + } + + private void SetEncounterMoves(PB7 pk, int level) + { + Span moves = stackalloc ushort[4]; + ILearnSource source = LearnSource7GG.Instance; + source.SetEncounterMoves(Species, Form, level, moves); + pk.SetMoves(moves); + } + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + // Find the first chain that has slots defined. + // Since it is possible to evolve before transferring, we only need the highest evolution species possible. + // PoGoEncTool has already extrapolated the evolutions to separate encounters! + + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + //if (!slot.IsBallValid(ball)) -- can have any of the in-game balls due to re-capture + // continue; + if (!Shiny.IsValid(pk)) + return false; + //if (slot.Gender != Gender.Random && (int) slot.Gender != pk.Gender) + // continue; + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + var stamp = PogoDateRangeExtensions.GetTimeStamp(pk.Met_Year + 2000, pk.Met_Month, pk.Met_Day); + if (!this.IsWithinStartEnd(stamp)) + return EncounterMatchRating.DeferredErrors; + if (!this.GetIVsValid(pk)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot8GO.cs similarity index 60% rename from PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs rename to PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot8GO.cs index 6b58e616e..33fb9af57 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot8GO.cs @@ -5,12 +5,40 @@ namespace PKHeX.Core; /// /// Encounter Slot representing data transferred to (HOME). -/// +/// /// -public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEncounterServerDate +public sealed record EncounterSlot8GO(int StartDate, int EndDate, ushort Species, byte Form, byte LevelMin, byte LevelMax, Shiny Shiny, Gender Gender, PogoType Type, PogoImportFormat OriginFormat) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IPogoSlot, IFixedOTFriendship, IEncounterServerDate { - public override int Generation => 8; + public int Generation => 8; public bool IsDateRestricted => true; + public bool IsShiny => Shiny.IsShiny(); + public Ball FixedBall => Type.GetValidBall(); + public bool EggEncounter => false; + public AbilityPermission Ability => AbilityPermission.Any12; + public int EggLocation => 0; + public GameVersion Version => GameVersion.GO; + public int Location => Locations.GO8; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName + { + get + { + var init = $"{Name} ({Type})"; + if (StartDate == 0 && EndDate == 0) + return init; + var start = PogoDateRangeExtensions.GetDateString(StartDate); + var end = PogoDateRangeExtensions.GetDateString(EndDate); + return $"{init}: {start}-{end}"; + } + } + + private Ball GetRequiredBall(Ball fallback) + { + var fix = FixedBall; + return fix == Ball.None ? fallback : fix; + } /// /// Encounters need a Parent Game to determine the original moves when transferred to HOME. @@ -19,13 +47,7 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn /// Future game releases might change this value. /// With respect to date legality, new dates might be incompatible with initial values. /// - public PogoImportFormat OriginFormat { get; } - - public EncounterSlot8GO(EncounterArea8g area, ushort species, byte form, int start, int end, Shiny shiny, Gender gender, PogoType type, PogoImportFormat originFormat) - : base(area, species, form, start, end, shiny, gender, type) - { - OriginFormat = originFormat; - } + public PogoImportFormat OriginFormat { get; } = OriginFormat; /// /// Checks if the is compatible with the . @@ -40,7 +62,7 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn return Type.IsBallValid(ball); } - protected override PKM GetBlank() => OriginFormat switch + private PKM GetBlank() => OriginFormat switch { PogoImportFormat.PK7 => new PK8(), PogoImportFormat.PB7 => new PB7(), @@ -70,7 +92,7 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)), }; - public override EntityContext Context => OriginFormat switch + public EntityContext Context => OriginFormat switch { PogoImportFormat.PK7 or PogoImportFormat.PB7 => PersonalTable.BDSP.IsPresentInGame(Species, Form) ? EntityContext.Gen8b @@ -83,29 +105,48 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)), }; - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) { - pk.HT_Name = "PKHeX"; - pk.CurrentHandler = 1; - if (pk is IHandlerLanguage l) - l.HT_Language = 2; - - base.ApplyDetails(sav, criteria, pk); - var ball = Type.GetValidBall(); - if (ball != Ball.None) - pk.Ball = (int)ball; - - pk.OT_Friendship = OT_Friendship; - pk.SetRandomEC(); - - if (pk is IScaledSize s) + var pk = GetBlank(); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - if (pk is IScaledSize3 s3) - s3.Scale = s.HeightScalar; - } + pk.Language = lang; + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.Species = Species; + pk.Form = Form; + pk.CurrentLevel = LevelMin; + pk.OT_Friendship = OT_Friendship; + pk.Met_Location = Location; + pk.Met_Level = LevelMin; + pk.Version = (byte)GameVersion.GO; + pk.Ball = (byte)GetRequiredBall(Ball.Poke); + pk.MetDate = this.GetRandomValidDate(); + pk.OT_Name = tr.OT; + pk.OT_Gender = tr.Gender; + pk.HT_Name = "PKHeX"; + pk.CurrentHandler = 1; + if (pk is IHandlerLanguage l) + l.HT_Language = 2; + } + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation); + SetEncounterMoves(pk, LevelMin); + + if (pk is IScaledSize s2) + { + s2.HeightScalar = PokeSizeUtil.GetRandomScalar(); + s2.WeightScalar = PokeSizeUtil.GetRandomScalar(); + if (pk is IScaledSize3 s3) + s3.Scale = s2.HeightScalar; + } if (pk is PA8 pa8) { pa8.ResetHeight(); @@ -117,18 +158,20 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn pk9.TeraTypeOriginal = pk9.TeraTypeOverride = TeraTypeUtil.GetTeraTypeImport(pi.Type1, pi.Type2); pk9.Obedience_Level = (byte)pk9.Met_Level; } + pk.ResetPartyStats(); + return pk; } - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + private void SetPINGA(PKM pk, EncounterCriteria criteria) { var pi = GetPersonal(); if (OriginFormat is PogoImportFormat.PK7) pk.EXP = Experience.GetEXP(LevelMin, pi.EXPGrowth); - int gender = criteria.GetGender(-1, pi); + var g = Gender == Gender.Random ? -1 : (int)Gender; + int gender = criteria.GetGender(g, pi); int nature = (int)criteria.GetNature(Nature.Random); var ability = criteria.GetAbilityFromNumber(Ability); - pk.PID = Util.Rand32(); pk.Nature = pk.StatNature = nature; pk.Gender = gender; @@ -136,16 +179,27 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn if ((uint)ability < pi.AbilityCount) pk.Ability = pi.GetAbilityAtIndex(ability); - pk.SetRandomIVsGO(); - base.SetPINGA(pk, criteria); + pk.SetRandomIVsGO(Type.GetMinIV()); + + switch (Shiny) + { + case Shiny.Random when !pk.IsShiny && criteria.Shiny.IsShiny(): + case Shiny.Always when !pk.IsShiny: // Force Square + var low = pk.PID & 0xFFFF; + pk.PID = ((low ^ pk.TID16 ^ pk.SID16 ^ 0) << 16) | low; + break; + case Shiny.Random when pk.IsShiny && !criteria.Shiny.IsShiny(): + case Shiny.Never when pk.IsShiny: // Force Not Shiny + pk.PID ^= 0x1000_0000; + break; + } } - protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) + private void SetEncounterMoves(PKM pk, int level) { Span moves = stackalloc ushort[4]; GetInitialMoves(level, moves); pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); } public void GetInitialMoves(int level, Span moves) @@ -153,12 +207,30 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn var source = GameData.GetLearnSource(OriginGroup); source.SetEncounterMoves(Species, Form, level, moves); } + #endregion - public override EncounterMatchRating GetMatchRating(PKM pk) + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + if (!IsBallValid((Ball)pk.Ball, pk.Species, pk)) + return false; + if (!Shiny.IsValid(pk)) + return false; + if (Gender != Gender.Random && (int)Gender != pk.Gender) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) { if (IsMatchPartial(pk)) return EncounterMatchRating.PartialMatch; - return base.GetMatchRating(pk) == EncounterMatchRating.PartialMatch ? EncounterMatchRating.PartialMatch : EncounterMatchRating.Match; + if (!this.GetIVsValid(pk)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; } public byte OT_Friendship => Species switch @@ -181,7 +253,7 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn { if (!IsWithinDistributionWindow(pk)) return true; - if (!GetIVsAboveMinimum(pk)) + if (!this.GetIVsAboveMinimum(pk)) return true; // Eevee & Glaceon have different base friendships. Make sure if it is invalid that we yield the other encounter before. @@ -199,8 +271,8 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn public bool IsWithinDistributionWindow(DateOnly date) { - var stamp = GetTimeStamp(date.Year, date.Month, date.Day); - return IsWithinStartEnd(stamp); + var stamp = PogoDateRangeExtensions.GetTimeStamp(date.Year, date.Month, date.Day); + return this.IsWithinStartEnd(stamp); } private bool IsFormArgIncorrect(ISpeciesForm pk) => Species switch @@ -227,6 +299,7 @@ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship, IEn // No Form Argument relevant to check _ => false, }; + #endregion } /// diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoDateRange.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoDateRange.cs new file mode 100644 index 000000000..94318f5e4 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoDateRange.cs @@ -0,0 +1,57 @@ +using System; + +namespace PKHeX.Core; + +public interface IPogoDateRange +{ + /// Start date the encounter became available. If zero, no date specified (unbounded start). + int StartDate { get; } + + /// Last day the encounter was available. If zero, no date specified (unbounded finish). + /// If there is no end date (yet), we'll try to clamp to a date in the near-future to prevent it from being open-ended. + int EndDate { get; } +} + +public static class PogoDateRangeExtensions +{ + public static string GetDateString(int time) => time == 0 ? "X" : $"{GetDate(time):yyyy.MM.dd}"; + + private static DateOnly GetDate(int time) + { + var d = time & 0xFF; + var m = (time >> 8) & 0xFF; + var y = time >> 16; + return new DateOnly(y, m, d); + } + + public static bool IsWithinStartEnd(this IPogoDateRange time, int stamp) + { + if (time.EndDate == 0) + return time.StartDate <= stamp && GetDate(stamp) <= GetMaxDate(); + if (time.StartDate == 0) + return stamp <= time.EndDate; + return time.StartDate <= stamp && stamp <= time.EndDate; + } + + /// + /// Converts a split timestamp into a single integer. + /// + public static int GetTimeStamp(int year, int month, int day) => (year << 16) | (month << 8) | day; + + private static DateOnly GetMaxDate() => DateOnly.FromDateTime(DateTime.UtcNow.AddHours(12)); // UTC+12 for Kiribati, no daylight savings + + /// + /// Gets a random date within the availability range. + /// + public static DateOnly GetRandomValidDate(this IPogoDateRange time) + { + if (time.StartDate == 0) + return time.EndDate == 0 ? GetMaxDate() : GetDate(time.EndDate); + + var start = GetDate(time.StartDate); + if (time.EndDate == 0) + return start; + var end = GetDate(time.EndDate); + return DateUtil.GetRandomDateWithin(start, end); + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoSlot.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoSlot.cs new file mode 100644 index 000000000..65394ec5e --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoSlot.cs @@ -0,0 +1,57 @@ +namespace PKHeX.Core; + +/// +/// Stores details about encounters relevant for legality. +/// +public interface IPogoSlot : IPogoDateRange +{ + /// Possibility of shiny for the encounter. + Shiny Shiny { get; } + + /// Method the Pokémon may be encountered with. + PogoType Type { get; } + + /// Gender the Pokémon may be encountered with. + Gender Gender { get; } +} + +/// +/// Contains details about an encounter that can be found in . +/// +public static class PogoSlotExtensions +{ + public static bool GetIVsAboveMinimum(this IPogoSlot slot, PKM pk) + { + int min = slot.Type.GetMinIV(); + if (min == 0) + return true; + return GetIVsAboveMinimum(pk, min); + } + + private static bool GetIVsAboveMinimum(PKM pk, int min) + { + if (pk.IV_ATK >> 1 < min) // ATK + return false; + if (pk.IV_DEF >> 1 < min) // DEF + return false; + return pk.IV_HP >> 1 >= min; // HP + } + + public static bool GetIVsValid(this IPogoSlot slot, PKM pk) + { + if (!slot.GetIVsAboveMinimum(pk)) + return false; + + // HP * 2 | 1 -> HP + // ATK * 2 | 1 -> ATK&SPA + // DEF * 2 | 1 -> DEF&SPD + // Speed is random. + + // All IVs must be odd (except speed) and equal to their counterpart. + if ((pk.IV_ATK & 1) != 1 || pk.IV_ATK != pk.IV_SPA) // ATK=SPA + return false; + if ((pk.IV_DEF & 1) != 1 || pk.IV_DEF != pk.IV_SPD) // DEF=SPD + return false; + return (pk.IV_HP & 1) == 1; // HP + } +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/PogoType.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/PogoType.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/EncounterSlot/GO/PogoType.cs rename to PKHeX.Core/Legality/Encounters/Templates/GO/PogoType.cs diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterArea1.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterArea1.cs new file mode 100644 index 000000000..90757edd1 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterArea1.cs @@ -0,0 +1,48 @@ +using System; + +namespace PKHeX.Core; + +/// +/// encounter area +/// +public sealed record EncounterArea1 : IEncounterArea +{ + public EncounterSlot1[] Slots { get; } + public GameVersion Version { get; } + + public readonly byte Location; + public readonly SlotType Type; + public readonly byte Rate; + + public static EncounterArea1[] GetAreas(BinLinkerAccessor input, GameVersion game) + { + var result = new EncounterArea1[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea1(input[i], game); + return result; + } + + private EncounterArea1(ReadOnlySpan data, GameVersion game) + { + Location = data[0]; + // 1 byte unused + Type = (SlotType)data[2]; + Rate = data[3]; + Version = game; + + var next = data[4..]; + int count = next.Length / 4; + var slots = new EncounterSlot1[count]; + for (int i = 0; i < slots.Length; i++) + { + const int size = 4; + var entry = next.Slice(i * size, size); + byte max = entry[3]; + byte min = entry[2]; + byte slotNum = entry[1]; + byte species = entry[0]; + slots[i] = new EncounterSlot1(this, species, min, max, slotNum); + } + Slots = slots; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterGBLanguage.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterGBLanguage.cs new file mode 100644 index 000000000..d4de13014 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterGBLanguage.cs @@ -0,0 +1,16 @@ +namespace PKHeX.Core; + +/// +/// Generations 1 & 2 cannot communicate between Japanese & International versions. +/// +public enum EncounterGBLanguage +{ + /// Can only be obtained in Japanese games. + Japanese, + + /// Can only be obtained in International (not Japanese) games. + International, + + /// Can be obtained in any localization. + Any, +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterGift1.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterGift1.cs new file mode 100644 index 000000000..f40feb31b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterGift1.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PKHeX.Core; + +/// +/// Event data for Generation 1 +/// +public sealed record EncounterGift1(ushort Species, byte Level, GameVersion Version = GameVersion.RB) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFixedGBLanguage, IMoveset +{ + public int Generation => 1; + public EntityContext Context => EntityContext.Gen1; + public bool EggEncounter => false; + public int EggLocation => 0; + public Ball FixedBall => Ball.Poke; + public AbilityPermission Ability => AbilityPermission.OnlyHidden; + public bool IsShiny => false; + public int Location => 0; + + public const ushort UnspecifiedID = 0; + + public Shiny Shiny { get; init; } = Shiny.Random; + public byte Form => 0; + + public EncounterGBLanguage Language { get; init; } = EncounterGBLanguage.Japanese; + + /// Trainer name for the event. + public string OT_Name { get; init; } = string.Empty; + + public IReadOnlyList OT_Names { get; init; } = Array.Empty(); + + /// Trainer ID for the event. + public ushort TID16 { get; init; } = UnspecifiedID; + + public IndividualValueSet IVs { get; init; } + public Moveset Moves { get; init; } + + public string Name => "Event Gift"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + + public PK1 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK1 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var lang = GetTemplateLanguage(tr); + var isJapanese = lang == (int)LanguageID.Japanese; + var pi = EncounterUtil1.GetPersonal1(Version, Species); + var pk = new PK1(isJapanese) + { + Species = Species, + CurrentLevel = LevelMin, + Catch_Rate = GetInitialCatchRate(), + DV16 = EncounterUtil1.GetRandomDVs(Util.Rand), + + OT_Name = EncounterUtil1.GetTrainerName(tr, lang), + TID16 = tr.TID16, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + Type1 = pi.Type1, + Type2 = pi.Type2, + }; + + if (TID16 != UnspecifiedID) + pk.TID16 = TID16; + if (OT_Name.Length != 0) + pk.OT_Name = OT_Name; + else if (OT_Names.Count != 0) + pk.OT_Name = OT_Names[Util.Rand.Next(OT_Names.Count)]; + + if (Version == GameVersion.Stadium) + { + // Amnesia Psyduck has different catch rates depending on language + if (Species == (int)Core.Species.Psyduck) + pk.Catch_Rate = pk.Japanese ? (byte)167 : (byte)168; + else + pk.Catch_Rate = Util.Rand.Next(2) == 0 ? (byte)167 : (byte)168; + } + + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + + pk.ResetPartyStats(); + return pk; + } + + private int GetTemplateLanguage(ITrainerInfo tr) + { + // Japanese events must be Japanese + if (Language == EncounterGBLanguage.Japanese) + return 1; + + // International events must be non-Japanese + var lang = (int)Core.Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version); + if (lang == 1 && Language == EncounterGBLanguage.International) + return 2; + return lang; + } + + #endregion + + private byte GetInitialCatchRate() + { + if (Version == GameVersion.Stadium) + { + // Amnesia Psyduck has different catch rates depending on language + if (Species == (int)Core.Species.Psyduck) + return (Language == EncounterGBLanguage.Japanese) ? (byte)167 : (byte)168; + } + + // Encounters can have different Catch Rates (RBG vs Y) + return EncounterUtil1.GetWildCatchRate(Version, Species); + } + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Language != EncounterGBLanguage.Any && pk.Japanese != (Language == EncounterGBLanguage.Japanese)) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (Level > evo.LevelMax) + return false; + // Encounters with this version have to originate from the Japanese Blue game. + if (Version == GameVersion.BU && !pk.Japanese) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + + // EC/PID check doesn't exist for these, so check Shiny state here. + if (!IsShinyValid(pk)) + return false; + + if (TID16 != UnspecifiedID && pk.TID16 != TID16) + return false; + + if (OT_Name.Length != 0) + { + if (pk.OT_Name != OT_Name) + return false; + } + else if (OT_Names.Count != 0) + { + if (!OT_Names.Contains(pk.OT_Name)) + return false; + } + + return true; + } + + private bool IsShinyValid(PKM pk) => Shiny switch + { + Shiny.Never => !pk.IsShiny, + Shiny.Always => pk.IsShiny, + _ => true, + }; + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format <= 2) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private static bool IsMatchLocation(PKM pk) + { + // Met Location is not stored in the PK1 format. + if (pk is ICaughtData2 { CaughtData: not 0 }) + return false; + return true; + } + + private bool IsMatchPartial(PKM pk) + { + if (pk is not PK1 pk1) + return false; + return !IsCatchRateValid(pk1.Catch_Rate); + } + + private bool IsCatchRateValid(byte catch_rate) + { + if (ParseSettings.AllowGen1Tradeback && PK1.IsCatchRateHeldItem(catch_rate)) + return true; + + if (Version == GameVersion.Stadium) + { + // Amnesia Psyduck has different catch rates depending on language + if (Species == (int)Core.Species.Psyduck) + return catch_rate == (Language == EncounterGBLanguage.Japanese ? 167 : 168); + return catch_rate is 167 or 168; + } + + // Encounters can have different Catch Rates (RBG vs Y) + return GBRestrictions.RateMatchesEncounter(Species, Version, catch_rate); + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterSlot1.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterSlot1.cs new file mode 100644 index 000000000..dd3c4df0f --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterSlot1.cs @@ -0,0 +1,77 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot1(EncounterArea1 Parent, ushort Species, byte LevelMin, byte LevelMax, byte SlotNumber) + : IEncounterConvertible, IEncounterable, IEncounterMatch, INumberedSlot +{ + public int Generation => 1; + public EntityContext Context => EntityContext.Gen1; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public AbilityPermission Ability => AbilityPermission.OnlyHidden; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + + public byte Form => 0; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + + public PK1 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK1 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version); + var isJapanese = lang == (int)LanguageID.Japanese; + var pi = EncounterUtil1.GetPersonal1(Version, Species); + var pk = new PK1(isJapanese) + { + Species = Species, + CurrentLevel = LevelMin, + Catch_Rate = EncounterUtil1.GetWildCatchRate(Version, Species), + DV16 = EncounterUtil1.GetRandomDVs(Util.Rand), + + OT_Name = EncounterUtil1.GetTrainerName(tr, lang), + TID16 = tr.TID16, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + Type1 = pi.Type1, + Type2 = pi.Type2, + }; + + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + + pk.ResetPartyStats(); + return pk; + } + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (LevelMin > evo.LevelMax) + return false; + + if (pk is not PK1 pk1) + return true; + + var rate = pk1.Catch_Rate; + var expect = EncounterUtil1.GetWildCatchRate(Version, Species); + if (expect != rate && !(ParseSettings.AllowGen1Tradeback && GBRestrictions.IsTradebackCatchRate(rate))) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterStatic1.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterStatic1.cs new file mode 100644 index 000000000..2d744dc65 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterStatic1.cs @@ -0,0 +1,133 @@ +namespace PKHeX.Core; + +/// +/// Generation 1 Static Encounter +/// +public sealed record EncounterStatic1(ushort Species, byte Level, GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible +{ + public int Generation => 1; + public EntityContext Context => EntityContext.Gen1; + public bool EggEncounter => false; + public int EggLocation => 0; + public Ball FixedBall => Ball.Poke; + public AbilityPermission Ability => AbilityPermission.OnlyHidden; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int Location => 0; + + private const int LightBallPikachuCatchRate = 0xA3; // 163 + public byte Form => 0; + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public bool IsStarterPikachu => Version == GameVersion.YW && Species == (int)Core.Species.Pikachu && Level == 5; + + private byte GetInitialCatchRate() + { + if (IsStarterPikachu) + return LightBallPikachuCatchRate; // Light Ball + + // Encounters can have different Catch Rates (RBG vs Y) + return EncounterUtil1.GetWildCatchRate(Version, Species); + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + + public PK1 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK1 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version); + var isJapanese = lang == (int)LanguageID.Japanese; + var pi = EncounterUtil1.GetPersonal1(Version, Species); + var pk = new PK1(isJapanese) + { + Species = Species, + CurrentLevel = LevelMin, + Catch_Rate = GetInitialCatchRate(), + DV16 = EncounterUtil1.GetRandomDVs(Util.Rand), + + OT_Name = EncounterUtil1.GetTrainerName(tr, lang), + TID16 = tr.TID16, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + Type1 = pi.Type1, + Type2 = pi.Type2, + }; + + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + + pk.ResetPartyStats(); + return pk; + } + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (Level > evo.LevelMax) + return false; + // Encounters with this version have to originate from the Japanese Blue game. + if (Version == GameVersion.BU && !pk.Japanese) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format <= 2) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private static bool IsMatchLocation(PKM pk) + { + // Met Location is not stored in the PK1 format. + if (pk is ICaughtData2 { CaughtData: not 0 }) + return false; + return true; + } + + private bool IsMatchPartial(PKM pk) + { + if (pk is not PK1 pk1) + return false; + return !IsCatchRateValid(pk1.Catch_Rate); + } + + private bool IsCatchRateValid(byte catch_rate) + { + if (ParseSettings.AllowGen1Tradeback && PK1.IsCatchRateHeldItem(catch_rate)) + return true; + + // Light Ball (Yellow) starter + if (IsStarterPikachu) + return catch_rate == LightBallPikachuCatchRate; + + // Encounters can have different Catch Rates (RBG vs Y) + return GBRestrictions.RateMatchesEncounter(Species, Version, catch_rate); + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterTrade1.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterTrade1.cs new file mode 100644 index 000000000..2dbc75d03 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterTrade1.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; + +namespace PKHeX.Core; + +/// +/// Trade Encounter data with a fixed Catch Rate +/// +/// +/// Generation 1 specific value used in detecting unmodified/un-traded Generation 1 Trade Encounter data. +/// Species & Minimum level (legal) possible to acquire at. +/// +public sealed record EncounterTrade1 : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible +{ + public int Generation => 1; + public EntityContext Context => EntityContext.Gen1; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public AbilityPermission Ability => AbilityPermission.OnlyHidden; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int Location => 0; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + public bool IsFixedNickname => true; + + private static IReadOnlyList TrainerNames => StringConverter12.G1TradeOTName; + private string[] Nicknames { get; } + public ushort Species { get; } + public byte Form => 0; + public bool EvolveOnTrade { get; init; } + public GameVersion Version { get; } + public byte LevelMinRBY { get; } + public byte LevelMinGSC { get; } + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => CanObtainMinGSC() ? LevelMinGSC : LevelMinRBY; + public byte LevelMax => 100; + + public EncounterTrade1(ReadOnlySpan names, byte index, ushort species, GameVersion version, byte rby) : this(names, index, species, version, rby, rby) { } + + public EncounterTrade1(ReadOnlySpan names, byte index, ushort species, GameVersion version, byte levelMinRBY, byte levelMinGSC) + { + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + Species = species; + Version = version; + LevelMinRBY = levelMinRBY; + LevelMinGSC = levelMinGSC; + } + + private bool IsNicknameValid(PKM pk) + { + var nick = pk.Nickname; + if (pk.Format <= 2) + return IsNicknameAnyMatch(nick); + + // Converted string 1/2->7 to language specific value + // Nicknames can be from any of the languages it can trade between. + int lang = pk.Language; + if (lang == 1) + { + // Special consideration for Hiragana strings that are transferred + if (Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio) + return nick == "ぐりお"; + return nick == Nicknames[1]; + } + + return GetNicknameIndex(nick) >= 2; + } + + private bool IsNicknameAnyMatch(ReadOnlySpan current) => GetNicknameIndex(current) >= 0; + + private static bool IsTrainerNameValid(PKM pk) + { + if (pk.Format <= 2) + return pk.OT_Name == StringConverter12.G1TradeOTStr; + return pk.Language switch + { + 1 => GetIndex(pk.OT_Name, TrainerNames) == 1, + _ => GetIndex(pk.OT_Name, TrainerNames) >= 2, + }; + } + + private int GetNicknameIndex(ReadOnlySpan nickname) => GetIndex(nickname, Nicknames); + + private static int GetIndex(ReadOnlySpan name, IReadOnlyList arr) + { + for (int i = 0; i < arr.Count; i++) + { + if (arr[i].AsSpan().SequenceEqual(name)) + return i; + } + + return -1; + } + + private bool CanObtainMinGSC() + { + if (!ParseSettings.AllowGen1Tradeback) + return false; + if (Version == GameVersion.BU && EvolveOnTrade) + return ParseSettings.AllowGBStadium2; + return true; + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK1 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK1 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var level = ParseSettings.AllowGen1Tradeback ? LevelMinGSC : LevelMinRBY; + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version); + var isJapanese = lang == (int)LanguageID.Japanese; + var pi = EncounterUtil1.GetPersonal1(Version, Species); + var pk = new PK1(isJapanese) + { + Species = Species, + CurrentLevel = level, + Catch_Rate = EncounterUtil1.GetWildCatchRate(Version, Species), + DV16 = EncounterUtil1.GetRandomDVs(Util.Rand), + + OT_Name = StringConverter12.G1TradeOTStr, + Nickname = Nicknames[lang], + TID16 = tr.TID16, + Type1 = pi.Type1, + Type2 = pi.Type2, + }; + + EncounterUtil1.SetEncounterMoves(pk, Version, level); + + pk.ResetPartyStats(); + + return pk; + } + #endregion + + #region Matching + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => IsTrainerNameValid(pk); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) => IsNicknameValid(pk); + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchLevel(pk, evo.LevelMax)) // minimum required level + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (Version == GameVersion.BU) + { + // Encounters with this version have to originate from the Japanese Blue game. + if (!pk.Japanese) + return false; + // Stadium 2 can transfer from GSC->RBY without a "Trade", thus allowing un-evolved outsiders + if (EvolveOnTrade && !ParseSettings.AllowGBStadium2 && pk.CurrentLevel < LevelMinRBY) + return false; + } + return true; + } + + private bool IsMatchLevel(PKM pk, int lvl) + { + if (pk is not PK1) + return lvl >= LevelMinGSC; + return lvl >= LevelMin; + } + + private bool IsMatchPartial(PKM pk) + { + if (!IsTrainerNameValid(pk)) + return true; + if (!IsNicknameValid(pk)) + return true; + if (EvolveOnTrade && pk.Species == Species) + return false; + if (ParseSettings.AllowGen1Tradeback) + return false; + if (pk is not PK1 pk1) + return false; + + var req = EncounterUtil1.GetWildCatchRate(Version, Species); + return req != pk1.Catch_Rate; + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterUtil1.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterUtil1.cs new file mode 100644 index 000000000..84bb83e07 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterUtil1.cs @@ -0,0 +1,49 @@ +using System; + +namespace PKHeX.Core; + +public static class EncounterUtil1 +{ + public const int FormDynamic = FormVivillon; + public const byte FormVivillon = 30; + public const byte FormRandom = 31; + + public static ushort GetRandomDVs(Random rand) => (ushort)rand.Next(ushort.MaxValue + 1); + + public static byte GetWildCatchRate(GameVersion version, ushort species) => (byte)(version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB)[species].CatchRate; + public static (byte Type1, byte Type2) GetTypes(GameVersion version, ushort species) + { + var pi = GetPersonal1(version, species); + return (pi.Type1, pi.Type2); + } + + public static PersonalInfo1 GetPersonal1(GameVersion version, ushort species) + { + var pt = version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; + return pt[species]; + } + + public static void SetEncounterMoves(T pk, GameVersion version, int level) where T : PKM + { + Span moves = stackalloc ushort[4]; + var source = GameData.GetLearnSource(version); + source.SetEncounterMoves(pk.Species, 0, level, moves); + pk.SetMoves(moves); + } + + public static string GetTrainerName(ITrainerInfo tr, int lang) => lang switch + { + (int)LanguageID.Japanese => tr.Language == 1 ? tr.OT : "ゲーフリ", + _ => tr.Language == 1 ? "GF" : tr.OT, + }; + + public static ushort GetDV16(IndividualValueSet actual) + { + ushort result = 0; + result |= (ushort)(actual.SPA << 0); + result |= (ushort)(actual.SPE << 4); + result |= (ushort)(actual.DEF << 8); + result |= (ushort)(actual.ATK << 12); + return result; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/IFixedGBLanguage.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/IFixedGBLanguage.cs new file mode 100644 index 000000000..6adbe4d14 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/IFixedGBLanguage.cs @@ -0,0 +1,12 @@ +namespace PKHeX.Core; + +/// +/// Exposes info on language restriction for Gen1/2. +/// +public interface IFixedGBLanguage +{ + /// + /// Language restriction for the encounter template. + /// + EncounterGBLanguage Language { get; } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterArea2.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterArea2.cs new file mode 100644 index 000000000..acb2d9b2b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterArea2.cs @@ -0,0 +1,75 @@ +using System; + +namespace PKHeX.Core; + +/// +/// encounter area +/// +public sealed record EncounterArea2 : IEncounterArea, IAreaLocation +{ + public EncounterSlot2[] Slots { get; } + public GameVersion Version { get; } + + private static ReadOnlySpan BCC_SlotRates => new byte[] { 20, 20, 10, 10, 05, 05, 10, 10, 05, 05 }; + private static ReadOnlySpan RatesGrass => new byte[] { 30, 30, 20, 10, 5, 4, 1 }; + private static ReadOnlySpan RatesSurf => new byte[] { 60, 30, 10 }; + + public readonly byte[]? Rates; + internal readonly EncounterTime Time; + public readonly byte Rate; + public readonly byte Location; + public readonly SlotType Type; + + public bool IsMatchLocation(int location) => location == Location; + + public static EncounterArea2[] GetAreas(BinLinkerAccessor input, GameVersion game) + { + var result = new EncounterArea2[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea2(input[i], game); + return result; + } + + private EncounterArea2(ReadOnlySpan data, GameVersion game) + { + Location = data[0]; + Time = (EncounterTime)data[1]; + var type = (Type = (SlotType)data[2]) & (SlotType)0xF; + Rate = data[3]; + + Version = game; + + var next = data[4..]; + if (type is > SlotType.Surf and not SlotType.BugContest) // Not Grass/Surf + { + const int size = 5; + int count = next.Length / size; + Rates = next[..count].ToArray(); + Slots = ReadSlots(next[count..], count); + } + else + { + const int size = 4; + int count = next.Length / size; + Rates = null; // fetch as needed. + Slots = ReadSlots(next, count); + } + } + + private EncounterSlot2[] ReadSlots(ReadOnlySpan data, int count) + { + const int size = 4; + var slots = new EncounterSlot2[count]; + for (int i = 0; i < slots.Length; i++) + { + var entry = data.Slice(i * size, size); + byte max = entry[3]; + byte min = entry[2]; + byte slotNum = entry[1]; + byte species = entry[0]; + var form = species == (int)Species.Unown ? EncounterUtil1.FormRandom : (byte)0; + slots[i] = new EncounterSlot2(this, species, form, min, max, slotNum); + } + return slots; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterGift2.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterGift2.cs new file mode 100644 index 000000000..eb014c6d3 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterGift2.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PKHeX.Core; + +/// +/// Event data for Generation 2 +/// +public sealed record EncounterGift2(ushort Species, byte Level, GameVersion Version = GameVersion.GS) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFixedGBLanguage, IHatchCycle, IMoveset +{ + public int Generation => 2; + public EntityContext Context => EntityContext.Gen2; + public byte Form => 0; + + public Ball FixedBall => Ball.Poke; + int ILocation.Location => Location; + public int EggLocation => 0; + public bool IsShiny => Shiny == Shiny.Always; + public AbilityPermission Ability => AbilityPermission.OnlyHidden; + + public Shiny Shiny { get; init; } = Shiny.Random; + public byte Location { get; init; } + public IndividualValueSet IVs { get; init; } + public Moveset Moves { get; init; } + public bool EggEncounter => EggCycles != 0; + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + public EncounterGBLanguage Language { get; init; } = EncounterGBLanguage.Japanese; + + /// Trainer name for the event. + public string OT_Name { get; init; } = string.Empty; + + public IReadOnlyList OT_Names { get; init; } = Array.Empty(); + + private const ushort UnspecifiedID = 0; + + /// Trainer ID for the event. + public ushort TID16 { get; init; } = UnspecifiedID; + + public bool IsGift => TID16 != UnspecifiedID; + + public sbyte CurrentLevel { get; init; } = -1; + + public byte EggCycles { get; init; } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK2 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = GetTemplateLanguage(tr); + var pk = new PK2 + { + Species = Species, + CurrentLevel = CurrentLevel == -1 ? LevelMin : CurrentLevel, + + TID16 = TID16 != UnspecifiedID ? TID16 : tr.TID16, + OT_Name = GetInitialOT(tr), + + OT_Friendship = PersonalTable.C[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + if (EggEncounter) + { + } + else if (Version == GameVersion.C || (Version == GameVersion.GSC && tr.Game == (int)GameVersion.C)) + { + if (!IsGift) + pk.OT_Gender = tr.Gender; + pk.Met_Level = LevelMin; + pk.Met_Location = Location; + pk.Met_TimeOfDay = EncounterTime.Any.RandomValidTime(); + } + + if (Shiny == Shiny.Always) + pk.SetShiny(); + + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, version, LevelMin); + + if (IVs.IsSpecified) + criteria.SetRandomIVs(pk, IVs); + else + criteria.SetRandomIVs(pk); + + pk.ResetPartyStats(); + + return pk; + } + + private int GetTemplateLanguage(ITrainerInfo tr) + { + // Japanese events must be Japanese + if (Language == EncounterGBLanguage.Japanese) + return 1; + + // International events must be non-Japanese + var lang = (int)Core.Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version); + if (lang == 1 && Language == EncounterGBLanguage.International) + return 2; + return lang; + } + + private string GetInitialOT(ITrainerInfo tr) + { + if (OT_Name.Length != 0) + return OT_Name; + if (OT_Names.Count != 0) + return OT_Names[Util.Rand.Next(OT_Names.Count)]; + return tr.OT; + } + + #endregion + + #region Matching + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Shiny == Shiny.Always && !pk.IsShiny) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (IVs.IsSpecified) + { + if (Shiny == Shiny.Always && !pk.IsShiny) + return false; + if (Shiny == Shiny.Never && pk.IsShiny) + return false; + if (pk.Format <= 2) + { + if (!Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) + return false; + } + } + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + + if (Language != EncounterGBLanguage.Any && pk.Japanese != (Language == EncounterGBLanguage.Japanese)) + return false; + + if (CurrentLevel != -1 && CurrentLevel > pk.CurrentLevel) + return false; + + // EC/PID check doesn't exist for these, so check Shiny state here. + if (!IsShinyValid(pk)) + return false; + + if (EggEncounter && !pk.IsEgg) + return true; + + // Check OT Details + if (TID16 != UnspecifiedID && pk.TID16 != TID16) + return false; + + if (OT_Name.Length != 0) + { + if (pk.OT_Name != OT_Name) + return false; + } + else if (OT_Names.Count != 0) + { + if (!OT_Names.Contains(pk.OT_Name)) + return false; + } + + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + if (pk is not ICaughtData2 c2) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + if (pk.IsEgg) + { + if (!EggEncounter) + return false; + if (c2.Met_Location != 0 && c2.Met_Level != 0) + return false; + if (pk.OT_Friendship > EggCycles) + return false; + } + else + { + switch (c2.Met_Level) + { + case 0 when c2.Met_Location != 0: + return false; + case 1: // 0 = second floor of every Pokémon Center, valid + return true; + default: + if (pk.Met_Location == 0 && c2.Met_Level != 0) + return false; + break; + } + } + + return true; + } + + private bool IsMatchLocation(PKM pk) + { + if (EggEncounter) + return true; + if (pk is not ICaughtData2 c2) + return true; + + if (Version is GameVersion.C or GameVersion.GSC) + { + if (c2.CaughtData is not 0) + return Location == pk.Met_Location; + if (pk.Species == (int)Core.Species.Celebi) + return false; // Cannot reset the Met data + } + return true; + } + + private bool IsShinyValid(PKM pk) => Shiny switch + { + Shiny.Never => !pk.IsShiny, + Shiny.Always => pk.IsShiny, + _ => true, + }; + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk is ICaughtData2 { CaughtData: not 0 }) + return pk.Met_Level == (EggEncounter ? 1 : Level); + return evo.LevelMax >= Level; + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterSlot2.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterSlot2.cs new file mode 100644 index 000000000..79a9474a9 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterSlot2.cs @@ -0,0 +1,156 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +/// Referenced Area object contains Time data which is used for origin data. +/// +public sealed record EncounterSlot2(EncounterArea2 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax, byte SlotNumber) + : IEncounterable, IEncounterMatch, IEncounterConvertible, INumberedSlot +{ + public int Generation => 2; + public EntityContext Context => EntityContext.Gen2; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public AbilityPermission Ability => AbilityPermission.OnlyHidden; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + + // we have "Special" bitflag. Strip it out. + public SlotType SlotType => Type & (SlotType)0xF; + public bool IsHeadbutt => SlotType == SlotType.Headbutt; + + private static ReadOnlySpan TreeIndexes => new byte[] + { + 02, 04, 05, 08, 11, 12, 14, 15, 18, 20, 21, 25, 26, 34, 37, 38, 39, 91, 92, + }; + + private static ReadOnlySpan Trees => new[] + { + 0x3FF_3FF, // Route 29 + 0x0FF_3FF, // Route 30 + 0x3FE_3FF, // Route 31 + 0x3EE_3FF, // Route 32 + 0x240_3FF, // Route 33 + 0x37F_3FF, // Azalea Town + 0x3FF_3FF, // Ilex Forest + 0x001_3FE, // Route 34 + 0x261_3FF, // Route 35 + 0x3FF_3FF, // Route 36 + 0x2B9_3FF, // Route 37 + 0x3FF_3FF, // Route 38 + 0x184_3FF, // Route 39 + 0x3FF_3FF, // Route 42 + 0x3FF_3FF, // Route 43 + 0x3FF_3FF, // Lake of Rage + 0x2FF_3FF, // Route 44 + 0x200_1FF, // Route 26 + 0x2BB_3FF, // Route 27 + }; + + public bool IsTreeAvailable(ushort trainerID) + { + var treeIndex = TreeIndexes.BinarySearch((byte)Location); + if (treeIndex < 0) + return false; + var permissions = Trees[treeIndex]; + + var pivot = trainerID % 10; + return Type switch + { + SlotType.Headbutt => (permissions & (1 << pivot)) != 0, + /*special*/ _ => (permissions & (1 << (pivot + 12))) != 0, + }; + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + + public PK2 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var isJapanese = lang == (int)LanguageID.Japanese; + var pk = new PK2(isJapanese) + { + Species = Species, + // Form is only Unown and is derived from IVs. + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.C[Species].BaseFriendship, + DV16 = EncounterUtil1.GetRandomDVs(Util.Rand), + + Language = lang, + OT_Name = tr.OT, + TID16 = tr.TID16, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + if (Version == GameVersion.C) + { + pk.OT_Gender = tr.Gender; + pk.Met_Level = LevelMin; + pk.Met_Location = Location; + pk.Met_TimeOfDay = GetRandomTime(); + } + + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + if (IsHeadbutt) + { + var id = pk.TID16; + if (!IsTreeAvailable(id)) + { + // Get a random TID that satisfies this slot. + do { id = (ushort)Util.Rand.Next(); } + while (!IsTreeAvailable(id)); + pk.TID16 = id; + } + } + + pk.ResetPartyStats(); + return pk; + } + + public int GetRandomTime() => Parent.Time.RandomValidTime(); + + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (evo.Form != Form) + { + if (Species != (int)Core.Species.Unown || evo.Form >= 26) // Don't yield !? forms + return false; + } + + if (pk is not ICaughtData2 {CaughtData: not 0} c2) + return LevelMin <= evo.LevelMax; + + if (!this.IsLevelWithinRange(c2.Met_Level)) + return false; + if (!Parent.Time.Contains(c2.Met_TimeOfDay)) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsHeadbutt && !IsTreeAvailable(pk.TID16)) + return EncounterMatchRating.DeferredErrors; + return EncounterMatchRating.Match; + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterStatic2.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterStatic2.cs new file mode 100644 index 000000000..94dae457e --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterStatic2.cs @@ -0,0 +1,214 @@ +namespace PKHeX.Core; + +/// +/// Generation 2 Static Encounter +/// +public sealed record EncounterStatic2(ushort Species, byte Level, GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IHatchCycle, IFixedGender, IMoveset +{ + public int Generation => 2; + public EntityContext Context => EntityContext.Gen2; + public byte Form => 0; + public byte EggCycles => DizzyPunchEgg ? (byte)20 : (byte)0; + public bool DizzyPunchEgg => EggEncounter && Moves.HasMoves; + + public Ball FixedBall => Ball.Poke; + int ILocation.Location => Location; + public int EggLocation => 0; + public bool IsShiny => Shiny == Shiny.Always; + public AbilityPermission Ability => AbilityPermission.OnlyHidden; + public bool Roaming => Species is (int)Core.Species.Entei or (int)Core.Species.Raikou or (int)Core.Species.Suicune && Location != 23; + + public Shiny Shiny { get; init; } = Shiny.Random; + public byte Location { get; init; } + public sbyte Gender { get; init; } = -1; + public IndividualValueSet IVs { get; init; } + public Moveset Moves { get; init; } + public bool EggEncounter { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK2 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK2 + { + Species = Species, + CurrentLevel = LevelMin, + + TID16 = tr.TID16, + OT_Name = tr.OT, + + OT_Friendship = PersonalTable.C[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + if (EggEncounter) + { + if (DizzyPunchEgg) + pk.EXP = 125; + } + else if (Version == GameVersion.C || (Version == GameVersion.GSC && tr.Game == (int)GameVersion.C)) + { + pk.OT_Gender = tr.Gender; + pk.Met_Level = LevelMin; + pk.Met_Location = Location; + pk.Met_TimeOfDay = EncounterTime.Any.RandomValidTime(); + } + + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, version, LevelMin); + + if (IVs.IsSpecified) + criteria.SetRandomIVs(pk, IVs); + else + criteria.SetRandomIVs(pk); + + pk.ResetPartyStats(); + + return pk; + } + + #endregion + + #region Matching + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Shiny == Shiny.Always && !pk.IsShiny) + return false; + if (EggEncounter && Moves.HasMoves) // Odd Egg + { + if (pk.Format > 2) + return false; + if (!pk.HasMove((int)Move.DizzyPunch)) + return false; + + // EXP is a fixed starting value for eggs + if (pk.IsEgg) + { + if (pk.EXP != 125) + return false; + } + else + { + if (pk.EXP < 125) + return false; + } + } + + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (IVs.IsSpecified) + { + if (Shiny == Shiny.Always && !pk.IsShiny) + return false; + if (Shiny == Shiny.Never && pk.IsShiny) + return false; + if (pk.Format <= 2) + { + if (!Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) + return false; + } + else + { + if (Gender != -1 && pk.Gender != Gender) + return false; + } + } + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + if (pk is not ICaughtData2 c2) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + if (pk.IsEgg) + { + if (!EggEncounter) + return false; + if (c2.Met_Location != 0 && c2.Met_Level != 0) + return false; + } + else + { + switch (c2.Met_Level) + { + case 0 when c2.Met_Location != 0: + return false; + case 1: // 0 = second floor of every Pokémon Center, valid + return true; + default: + if (pk.Met_Location == 0 && c2.Met_Level != 0) + return false; + break; + } + } + + return true; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk is ICaughtData2 { CaughtData: not 0 }) + return pk.Met_Level == (EggEncounter ? 1 : Level); + + return Level <= evo.LevelMax; + } + + // Routes 29-46, except 40 & 41; total 16. + // 02, 04, 05, 08, 11, 15, 18, 20, + // 21, 25, 26, 34, 37, 39, 43, 45, + private const ulong RoamLocations = 0b10_1000_1010_0100_0000_0110_0011_0100_1000_1001_0011_0100; + + private bool IsMatchLocation(PKM pk) + { + if (EggEncounter) + return true; + if (pk is not ICaughtData2 c2) + return true; + if (c2.CaughtData is 0 && Version != GameVersion.C) + return true; // GS + + if (Roaming) + { + // Gen2 met location is always u8 + var loc = c2.Met_Location; + return loc <= 45 && ((RoamLocations & (1UL << loc)) != 0); + } + if (Version is GameVersion.C or GameVersion.GSC) + { + if (c2.CaughtData is not 0) + return Location == pk.Met_Location; + if (pk.Species == (int)Core.Species.Celebi) + return false; // Cannot reset the Met data + } + return true; + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Enums/EncounterTime.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTime.cs similarity index 100% rename from PKHeX.Core/Legality/Enums/EncounterTime.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTime.cs diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTrade2.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTrade2.cs new file mode 100644 index 000000000..30cd8a80c --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTrade2.cs @@ -0,0 +1,203 @@ +using System; +using static PKHeX.Core.Species; + +namespace PKHeX.Core; + +/// +/// Generation 2 Trade Encounter +/// +public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible +{ + public int Generation => 2; + public EntityContext Context => EntityContext.Gen2; + public int Location => Locations.LinkTrade2NPC; + public GameVersion Version => GameVersion.GSC; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public AbilityPermission Ability => AbilityPermission.OnlyHidden; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + public bool IsFixedNickname => true; + + public byte Form => 0; + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => 100; + + private string[] TrainerNames { get; } + private string[] Nicknames { get; } + + public byte Gender { get; init; } + public byte OTGender { get; init; } + public IndividualValueSet IVs { get; init; } + public ushort Species { get; } + public byte Level { get; } + public ushort TID16 { get; } + + public EncounterTrade2(ReadOnlySpan names, byte index, ushort species, byte level, ushort tid16) + { + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + Species = species; + Level = level; + TID16 = tid16; + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK2 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + // Prefer to generate as Crystal, as it will include met data. + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, GameVersion.C); + var isJapanese = lang == (int)LanguageID.Japanese; + var pk = new PK2(isJapanese) + { + Species = Species, + CurrentLevel = Level, + + Met_Location = Location, + + Nickname = Nicknames[lang], + OT_Name = TrainerNames[lang], + OT_Friendship = PersonalTable.C[Species].BaseFriendship, + }; + + if (IVs.IsSpecified) + { + pk.DV16 = EncounterUtil1.GetDV16(IVs); + pk.OT_Gender = OTGender; + pk.TID16 = TID16; + } + else + { + pk.DV16 = EncounterUtil1.GetRandomDVs(Util.Rand); + pk.TID16 = tr.TID16; + } + + EncounterUtil1.SetEncounterMoves(pk, Version, Level); + + pk.ResetPartyStats(); + + return pk; + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (evo.LevelMax < LevelMin) + return false; + if (pk.Format <= 2) + { + // Gender is tied to IVs. Don't bother checking Gender, just check IVs. + if (IVs.IsSpecified) + { + if (!Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) + return false; + if (pk.TID16 != TID16) + return false; + } + if (pk is ICaughtData2 { CaughtData: not 0 } c) + { + if (c.Met_Location != Locations.LinkTrade2NPC) + return false; + if (c.Met_Level != 0) + return false; + if (IVs.IsSpecified && c.OT_Gender != OTGender) + return false; + } + } + else // 7+ + { + // require male except if transferred from GS + if (pk.VC1 && pk.OT_Gender != 0) + return false; + if (IVs.IsSpecified) + { + if (pk.Gender != Gender) + return false; + if (pk.TID16 != TID16) + return false; + } + } + return true; + } + + private bool IsTrainerNicknameCorrect(PKM pk) + { + var indexOT = GetIndexTrainer(pk.OT_Name, pk); + if (indexOT == -1) + return false; + if (pk.Nickname != Nicknames[indexOT]) + return false; + return true; + } + + private int GetIndexTrainer(ReadOnlySpan OT, PKM pk) + { + if (pk.Japanese) + return OT.SequenceEqual(TrainerNames[1]) ? 1 : -1; + if (pk.Korean) + return OT.SequenceEqual(TrainerNames[(int)LanguageID.Korean]) ? 2 : -1; + + var lang = GetInternationalLanguageID(OT); + if (pk.Format < 7) + return lang; + + if (lang is not (-1 or (int)LanguageID.Spanish)) + return lang; + return SanityCheckTrainerNameIndex(pk, OT, lang); + } + + private int SanityCheckTrainerNameIndex(ILangNick pk, ReadOnlySpan OT, int lang) => Species switch + { + // Can't transfer verbatim with Spanish origin glyphs to French VC. + (int)Voltorb when pk.Language == (int)LanguageID.French && OT is "FALCçN" => (int)LanguageID.Spanish, // FALCÁN + (int)Shuckle when pk.Language == (int)LanguageID.French && OT is "MANôA" => (int)LanguageID.Spanish, // MANÍA + _ => lang, + }; + + private int GetInternationalLanguageID(ReadOnlySpan OT) + { + const int start = (int)LanguageID.English; + const int end = (int)LanguageID.Spanish; + + var tr = TrainerNames; + for (int i = start; i <= end; i++) + { + if (OT.SequenceEqual(tr[i])) + return i; + } + return -1; + } + + // Already required for encounter matching. + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (!IsTrainerNicknameCorrect(pk)) + return EncounterMatchRating.DeferredErrors; + return EncounterMatchRating.Match; + } + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => GetIndexTrainer(trainer, pk) != -1; + + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) + { + var index = GetIndexTrainer(pk.OT_Name, pk); + if (index == -1) + return false; + return nickname.SequenceEqual(Nicknames[index]); + } + + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterGift3Colo.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterGift3Colo.cs new file mode 100644 index 000000000..8d2ddb9ea --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterGift3Colo.cs @@ -0,0 +1,175 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 3 Static Encounter +/// +public sealed record EncounterGift3Colo : IEncounterable, IEncounterMatch, IEncounterConvertible, IRandomCorrelation, IFixedTrainer, IMoveset +{ + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public GameVersion Version { get; } + int ILocation.EggLocation => 0; + int ILocation.Location => Location; + public bool IsShiny => false; + public Shiny Shiny => Shiny.Never; + public byte Form => 0; + public bool EggEncounter => false; + public AbilityPermission Ability => AbilityPermission.Any12; + public Ball FixedBall => Ball.Poke; + public bool IsFixedTrainer => true; + + private readonly string[] TrainerNames; + public ushort Species { get; } + public byte Level { get; } + public required byte Location { get; init; } + public Moveset Moves { get; init; } + public required ushort TID16 { get; init; } + public required byte OT_Gender { get; init; } + + public EncounterGift3Colo(ushort species, byte level, string[] trainers, GameVersion game) + { + Species = species; + Level = level; + TrainerNames = trainers; + Version = game; + } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public bool IsColoStarter => Species is (ushort)Core.Species.Espeon or (ushort)Core.Species.Umbreon; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public CK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public CK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = GetTemplateLanguage(tr); + var pk = new CK3 + { + Species = Species, + CurrentLevel = Level, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = Level, + Version = (byte)Version, + Ball = (byte)Ball.Poke, + + Language = lang, + OT_Name = TrainerNames[lang], + OT_Gender = OT_Gender, + ID32 = TID16, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, Version, Level); + + pk.ResetPartyStats(); + return pk; + } + + private int GetTemplateLanguage(ITrainerInfo tr) => (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + + private void SetPINGA(CK3 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.CXD); + } while (Shiny == Shiny.Never && pk.IsShiny); + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format == 3) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return evo.LevelMax >= Level; + return pk.Met_Level == Level; + } + + private bool IsMatchLocation(PKM pk) + { + if (pk.Format != 3) + return true; // transfer location verified later + + var met = pk.Met_Location; + return Location == met; + } + + private bool IsMatchPartial(PKM pk) + { + if (pk.Ball != (byte)FixedBall) + return true; + return false; + } + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + if (IsColoStarter) + return val is PIDType.CXD_ColoStarter; + return val is PIDType.CXD; + } + + public PIDType GetSuggestedCorrelation() => PIDType.CXD; + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) + { + if ((uint)language >= TrainerNames.Length) + return false; + + var max = language == 1 ? 5 : 7; + var expect = TrainerNames[language].AsSpan(); + + if (pk is CK3 && expect.SequenceEqual(trainer)) + return true; // not yet transferred to mainline Gen3 + + if (expect.Length > max) + expect = expect[..max]; + return expect.SequenceEqual(trainer); + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterShadow3Colo.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterShadow3Colo.cs new file mode 100644 index 000000000..5bc5fe9aa --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterShadow3Colo.cs @@ -0,0 +1,215 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Shadow Pokémon Encounter found in +/// +/// Initial Shadow Gauge value. +/// Initial Shadow Gauge value. +/// Team Specification with required , and Gender. +// ReSharper disable NotAccessedPositionalProperty.Global +public sealed record EncounterShadow3Colo(byte ID, short Gauge, ReadOnlyMemory PartyPrior) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IShadow3, IMoveset, IRandomCorrelation +{ + // ReSharper restore NotAccessedPositionalProperty.Global + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public GameVersion Version => GameVersion.COLO; + int ILocation.EggLocation => 0; + int ILocation.Location => Location; + public bool IsShiny => false; + public bool EggEncounter => false; + public Shiny Shiny => Shiny.Random; + public AbilityPermission Ability => AbilityPermission.Any12; + public Ball FixedBall => Ball.None; + public byte Form => 0; + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public required byte Location { get; init; } + public required Moveset Moves { get; init; } + + public string Name => "Shadow Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + /// + /// Originates from the EReader scans (Japanese Only) + /// + public bool EReader => Location == 128; // @ Card e Room (Japanese games only) + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public CK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public CK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = GetTemplateLanguage(tr); + var pk = new CK3 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)GameVersion.CXD, + Ball = (byte)Ball.Poke, + + Language = lang, + OT_Name = tr.Language == lang ? tr.OT : lang == 1 ? "ゲーフリ" : "GF", + OT_Gender = 0, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + + // Fake as Purified + RibbonNational = true, + }; + + SetPINGA(pk, criteria); + pk.SetMoves(Moves); + + pk.ResetPartyStats(); + return pk; + } + + private int GetTemplateLanguage(ITrainerInfo tr) => EReader ? 1 : (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + if (!EReader) + SetPINGA_Regular(pk, criteria); + else + SetPINGA_EReader(pk); + } + + private void SetPINGA_Regular(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + int ability = criteria.GetAbilityFromNumber(0); + + // Ensure that any generated specimen has valid Shadow Locks + // This can be kinda slow, depending on how many locks / how strict they are. + // Cancel this operation if too many attempts are made to prevent infinite loops. + int ctr = 0; + const int max = 100_000; + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.CXD); + var pidiv = MethodFinder.Analyze(pk); + var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); + if (result) + break; + } + while (++ctr <= max); + + System.Diagnostics.Debug.Assert(ctr < 100_000); + } + + private void SetPINGA_EReader(PKM pk) + { + // E-Reader have all IVs == 0 + for (int i = 0; i < 6; i++) + pk.SetIV(i, 0); + + // All E-Reader shadows are actually nature/gender locked. + var locked = PartyPrior.Span[0].Locks[^1]; + var (nature, gender) = locked.GetLock; + + // Ensure that any generated specimen has valid Shadow Locks + // This can be kinda slow, depending on how many locks / how strict they are. + // Cancel this operation if too many attempts are made to prevent infinite loops. + int ctr = 0; + const int max = 100_000; + do + { + var seed = Util.Rand32(); + PIDGenerator.SetValuesFromSeedXDRNG_EReader(pk, seed); + if (pk.Nature != nature || pk.Gender != gender) + continue; + var pidiv = new PIDIV(PIDType.CXD, seed); + var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); + if (result) + break; + } + while (++ctr <= max); + +#if DEBUG + System.Diagnostics.Debug.Assert(ctr < 100_000); +#endif + } + + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private bool IsMatchPartial(PKM pk) + { + if (pk.FatefulEncounter) + return true; + return FixedBall != Ball.None && pk.Ball != (byte)FixedBall; + } + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format == 3) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return evo.LevelMax >= Level; + return pk.Met_Level == Level; + } + + private bool IsMatchLocation(PKM pk) + { + if (pk.Format != 3) + return true; // transfer location verified later + return pk.Met_Location == Location; + } + + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + if (EReader) + return true; + return val is PIDType.CXD; + } + + public PIDType GetSuggestedCorrelation() + { + if (EReader) + return PIDType.None; + return PIDType.CXD; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterStatic3Colo.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterStatic3Colo.cs new file mode 100644 index 000000000..e214e48ca --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterStatic3Colo.cs @@ -0,0 +1,149 @@ +namespace PKHeX.Core; + +/// +/// Generation 3 Static Encounter +/// +public sealed record EncounterStatic3Colo(ushort Species, byte Level) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFixedGender, IRandomCorrelation +{ + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public GameVersion Version => GameVersion.COLO; + int ILocation.EggLocation => 0; + int ILocation.Location => Location; + public bool IsShiny => false; + public Shiny Shiny => Shiny.Never; + public byte Form => 0; + public bool EggEncounter => false; + + public AbilityPermission Ability => AbilityPermission.Any12; + + public Ball FixedBall => Ball.Poke; + public byte Location => 254; + public sbyte Gender => 0; + public required Moveset Moves { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public bool IsColoStarter => Species is (ushort)Core.Species.Espeon or (ushort)Core.Species.Umbreon; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public CK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public CK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = GetTemplateLanguage(tr); + var pk = new CK3 + { + Species = Species, + CurrentLevel = Level, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = Level, + Version = (byte)GameVersion.CXD, + Ball = (byte)Ball.Poke, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = 0, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, Version, Level); + + pk.ResetPartyStats(); + return pk; + } + + private int GetTemplateLanguage(ITrainerInfo tr) => (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + + private void SetPINGA(CK3 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + const PIDType type = PIDType.CXD; + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, type); + } while (Shiny == Shiny.Never && pk.IsShiny); + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format == 3) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return evo.LevelMax >= Level; + return pk.Met_Level == Level; + } + + private bool IsMatchLocation(PKM pk) + { + if (pk.Format != 3) + return true; // transfer location verified later + + var met = pk.Met_Location; + return Location == met; + } + + private bool IsMatchPartial(PKM pk) + { + if (pk.Ball != (byte)FixedBall) + return true; + return false; + } + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + if (IsColoStarter) + return val is PIDType.CXD_ColoStarter; + return val is PIDType.CXD; + } + + public PIDType GetSuggestedCorrelation() => PIDType.CXD; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/IShadow3.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/IShadow3.cs new file mode 100644 index 000000000..ffa51fd6c --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/IShadow3.cs @@ -0,0 +1,9 @@ +using System; + +namespace PKHeX.Core; + +public interface IShadow3 +{ + GameVersion Version { get; } + ReadOnlyMemory PartyPrior { get; } +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea3.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterArea3.cs similarity index 63% rename from PKHeX.Core/Legality/Areas/EncounterArea3.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterArea3.cs index 779981382..6798df09b 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea3.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterArea3.cs @@ -1,17 +1,21 @@ using System; -using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -/// /// /// encounter area /// -public sealed record EncounterArea3 : EncounterArea +public sealed record EncounterArea3 : IEncounterArea, ISlotRNGType, IAreaLocation { - public readonly int Rate; - public readonly EncounterSlot3[] Slots; + public EncounterSlot3[] Slots { get; } + public GameVersion Version { get; } + public SlotType Type { get; } + + public readonly byte Rate; + public readonly byte Location; + + public bool IsMatchLocation(int location) => location == Location; public static EncounterArea3[] GetAreas(BinLinkerAccessor input, GameVersion game) { @@ -29,20 +33,22 @@ public sealed record EncounterArea3 : EncounterArea return result; } - private EncounterArea3(ReadOnlySpan data, GameVersion game) : base(game) + private EncounterArea3(ReadOnlySpan data, GameVersion game) { - Location = ReadInt16LittleEndian(data); + Location = data[0]; Type = (SlotType)data[2]; Rate = data[3]; + Version = game; Slots = ReadRegularSlots(data); } - private EncounterArea3(ReadOnlySpan data, GameVersion game, SlotType type) : base(game) + private EncounterArea3(ReadOnlySpan data, GameVersion game, SlotType type) { - Location = ReadInt16LittleEndian(data); + Location = data[0]; Type = type; Rate = data[3]; + Version = game; Slots = ReadSwarmSlots(data); } @@ -109,53 +115,4 @@ public sealed record EncounterArea3 : EncounterArea return new EncounterSlot3Swarm(this, species, min, max, slotNum, moves); } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - if (pk.Format != 3) // Met Location and Met Level are changed on PK3->PK4 - return GetSlotsFuzzy(chain); - if (pk.Met_Location != Location) - return Array.Empty(); - return GetSlotsMatching(chain, pk.Met_Level); - } - - private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - break; - if (!slot.IsLevelWithinRange(lvl)) - break; - - yield return slot; - break; - } - } - } - - private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - break; - if (slot.LevelMin > evo.LevelMax) - break; - - yield return slot; - break; - } - } - } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3.cs new file mode 100644 index 000000000..3c0b87ec0 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3.cs @@ -0,0 +1,126 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public record EncounterSlot3(EncounterArea3 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax, byte SlotNumber, byte MagnetPullIndex, byte MagnetPullCount, byte StaticIndex, byte StaticCount) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMagnetStatic, INumberedSlot, ISlotRNGType, IRandomCorrelation +{ + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public bool EggEncounter => false; + public Ball FixedBall => GetRequiredBall(); + + public AbilityPermission Ability => AbilityPermission.Any12; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + + private Ball GetRequiredBall(Ball fallback = Ball.None) => Locations.IsSafariZoneLocation3(Location) ? Ball.Safari : fallback; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PK3 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + Ball = (byte)GetRequiredBall(Ball.Poke), + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + SetEncounterMoves(pk); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PK3 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + if (Species == (int)Core.Species.Unown) + { + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.Method_1_Unown); + ability ^= 1; // some nature-forms cannot have a certain PID-ability set, so just flip it as Unown doesn't have dual abilities. + } while (pk.Form != Form); + } + else + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.Method_1); + } + } + + protected virtual void SetEncounterMoves(PKM pk) => EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Form != evo.Form && Species is not (int)Core.Species.Burmy) + return false; + + if (pk.Format == 3) + { + // Must match level exactly. + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + } + else + { + if (evo.LevelMax < LevelMin) + return false; + } + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsDeferredSafari3(pk.Ball == (int)Ball.Safari)) + return EncounterMatchRating.PartialMatch; + if (IsDeferredWurmple(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + private bool IsDeferredWurmple(PKM pk) => Species == (int)Core.Species.Wurmple && pk.Species != (int)Core.Species.Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk); + + private bool IsDeferredSafari3(bool IsSafariBall) => IsSafariBall != Locations.IsSafariZoneLocation3(Location); + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + if (Species != (int)Core.Species.Unown) + return val is (PIDType.Method_1 or PIDType.Method_2 or PIDType.Method_3 or PIDType.Method_4); + return val is (PIDType.Method_1_Unown or PIDType.Method_2_Unown or PIDType.Method_3_Unown or PIDType.Method_4_Unown); + } + + public PIDType GetSuggestedCorrelation() => Species == (int)Core.Species.Unown ? PIDType.Method_1_Unown : PIDType.Method_1; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3Swarm.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3Swarm.cs new file mode 100644 index 000000000..3702130be --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3Swarm.cs @@ -0,0 +1,17 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +/// Handled differently as these slots have fixed moves that are different from their normal level-up moves. +/// +internal sealed record EncounterSlot3Swarm(EncounterArea3 Parent, ushort Species, byte LevelMin, byte LevelMax, byte SlotNumber, Moveset Moves) + : EncounterSlot3(Parent, Species, 0, LevelMin, LevelMax, SlotNumber, 0, 0, 0, 0), IMoveset +{ + protected override void SetEncounterMoves(PKM pk) + { + pk.SetMoves(Moves); + pk.SetMaximumPPCurrent(Moves); + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterStatic3.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterStatic3.cs new file mode 100644 index 000000000..ddfced914 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterStatic3.cs @@ -0,0 +1,223 @@ +namespace PKHeX.Core; + +/// +/// Generation 3 Static Encounter +/// +public sealed record EncounterStatic3(ushort Species, byte Level, GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFatefulEncounterReadOnly, IRandomCorrelation, IMoveset +{ + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public bool Roaming { get; init; } + int ILocation.EggLocation => 0; + int ILocation.Location => Location; + public bool IsShiny => false; + private bool Gift => FixedBall == Ball.Poke; + public Shiny Shiny => Shiny.Random; + + public AbilityPermission Ability => AbilityPermission.Any12; + + public Ball FixedBall { get; init; } + public bool FatefulEncounter { get; init; } + + public required byte Location { get; init; } + public byte Form { get; init; } + public bool EggEncounter { get; init; } + public Moveset Moves { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = GetTemplateLanguage(tr); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PK3 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)version, + Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke), + FatefulEncounter = FatefulEncounter, + + Language = lang, + OT_Name = tr.Language == lang ? tr.OT : lang == 1 ? "ゲーフリ" : "GF", + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + if (EggEncounter) + { + // Fake as hatched. + pk.Met_Level = EggStateLegality.EggMetLevel34; + pk.Met_Location = version is GameVersion.FR or GameVersion.LG + ? Locations.HatchLocationFRLG + : Locations.HatchLocationRSE; + } + + SetPINGA(pk, criteria); + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + + pk.ResetPartyStats(); + return pk; + } + + private int GetTemplateLanguage(ITrainerInfo tr) + { + // Old Sea Map was only distributed to Japanese games. + if (Species is (ushort)Core.Species.Mew) + return (int)LanguageID.Japanese; + + // Deoxys for Emerald was not available for Japanese games. + if (Species is (ushort)Core.Species.Deoxys && tr.Language == 1) + return (int)LanguageID.English; + + return (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + } + + private void SetPINGA(PK3 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + if (Species == (int)Core.Species.Unown) + { + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.Method_1_Unown); + ability ^= 1; // some nature-forms cannot have a certain PID-ability set, so just flip it as Unown doesn't have dual abilities. + } while (pk.Form != Form); + } + else + { + PIDType type = this switch + { + { Roaming: true, Version: not GameVersion.E } => PIDType.Method_1_Roamer, + _ => PIDType.Method_1, + }; + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, type); + } while (Shiny == Shiny.Never && pk.IsShiny); + } + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + private bool IsDeferredWurmple(PKM pk) => Species == (int)Core.Species.Wurmple && pk.Species != (int)Core.Species.Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk); + + private bool IsDeferredSafari3(bool IsSafariBall) => IsSafariBall != Locations.IsSafariZoneLocation3(Location); + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format == 3) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return evo.LevelMax >= Level; + if (!EggEncounter) + return pk.Met_Level == Level; + return pk is { Met_Level: EggStateLegality.EggMetLevel34, CurrentLevel: >= 5 }; // met level 0, origin level 5 + } + + private bool IsMatchLocation(PKM pk) + { + if (pk.Format != 3) + return true; // transfer location verified later + + if (EggEncounter) + return !pk.IsEgg || pk.Met_Location == Location; + + var met = pk.Met_Location; + if (!Roaming) + return Location == met; + + // Route 101-138 + if (Version <= GameVersion.E) + return met is >= 16 and <= 49; + // Route 1-25 encounter is possible either in grass or on water + return met is >= 101 and <= 125; + } + + private bool IsMatchPartial(PKM pk) + { + if (IsDeferredSafari3(pk.Ball == (int)Ball.Safari)) + return true; + if (IsDeferredWurmple(pk)) + return true; + if (Gift && pk.Ball != (byte)FixedBall) + return true; + if (FatefulEncounter != pk.FatefulEncounter) + return true; + return false; + } + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + var version = pk.Version; + if (version is (int)GameVersion.E) + return val is PIDType.Method_1; + if (version is (int)GameVersion.FR or(int) GameVersion.LG) + return Roaming ? IsRoamerPIDIV(val, pk) : val is PIDType.Method_1; + // RS, roamer glitch && RSBox s/w emulation => method 4 available + return Roaming ? IsRoamerPIDIV(val, pk) : val is (PIDType.Method_1 or PIDType.Method_4); + } + + private static bool IsRoamerPIDIV(PIDType val, PKM pk) + { + // Roamer PIDIV is always Method 1. + // M1 is checked before M1R. A M1R PIDIV can also be a M1 PIDIV, so check that collision. + if (PIDType.Method_1_Roamer == val) + return true; + if (PIDType.Method_1 != val) + return false; + + // only 8 bits are stored instead of 32 -- 5 bits HP, 3 bits for ATK. + // return pk.IV32 <= 0xFF; + return pk is { IV_DEF: 0, IV_SPE: 0, IV_SPA: 0, IV_SPD: 0, IV_ATK: <= 7 }; + } + + public PIDType GetSuggestedCorrelation() => PIDType.Method_1; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterTrade3.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterTrade3.cs new file mode 100644 index 000000000..797aaab9b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterTrade3.cs @@ -0,0 +1,177 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 3 Trade Encounter +/// +public sealed record EncounterTrade3 : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible, IContestStatsReadOnly +{ + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public int Location => Locations.LinkTrade3NPC; + public Shiny Shiny => Shiny.FixedValue; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + public bool IsFixedNickname => true; + + private string[] TrainerNames { get; } + private string[] Nicknames { get; } + + public required AbilityPermission Ability { get; init; } + public required byte Gender { get; init; } + public required byte OTGender { get; init; } + public required IndividualValueSet IVs { get; init; } + public ushort Species { get; } + public byte Form => 0; + public byte Level { get; } + public GameVersion Version { get; } + + /// + /// Fixed value the encounter must have. + /// + public uint PID { get; } + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => 100; + + public required ushort TID16 { get; init; } + public ushort SID16 { get; init; } + + public byte CNT_Cool { get; private init; } + public byte CNT_Beauty { get; private init; } + public byte CNT_Cute { get; private init; } + public byte CNT_Smart { get; private init; } + public byte CNT_Tough { get; private init; } + public byte CNT_Sheen { get; private init; } + + public required ReadOnlySpan Contest + { + init + { + CNT_Cool = value[0]; + CNT_Beauty = value[1]; + CNT_Cute = value[2]; + CNT_Smart = value[3]; + CNT_Tough = value[4]; + CNT_Sheen = value[5]; + } + } + + public EncounterTrade3(ReadOnlySpan names, byte index, GameVersion game, uint pid, ushort species, byte level) + { + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + Version = game; + PID = pid; + Species = species; + Level = level; + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK3 + { + Species = Species, + CurrentLevel = Level, + + Met_Location = Location, + Met_Level = Level, + Version = (byte)version, + Ball = (byte)FixedBall, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Language = lang, + OT_Gender = OTGender, + TID16 = TID16, + SID16 = SID16, + }; + + // Italian LG Jynx untranslated from English name + if (Species == (int)Core.Species.Jynx && version == GameVersion.LG && lang == (int)LanguageID.Italian) + lang = 2; + pk.Nickname = Nicknames[lang]; + pk.OT_Name = TrainerNames[lang]; + + EncounterUtil1.SetEncounterMoves(pk, Version, Level); + SetPINGA(pk); + + pk.ResetPartyStats(); + this.CopyContestStatsTo(pk); + + return pk; + } + + private void SetPINGA(PK3 pk) + { + pk.PID = PID; + pk.SetRandomIVsTemplate(IVs, 0); + pk.RefreshAbility((int)Ability >> 1); + // Nature and Gender are derived from PID. + } + + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.EncryptionConstant != PID) + return false; + if (!Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + if (pk.TID16 != TID16) + return false; + if (pk.SID16 != SID16) + return false; + if (evo.LevelMax < Level) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.Gender != Gender) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (pk.Egg_Location != 0) + return false; + if (pk is IContestStatsReadOnly s && s.IsContestBelow(this)) + return false; + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) + { + if (Species == (int)Core.Species.Jynx && pk.Version == (int)GameVersion.LG && language == (int)LanguageID.Italian) + language = 2; + return language != 0 && (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + } + + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) + { + if (Species == (int)Core.Species.Jynx && pk.Version == (int)GameVersion.LG && language == (int)LanguageID.Italian) + language = 2; + return language != 0 && (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + } + + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterArea3XD.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterArea3XD.cs new file mode 100644 index 000000000..e599c3e36 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterArea3XD.cs @@ -0,0 +1,25 @@ +namespace PKHeX.Core; + +/// +/// encounter area +/// +public sealed record EncounterArea3XD : IVersion, IEncounterArea, IAreaLocation +{ + public EncounterSlot3XD[] Slots { get; } + public SlotType Type => SlotType.Grass; + public GameVersion Version => GameVersion.XD; + public readonly byte Location; + + public bool IsMatchLocation(int location) => location == Location; + + public EncounterArea3XD(byte loc, ushort s0, byte l0, ushort s1, byte l1, ushort s2, byte l2) + { + Location = loc; + Slots = new[] + { + new EncounterSlot3XD(this, s0, 10, l0, 0), + new EncounterSlot3XD(this, s1, 10, l1, 1), + new EncounterSlot3XD(this, s2, 10, l2, 2), + }; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterShadow3XD.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterShadow3XD.cs new file mode 100644 index 000000000..ba08ce677 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterShadow3XD.cs @@ -0,0 +1,168 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Shadow Pokémon Encounter found in +/// +/// Initial Shadow Gauge value. +/// Initial Shadow Gauge value. +/// Team Specification with required , and Gender. +// ReSharper disable NotAccessedPositionalProperty.Global +public sealed record EncounterShadow3XD(byte ID, short Gauge, ReadOnlyMemory PartyPrior) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IShadow3, IFatefulEncounterReadOnly, IMoveset, IRandomCorrelation +{ + // ReSharper restore NotAccessedPositionalProperty.Global + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public GameVersion Version => GameVersion.XD; + int ILocation.EggLocation => 0; + int ILocation.Location => Location; + public bool IsShiny => false; + public bool EggEncounter => false; + public Shiny Shiny => Shiny.Never; // Different from Colosseum! + public AbilityPermission Ability => AbilityPermission.Any12; + public bool FatefulEncounter => true; + public byte Form => 0; + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public required byte Location { get; init; } + public Ball FixedBall { get; init; } = Ball.None; + public required Moveset Moves { get; init; } + + public string Name => "Shadow Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public XK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new XK3 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)GameVersion.CXD, + Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke), + FatefulEncounter = FatefulEncounter, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = 0, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + + // Fake as Purified + RibbonNational = true, + }; + + SetPINGA(pk, criteria); + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, Version, Level); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + int ability = criteria.GetAbilityFromNumber(0); + + // Ensure that any generated specimen has valid Shadow Locks + // This can be kinda slow, depending on how many locks / how strict they are. + // Cancel this operation if too many attempts are made to prevent infinite loops. + int ctr = 0; + const int max = 100_000; + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.CXD); + var pidiv = MethodFinder.Analyze(pk); + var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); + if (result) + break; + } + while (++ctr <= max); + + System.Diagnostics.Debug.Assert(ctr < 100_000); + } + + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private bool IsMatchPartial(PKM pk) + { + if (!pk.FatefulEncounter) + return true; + return FixedBall != Ball.None && pk.Ball != (byte)FixedBall; + } + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format == 3) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return evo.LevelMax >= Level; + return pk.Met_Level == Level; + } + + private bool IsMatchLocation(PKM pk) + { + if (pk.Format != 3) + return true; // transfer location verified later + + var met = pk.Met_Location; + if (met == Location) + return true; + + // XD can re-battle with Miror B + // Realgam Tower, Rock, Oasis, Cave, Pyrite Town + return Version == GameVersion.XD && met is (59 or 90 or 91 or 92 or 113); + } + + #endregion + + public bool IsCompatible(PIDType val, PKM pk) => val is PIDType.CXD or PIDType.CXDAnti; + public PIDType GetSuggestedCorrelation() => PIDType.CXD; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterSlot3XD.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterSlot3XD.cs new file mode 100644 index 000000000..e99adf3e5 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterSlot3XD.cs @@ -0,0 +1,78 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot3XD(EncounterArea3XD Parent, ushort Species, byte LevelMin, byte LevelMax, byte SlotNumber) + : IEncounterable, IEncounterMatch, IEncounterConvertible, INumberedSlot, IFatefulEncounterReadOnly, IRandomCorrelation +{ + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public bool FatefulEncounter => true; + public bool EggEncounter => false; + public Ball FixedBall => Ball.None; + public AbilityPermission Ability => AbilityPermission.Any12; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + + public byte Form => 0; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public XK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new XK3 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + FatefulEncounter = FatefulEncounter, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)GameVersion.CXD, + Ball = (byte)Ball.Poke, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = 0, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, GameVersion.XD, LevelMin); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(XK3 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + int ability = criteria.GetAbilityFromNumber(0); + PIDGenerator.SetRandomPokeSpotPID(pk, nature, gender, ability, SlotNumber); + } + + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) => true; // Handled by Area + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + #endregion + + public bool IsCompatible(PIDType val, PKM pk) => val == PIDType.PokeSpot; + public PIDType GetSuggestedCorrelation() => PIDType.PokeSpot; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterStatic3XD.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterStatic3XD.cs new file mode 100644 index 000000000..c2a70a9db --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterStatic3XD.cs @@ -0,0 +1,160 @@ +namespace PKHeX.Core; + +/// +/// Generation 3 Static Encounter +/// +public sealed record EncounterStatic3XD(ushort Species, byte Level) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFatefulEncounterReadOnly, IRandomCorrelation, IMoveset +{ + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public GameVersion Version => GameVersion.XD; + int ILocation.EggLocation => 0; + int ILocation.Location => Location; + public bool IsShiny => false; + private bool Gift => FixedBall == Ball.Poke; + public Shiny Shiny => Shiny.Random; + public AbilityPermission Ability => AbilityPermission.Any12; + + public Ball FixedBall { get; init; } + public bool FatefulEncounter { get; init; } + + public required byte Location { get; init; } + public byte Form => 0; + public bool EggEncounter => false; + public Moveset Moves { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public bool IsColoStarter => Species is (ushort)Core.Species.Espeon or (ushort)Core.Species.Umbreon; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public XK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new XK3 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)GameVersion.CXD, + Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke), + FatefulEncounter = FatefulEncounter, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = 0, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, Version, Level); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(XK3 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + if (Species == (int)Core.Species.Unown) + { + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.Method_1_Unown); + ability ^= 1; // some nature-forms cannot have a certain PID-ability set, so just flip it as Unown doesn't have dual abilities. + } while (pk.Form != Form); + } + else + { + const PIDType type = PIDType.CXD; + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, type); + } while (Shiny == Shiny.Never && pk.IsShiny); + } + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format == 3) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return evo.LevelMax >= Level; + return pk.Met_Level == Level; + } + + private bool IsMatchLocation(PKM pk) + { + if (pk.Format != 3) + return true; // transfer location verified later + + var met = pk.Met_Location; + return Location == met; + } + + private bool IsMatchPartial(PKM pk) + { + if (Gift && pk.Ball != (byte)FixedBall) + return true; + return false; + } + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + if (IsColoStarter) + return val is PIDType.CXD_ColoStarter; + if (val is PIDType.CXD) + return true; + return val is PIDType.CXDAnti && FatefulEncounter; + } + + public PIDType GetSuggestedCorrelation() => PIDType.CXD; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterTrade3XD.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterTrade3XD.cs new file mode 100644 index 000000000..5f1542533 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterTrade3XD.cs @@ -0,0 +1,186 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 3 Static Encounter +/// +public sealed record EncounterTrade3XD : IEncounterable, IEncounterMatch, IEncounterConvertible, IRandomCorrelation, IFixedTrainer, IFixedNickname, IFatefulEncounterReadOnly, IMoveset +{ + public int Generation => 3; + public EntityContext Context => EntityContext.Gen3; + public GameVersion Version => GameVersion.XD; + int ILocation.EggLocation => 0; + int ILocation.Location => Location; + public bool IsShiny => false; + private bool Gift => FixedBall == Ball.Poke; + public Shiny Shiny => Shiny.Random; + public AbilityPermission Ability => AbilityPermission.Any12; + public bool FatefulEncounter => true; + + public bool IsFixedTrainer => true; + public bool IsFixedNickname => Nicknames.Length > 0; + public ushort Species { get; } + public byte Level { get; } + + public Ball FixedBall => Ball.Poke; + + public required byte Location { get; init; } + public byte Form => 0; + public bool EggEncounter => false; + public required Moveset Moves { get; init; } + public required ushort TID16 { get; init; } + // SID: Based on player ID + + private readonly string[] TrainerNames; + + private readonly string[] Nicknames; + + public EncounterTrade3XD(ushort species, byte level, string[] trainer) : this(species, level, trainer, Array.Empty()) { } + + public EncounterTrade3XD(ushort species, byte level, string[] trainer, string[] nicknames) + { + Species = species; + Level = level; + TrainerNames = trainer; + Nicknames = nicknames; + } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public XK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = GetTemplateLanguage(tr); + var pk = new XK3 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.E[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = Level, + Version = (byte)GameVersion.CXD, + Ball = (byte)Ball.Poke, + + Language = lang, + OT_Name = TrainerNames[lang], + OT_Gender = 0, + TID16 = TID16, + SID16 = tr.SID16, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, Version, Level); + + pk.ResetPartyStats(); + return pk; + } + + private int GetTemplateLanguage(ITrainerInfo tr) => (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + + private void SetPINGA(XK3 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + if (Species == (int)Core.Species.Unown) + { + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.Method_1_Unown); + ability ^= 1; // some nature-forms cannot have a certain PID-ability set, so just flip it as Unown doesn't have dual abilities. + } while (pk.Form != Form); + } + else + { + const PIDType type = PIDType.CXD; + do + { + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, type); + } while (Shiny == Shiny.Never && pk.IsShiny); + } + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (pk.TID16 != TID16) // SID is from player! + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private static bool IsMatchEggLocation(PKM pk) + { + if (pk.Format == 3) + return true; + + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return evo.LevelMax >= Level; + return pk.Met_Level == Level; + } + + private bool IsMatchLocation(PKM pk) + { + if (pk.Format != 3) + return true; // transfer location verified later + + var met = pk.Met_Location; + return Location == met; + } + + private bool IsMatchPartial(PKM pk) + { + if (Gift && pk.Ball != (byte)FixedBall) + return true; + return false; + } + #endregion + + public bool IsCompatible(PIDType val, PKM pk) => val is PIDType.CXD; + public PIDType GetSuggestedCorrelation() => PIDType.CXD; + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) + { + if (!IsFixedNickname) + return true; + return nickname.SequenceEqual(GetNickname(language)); + } + + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterArea4.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterArea4.cs new file mode 100644 index 000000000..eca613f5b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterArea4.cs @@ -0,0 +1,112 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// +/// encounter area +/// +public sealed record EncounterArea4 : IEncounterArea, ISlotRNGType, IGroundTypeTile, IAreaLocation +{ + public EncounterSlot4[] Slots { get; } + public GameVersion Version { get; } + public SlotType Type { get; } + public GroundTileAllowed GroundTile { get; } + + public readonly ushort Location; + public readonly byte Rate; + + public bool IsMatchLocation(int location) => location == Location; + + public static EncounterArea4[] GetAreas(BinLinkerAccessor input, GameVersion game) + { + var result = new EncounterArea4[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea4(input[i], game); + return result; + } + + private EncounterArea4(ReadOnlySpan data, GameVersion game) + { + Location = ReadUInt16LittleEndian(data); + Type = (SlotType)data[2]; + Rate = data[3]; + Version = game; + // although GroundTilePermission flags are 32bit, none have values > 16bit. + GroundTile = (GroundTileAllowed)ReadUInt16LittleEndian(data[4..]); + + Slots = ReadRegularSlots(data); + } + + private EncounterSlot4[] ReadRegularSlots(ReadOnlySpan data) + { + const int size = 10; + int count = (data.Length - 6) / size; + var slots = new EncounterSlot4[count]; + for (int i = 0; i < slots.Length; i++) + { + int offset = 6 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadRegularSlot(entry); + } + + return slots; + } + + private EncounterSlot4 ReadRegularSlot(ReadOnlySpan entry) + { + ushort species = ReadUInt16LittleEndian(entry); + byte form = entry[2]; + byte slotNum = entry[3]; + byte min = entry[4]; + byte max = entry[5]; + byte mpi = entry[6]; + byte mpc = entry[7]; + byte sti = entry[8]; + byte stc = entry[9]; + return new EncounterSlot4(this, species, form, min, max, slotNum, mpi, mpc, sti, stc); + } + + public bool IsMunchlaxTree(ITrainerID32 pk) => IsMunchlaxTree(pk, Location); + + private static bool IsMunchlaxTree(ITrainerID32 pk, ushort location) + { + // We didn't encode the honey tree index to the encounter slot resource. + // Check if any of the slot's location doesn't match any of the groupC trees' area location ID. + var trees = SAV4Sinnoh.CalculateMunchlaxTrees(pk.TID16, pk.SID16); + return IsMunchlaxTree(trees, location); + } + + private static bool IsMunchlaxTree(in MunchlaxTreeSet4 trees, ushort location) + { + return LocationID_HoneyTree[trees.Tree1] == location + && LocationID_HoneyTree[trees.Tree2] == location + && LocationID_HoneyTree[trees.Tree3] == location + && LocationID_HoneyTree[trees.Tree4] == location; + } + + private static ReadOnlySpan LocationID_HoneyTree => new byte[] + { + 20, // 00 Route 205 Floaroma + 20, // 01 Route 205 Eterna + 21, // 02 Route 206 + 22, // 03 Route 207 + 23, // 04 Route 208 + 24, // 05 Route 209 + 25, // 06 Route 210 Solaceon + 25, // 07 Route 210 Celestic + 26, // 08 Route 211 + 27, // 09 Route 212 Hearthome + 27, // 10 Route 212 Pastoria + 28, // 11 Route 213 + 29, // 12 Route 214 + 30, // 13 Route 215 + 33, // 14 Route 218 + 36, // 15 Route 221 + 37, // 16 Route 222 + 47, // 17 Valley Windworks + 48, // 18 Eterna Forest + 49, // 19 Fuego Ironworks + 58, // 20 Floaroma Meadow + }; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterSlot4.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterSlot4.cs new file mode 100644 index 000000000..3f65b4e00 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterSlot4.cs @@ -0,0 +1,166 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot4(EncounterArea4 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax, byte SlotNumber, byte MagnetPullIndex, byte MagnetPullCount, byte StaticIndex, byte StaticCount) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMagnetStatic, INumberedSlot, IGroundTypeTile, ISlotRNGType, IEncounterFormRandom, IRandomCorrelation +{ + public int Generation => 4; + public EntityContext Context => EntityContext.Gen4; + public bool EggEncounter => false; + public AbilityPermission Ability => AbilityPermission.Any12; + public Ball FixedBall => GetRequiredBallValue(); + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsRandomUnspecificForm => Form >= EncounterUtil1.FormDynamic; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + public GroundTileAllowed GroundTile => Parent.GroundTile; + + public bool CanUseRadar => Version is not (GameVersion.HG or GameVersion.SS) && GroundTile.HasFlag(GroundTileAllowed.Grass) && !Locations.IsSafariZoneLocation4(Location); + + private Ball GetRequiredBallValue(Ball fallback = Ball.None) + { + if (Type is SlotType.BugContest) + return Ball.Sport; + return Locations.IsSafariZoneLocation4(Location) ? Ball.Safari : fallback; + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK4 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PK4 + { + Species = Species, + Form = GetWildForm(Form), + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.HGSS[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + GroundTile = GroundTile.GetIndex(), + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)GetRequiredBallValue(Ball.Poke), + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + + pk.ResetPartyStats(); + return pk; + } + + private byte GetWildForm(byte form) + { + if (form == EncounterUtil1.FormRandom) // flagged as totally random + return (byte)Util.Rand.Next(PersonalTable.HGSS[Species].FormCount); + return form; + } + + private void SetPINGA(PK4 pk, EncounterCriteria criteria) + { + int ctr = 0; + do + { + SetPINGAInner(pk, criteria); + var pidiv = MethodFinder.Analyze(pk); + var frames = FrameFinder.GetFrames(pidiv, pk); + foreach (var frame in frames) + { + if (frame.IsSlotCompatibile(this, pk)) + return; + } + } while (ctr++ < 10_000); + } + + private void SetPINGAInner(PK4 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.Method_1); + pk.Gender = gender; + } + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Form != evo.Form && Species is not (int)Core.Species.Burmy) + { + // Unown forms are random, not specific form IDs + if (!IsRandomUnspecificForm) + return false; + } + + if (pk.Format == 4) + { + // Must match level exactly. + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + } + else + { + if (evo.LevelMax < LevelMin) + return false; + } + + // A/B/C tables, only Munchlax is a 'C' encounter, and A/B are accessible from any tree. + // C table encounters are only available from 4 trees, which are determined by TID16/SID16 of the save file. + if (Type is SlotType.HoneyTree && Species == (int)Core.Species.Munchlax && !Parent.IsMunchlaxTree(pk)) + return false; + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if ((pk.Ball == (int)Ball.Safari) != Locations.IsSafariZoneLocation4(Location)) + return EncounterMatchRating.PartialMatch; + if ((pk.Ball == (int)Ball.Sport) != (Type == SlotType.BugContest)) + { + // Nincada => Shedinja can wipe the ball back to Poke + if (pk.Species != (int)Core.Species.Shedinja || pk.Ball != (int)Ball.Poke) + return EncounterMatchRating.PartialMatch; + } + if (IsDeferredWurmple(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + private bool IsDeferredWurmple(PKM pk) => Species == (int)Core.Species.Wurmple && pk.Species != (int)Core.Species.Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk); + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + if (val is PIDType.Method_1) + return true; + // Chain shiny with Poké Radar is only possible in DPPt, in grass. Safari Zone does not allow using the Poké Radar + if (val is PIDType.ChainShiny) + return pk.IsShiny && CanUseRadar; + if (val is PIDType.CuteCharm) + return pk.Gender is 0 or 1 && MethodFinder.IsCuteCharm4Valid(this, pk); + return false; + } + + public PIDType GetSuggestedCorrelation() => PIDType.Method_1; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4.cs new file mode 100644 index 000000000..a0918dcf3 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4.cs @@ -0,0 +1,261 @@ +using static PKHeX.Core.GroundTileAllowed; + +namespace PKHeX.Core; + +/// +/// Generation 4 Static Encounter +/// +public sealed record EncounterStatic4(GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMoveset, IGroundTypeTile, IFatefulEncounterReadOnly, IFixedGender, IRandomCorrelation +{ + public int Generation => 4; + public EntityContext Context => EntityContext.Gen4; + int ILocation.Location => Location; + int ILocation.EggLocation => EggLocation; + public bool IsShiny => false; + public bool EggEncounter => EggLocation != 0; + private bool Gift => FixedBall == Ball.Poke; + + public Ball FixedBall { get; init; } + public bool FatefulEncounter { get; init; } + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public required byte Location { get; init; } + public AbilityPermission Ability { get; init; } + public byte Form { get; init; } + public Shiny Shiny { get; init; } + public ushort EggLocation { get; init; } + public sbyte Gender { get; init; } = -1; + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + /// Indicates if the encounter is a Roamer (variable met location) + public bool Roaming { get; init; } + + /// values permitted for the encounter. + public GroundTileAllowed GroundTile { get; init; } = None; + + public ushort HeldItem { get; init; } + public Nature Nature { get; init; } = Nature.Random; + public Moveset Moves { get; init; } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK4 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PK4 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.HGSS[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)version, + GroundTile = GroundTile.GetIndex(), + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke), + FatefulEncounter = FatefulEncounter, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + if (EggEncounter) + { + // Fake as hatched. + pk.Met_Location = version is GameVersion.HG or GameVersion.SS ? Locations.HatchLocationHGSS : Locations.HatchLocationDPPt; + pk.Met_Level = EggStateLegality.EggMetLevel34; + pk.Egg_Location = EggLocation; + pk.EggMetDate = pk.MetDate; + } + + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature); + int ability = criteria.GetAbilityFromNumber(Ability); + + PIDType type = this switch + { + { Shiny: Shiny.Always } => PIDType.ChainShiny, + { Species: (ushort)Core.Species.Pichu } => PIDType.Pokewalker, + _ => PIDType.Method_1, + }; + PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender, type); + pk.StatNature = pk.Nature; + } + + #endregion + + #region Matching + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + private bool IsMatchLocation(PKM pk) + { + // Met location is lost on transfer + if (pk is not G4PKM pk4) + return true; + + var met = pk4.Met_Location; + if (EggEncounter) + return true; + if (!Roaming) + return met == Location; + + return pk4.GroundTile switch + { + GroundTileType.Grass => IsMatchLocationGrass(Location, met), + GroundTileType.Water => IsMatchLocationWater(Location, met), + _ => false, + }; + } + + private bool IsMatchEggLocation(PKM pk) + { + if (!EggEncounter) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + var eggloc = pk.Egg_Location; + // Transferring 4->5 clears Pt/HG/SS location value and keeps Faraway Place + if (pk is not G4PKM pk4) + { + if (eggloc == Locations.LinkTrade4) + return true; + var cmp = Locations.IsPtHGSSLocationEgg(EggLocation) ? Locations.Faraway4 : EggLocation; + return eggloc == cmp; + } + + if (!pk4.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade4; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk4.Met_Location is not (0 or Locations.LinkTrade4)) + return false; + return true; + } + + private static bool IsMatchLocationGrass(int location, int met) => location switch + { + FirstS => IsMatchRoamerLocation(PermitGrassS, met, FirstS), + FirstJ => IsMatchRoamerLocation(PermitGrassJ, met, FirstJ), + FirstH => IsMatchRoamerLocation(PermitGrassH, met, FirstH), + _ => false, + }; + + private static bool IsMatchLocationWater(int location, int met) => location switch + { + FirstS => IsMatchRoamerLocation(PermitWaterS, met, FirstS), + FirstJ => IsMatchRoamerLocation(PermitWaterJ, met, FirstJ), + FirstH => IsMatchRoamerLocation(PermitWaterH, met, FirstH), + _ => false, + }; + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 4) // Met Level lost on PK4=>PK5 + return Level <= evo.LevelMax; + + return pk.Met_Level == (EggEncounter ? 0 : Level); + } + + private bool IsMatchPartial(PKM pk) => Gift && pk.Ball != (byte)FixedBall; + + public static bool IsMatchRoamerLocation(ulong permit, int location, int first) + { + var value = location - first; + if ((uint)value >= 64) + return false; + return (permit & (1ul << value)) != 0; + } + + public static bool IsMatchRoamerLocation(uint permit, int location, int first) + { + var value = location - first; + if ((uint)value >= 32) + return false; + return (permit & (1u << value)) != 0; + } + + // Merged all locations into a bitmask for quick computation. + private const int FirstS = 16; + private const ulong PermitGrassS = 0x2_8033FFFF; // 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, 47, 49, + private const ulong PermitWaterS = 0x2_803E3B9E; // 18, 19, 20, 23, 24, 25, 27, 28, 29, 33, 34, 35, 36, 37, 47, 49, + + private const int FirstJ = 177; + private const uint PermitGrassJ = 0x0003E7FF; // 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 190, 191, 192, 193, 194, + private const uint PermitWaterJ = 0x0001E06E; // 178, 179, 180, 182, 183, 190, 191, 192, 193, + + private const int FirstH = 149; + private const uint PermitGrassH = 0x0AB3FFFF; // 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 169, 170, 172, 174, 176, + private const uint PermitWaterH = 0x0ABC1B28; // 152, 154, 157, 158, 160, 161, 167, 168, 169, 170, 172, 174, 176, + + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + if (Species == (int)Core.Species.Pichu) + return val == PIDType.Pokewalker; + if (Shiny == Shiny.Always) + return val == PIDType.ChainShiny; + if (val is PIDType.Method_1) + return true; + if (val is PIDType.CuteCharm) + return MethodFinder.IsCuteCharm4Valid(this, pk); + return false; + } + + public PIDType GetSuggestedCorrelation() + { + if (Species == (int)Core.Species.Pichu) + return PIDType.Pokewalker; + if (Shiny == Shiny.Always) + return PIDType.ChainShiny; + return PIDType.Method_1; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs new file mode 100644 index 000000000..7b6fdb332 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs @@ -0,0 +1,183 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// +/// Generation 4 Pokéwalker Encounter +/// +public sealed record EncounterStatic4Pokewalker(PokewalkerCourse4 Course) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMoveset, IRandomCorrelation +{ + public int Generation => 4; + public EntityContext Context => EntityContext.Gen4; + public GameVersion Version => GameVersion.HGSS; + + public int Location => Locations.PokeWalker4; + public bool IsShiny => false; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public AbilityPermission Ability => AbilityPermission.Any12; + public Shiny Shiny => Shiny.Never; + public byte Form => 0; + public int EggLocation => 0; + + public ushort Species { get; } + public byte Level { get; } + public byte Gender { get; } + public Moveset Moves { get; } + + public string Name => $"Pokéwalker Encounter ({Course})"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + private EncounterStatic4Pokewalker(ReadOnlySpan data, PokewalkerCourse4 course) : this(course) + { + Species = ReadUInt16LittleEndian(data); + Level = data[2]; + Gender = data[3]; + var move1 = ReadUInt16LittleEndian(data[0x4..]); + var move2 = ReadUInt16LittleEndian(data[0x6..]); + var move3 = ReadUInt16LittleEndian(data[0x8..]); + var move4 = ReadUInt16LittleEndian(data[0xA..]); + Moves = new(move1, move2, move3, move4); + } + + public static EncounterStatic4Pokewalker[] GetAll(ReadOnlySpan data) + { + const int size = 0xC; + var count = data.Length / size; + System.Diagnostics.Debug.Assert(count == 6 * (int)PokewalkerCourse4.MAX_COUNT); + var result = new EncounterStatic4Pokewalker[count]; + for (int i = 0; i < result.Length; i++) + { + var offset = i * size; + var slice = data.Slice(offset, size); + var course = (PokewalkerCourse4)(i / 6); + result[i] = new(slice, course); + } + return result; + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK4 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PK4 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.HGSS[Species].BaseFriendship, + + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)version, + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)FixedBall, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature.Random); + + // Cannot force an ability; nature-gender-trainerID only yield fixed PIDs. + // int ability = criteria.GetAbilityFromNumber(Ability, pi); + + PIDGenerator.SetRandomPIDPokewalker(pk, nature, gender); + criteria.SetRandomIVs(pk); + } + + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (!IsMatchGender(pk)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + private bool IsMatchGender(PKM pk) + { + if (pk.Gender == Gender) + return true; + + // Azurill-F can change to M when evolving in Gen4 (but not in Gen5+) due to Gender Ratio differences. + if (pk.Species != Species && Species == (ushort)Core.Species.Azurill && Gender == 1) + return EntityGender.GetFromPIDAndRatio(pk.PID, 0xBF) == Gender; + + return true; + } + + private static bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLocation(PKM pk) + { + if (pk.Format == 4) + return pk.Met_Location == Location; + return true; // transfer location verified later + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 4) // Met Level lost on PK4=>PK5 + return evo.LevelMax >= Level; + return pk.Met_Level == Level; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + private static bool IsMatchPartial(PKM pk) => pk.Ball != (byte)Ball.Poke; + #endregion + + public bool IsCompatible(PIDType val, PKM pk) + { + if (val is PIDType.Pokewalker) + return true; + + // Pokewalker can sometimes be confused with CuteCharm due to the PID creation routine. Double check if it is okay. + if (val is PIDType.CuteCharm) + return MethodFinder.GetCuteCharmMatch(pk, pk.EncryptionConstant, out _) && MethodFinder.IsCuteCharm4Valid(this, pk); + return false; + } + + public PIDType GetSuggestedCorrelation() => PIDType.Pokewalker; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4PID.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4PID.cs new file mode 100644 index 000000000..036cb90aa --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4PID.cs @@ -0,0 +1,296 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 4 Trade Encounter with a fixed PID value. +/// +public sealed record EncounterTrade4PID + : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible, IContestStatsReadOnly, IMoveset +{ + public int Generation => 4; + public EntityContext Context => EntityContext.Gen4; + public Shiny Shiny => Shiny.FixedValue; + public bool IsFixedNickname => true; + public GameVersion Version { get; } + public bool EggEncounter => false; + public int EggLocation => 0; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public bool IsFixedTrainer => true; + public byte LevelMin => Level; + public byte LevelMax => MetLocation == default ? Level : (byte)100; + + private readonly string[] TrainerNames; + private readonly string[] Nicknames; + + public ushort Species { get; } + public byte Level { get; } + public required AbilityPermission Ability { get; init; } + public required byte OTGender { get; init; } + public required ushort TID16 { get; init; } + public required ushort SID16 { get; init; } + public required byte Gender { get; init; } + public required IndividualValueSet IVs { get; init; } + public Nature Nature => (Nature)(PID % 25); + public byte Form => 0; + private uint ID32 => (uint)(TID16 | (SID16 << 16)); + + public Moveset Moves { get; init; } + + public byte MetLocation { get; init; } + public int Location => MetLocation == default ? Locations.LinkTrade4NPC : MetLocation; + + /// + /// Fixed value the encounter must have. + /// + public readonly uint PID; + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + + public byte CNT_Cool { get; private init; } + public byte CNT_Beauty { get; private init; } + public byte CNT_Cute { get; private init; } + public byte CNT_Smart { get; private init; } + public byte CNT_Tough { get; private init; } + public byte CNT_Sheen => 0; + + public byte Contest + { + init + { + CNT_Cool = value; + CNT_Beauty = value; + CNT_Cute = value; + CNT_Smart = value; + CNT_Tough = value; + //CNT_Sheen = value; + } + } + + public EncounterTrade4PID(ReadOnlySpan names, byte index, GameVersion game, uint pid, ushort species, byte level) + { + Version = game; + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + PID = pid; + Species = species; + Level = level; + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK4 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK4 + { + PID = PID, + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateNDS(), + Gender = Gender, + Nature = (byte)Nature, + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = GetReceivedLanguage(lang, version), + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Friendship = PersonalTable.DP[Species, Form].BaseFriendship, + + IsNicknamed = true, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + + HT_Name = tr.OT, + HT_Gender = tr.Gender, + }; + + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, version, Level); + pk.SetRandomIVsTemplate(IVs, 0); + pk.PID = PID; + pk.Gender = Gender; + pk.RefreshAbility((int)(PID % 2)); + this.CopyContestStatsTo(pk); + pk.ResetPartyStats(); + + return pk; + } + + private int GetReceivedLanguage(int lang, GameVersion game) + { + if (Version == GameVersion.DPPt) + return GetLanguageDPPt(lang, game); + + // HGSS + // Has English Language ID for all except English origin, which is French + if (Species == (int)Core.Species.Pikachu) + return (int)(lang == (int)LanguageID.English ? LanguageID.French : LanguageID.English); + return lang; + } + + private int GetLanguageDPPt(int lang, GameVersion game) + { + // Has German Language ID for all except German origin, which is English + if (Species == (int)Core.Species.Magikarp) + return (int)(lang == (int)LanguageID.German ? LanguageID.English : LanguageID.German); + // All other trades received (DP only): English games have a Japanese language ID instead of English. + if (game is not GameVersion.Pt && lang == (int)LanguageID.English) + return (int)LanguageID.Japanese; + return lang; + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) => (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchLevel(pk, evo)) + return false; + if (pk.ID32 != ID32) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) + return false; + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (pk is IContestStatsReadOnly s && s.IsContestBelow(this)) + return false; + return true; + } + + private bool IsMatchLocation(PKM pk) + { + // Met location is lost on transfer + if (pk is not G4PKM pk4) + return true; + + var met = pk4.Met_Location; + return met == Location; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 4) // Met Level lost on PK4=>PK5 + return evo.LevelMax >= Level; + + if (MetLocation != default) + return pk.Met_Level == Level; + return pk.Met_Level >= LevelMin; + } + + private bool IsMatchNatureGenderShiny(PKM pk) + { + if (pk.EncryptionConstant != PID) + return false; + if ((int)Nature != pk.Nature) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is PB8) + expect = Locations.Default8bNone; + return pk.Egg_Location == expect; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion + + public int DetectOriginalLanguage(PKM pk) + { + int lang = pk.Language; + switch (Species) + { + case (int)Core.Species.Pikachu: // HGSS Pikachu + return DetectTradeLanguageG4SurgePikachu(pk, lang); + case (int)Core.Species.Magikarp: // DPPt Magikarp + return DetectTradeLanguageG4MeisterMagikarp(pk, lang); + } + // DP English origin are Japanese lang. Can't have LanguageID 2 + if (lang != 1 || pk.Version is not ((int)GameVersion.D or (int)GameVersion.P)) + return lang; + + // Since two locales (JPN/ENG) can have the same LanguageID, check which we should be validating with. + ReadOnlySpan ot = pk.OT_Name; + var expect = TrainerNames[1]; + var match = ot.SequenceEqual(expect); + if (!match) + return 2; // verify strings with English locale instead. + return lang; + } + + private int DetectTradeLanguageG4MeisterMagikarp(PKM pk,int currentLanguageID) + { + if (currentLanguageID == (int)LanguageID.English) + return (int)LanguageID.German; + + // All have German, regardless of origin version. + var lang = DetectTradeLanguage(pk.OT_Name, currentLanguageID); + if (lang == (int)LanguageID.English) // possible collision with FR/ES/DE. Check nickname + return pk.Nickname == Nicknames[(int)LanguageID.French] ? (int)LanguageID.French : (int)LanguageID.Spanish; // Spanish is same as English + + return lang; + } + + private int DetectTradeLanguageG4SurgePikachu(PKM pk, int currentLanguageID) + { + if (currentLanguageID == (int)LanguageID.French) + return (int)LanguageID.English; + + // All have English, regardless of origin version. + var lang = DetectTradeLanguage(pk.OT_Name, currentLanguageID); + if (lang == 2) // possible collision with ES/IT. Check nickname + return pk.Nickname == Nicknames[(int)LanguageID.Italian] ? (int)LanguageID.Italian : (int)LanguageID.Spanish; + + return lang; + } + private int DetectTradeLanguage(ReadOnlySpan OT, int currentLanguageID) + { + var names = TrainerNames; + for (int lang = 1; lang < names.Length; lang++) + { + var expect = names[lang]; + var match = OT.SequenceEqual(expect); + if (match) + return lang; + } + return currentLanguageID; + } + + public bool IsIncorrectEnglish(PKM pk) + { + // Localized English forgot to change the Language ID values. + return pk is { Language: (int)LanguageID.English, Version: (int)GameVersion.D or (int)GameVersion.P }; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4RanchGift.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4RanchGift.cs new file mode 100644 index 000000000..e5a605d39 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4RanchGift.cs @@ -0,0 +1,166 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 4 Trade Encounter with a fixed PID value, met location, and version. +/// +public sealed record EncounterTrade4RanchGift + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFatefulEncounterReadOnly, IFixedTrainer, IMoveset +{ + public int Generation => 4; + public EntityContext Context => EntityContext.Gen4; + + /// + /// Fixed value the encounter must have. + /// + public readonly uint PID; + + public int MetLocation { private get; init; } + public int Location => MetLocation; + public Shiny Shiny => FatefulEncounter ? Shiny.Never : Shiny.FixedValue; + public GameVersion Version { get; } + public bool EggEncounter => false; + public int EggLocation { get; init; } + + public Ball FixedBall { get; init; } = Ball.Poke; + public bool IsShiny => false; + public bool IsFixedTrainer => true; + public byte LevelMin => Level; + public byte LevelMax => Level; + public ushort Species { get; } + public byte Level { get; } + + public bool FatefulEncounter { get; } + public required Moveset Moves { get; init; } + public required ushort TID16 { get; init; } + public required ushort SID16 { get; init; } + private uint ID32 => (uint)(TID16 | (SID16 << 16)); + public required byte OTGender { get; init; } + public required byte Gender { get; init; } + public required AbilityPermission Ability { get; init; } + public byte CurrentLevel { get; init; } + public byte Form { get; init; } + + private static readonly string[] TrainerNames = { string.Empty, "ユカリ", "Hayley", "EULALIE", "GIULIA", "EUKALIA", string.Empty, "Eulalia" }; + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + + public EncounterTrade4RanchGift(uint pid, ushort species, byte level) + { + Version = GameVersion.D; + PID = pid; + Species = species; + Level = level; + } + + public EncounterTrade4RanchGift(ushort species, byte level) + { + Version = GameVersion.D; + Species = species; + Level = level; + FatefulEncounter = true; + MetLocation = 3000; + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK4 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var actualLevel = CurrentLevel != default ? CurrentLevel : Level; + var pk = new PK4 + { + Species = Species, + CurrentLevel = actualLevel, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang, + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Friendship = PersonalTable.DP[Species, Form].BaseFriendship, + + IsNicknamed = true, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + + HT_Name = tr.OT, + HT_Gender = tr.Gender, + }; + + EncounterUtil1.SetEncounterMoves(pk, version, actualLevel); + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pid = FatefulEncounter ? Util.Rand32() : PID; + pk.PID = pid; + pk.Nature = (int)(pid % 25); + pk.Gender = Gender; + pk.RefreshAbility((int)(pid % 2)); + criteria.SetRandomIVs(pk); + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (pk.ID32 != ID32) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (pk.IsEgg) + return false; + return true; + } + + private bool IsMatchNatureGenderShiny(PKM pk) + { + if (pk.Gender != Gender) + return false; + if (FatefulEncounter) + return !pk.IsShiny; + return PID == pk.EncryptionConstant; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is PB8) + expect = Locations.Default8bNone; + return pk.Egg_Location == expect; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Enums/GroundTileAllowed.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/GroundTileAllowed.cs similarity index 100% rename from PKHeX.Core/Legality/Enums/GroundTileAllowed.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen4/GroundTileAllowed.cs diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/IGroundTypeTile.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/IGroundTypeTile.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/EncounterSlot/IGroundTypeTile.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen4/IGroundTypeTile.cs diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/PokewalkerCourse4.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/PokewalkerCourse4.cs new file mode 100644 index 000000000..3aae19211 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/PokewalkerCourse4.cs @@ -0,0 +1,33 @@ +namespace PKHeX.Core; + +public enum PokewalkerCourse4 : byte +{ + RefreshingField = 0, + NoisyForest = 1, + RuggedRoad = 2, + BeautifulBeach = 3, + SuburbanArea = 4, + DimCave = 5, + BlueLake = 6, + TownOutskirts = 7, + HoennField = 8, + WarmBeach = 9, + VolcanoPath = 10, + Treehouse = 11, + ScaryCave = 12, + SinnohField = 13, + IcyMountainRoad = 14, + BigForest = 15, + WhiteLake = 16, + StormyBeach = 17, + Resort = 18, + QuietCave = 19, + BeyondTheSea = 20, + NightSkysEdge = 21, + YellowForest = 22, + Rally = 23, // JPN Exclusive + Sightseeing = 24, // JPN/KOR Exclusive + WinnersPath = 25, + AmityMeadow = 26, // JPN Exclusive + MAX_COUNT = 27, +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/DreamWorldEntry.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/DreamWorldEntry.cs new file mode 100644 index 000000000..710a33652 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/DreamWorldEntry.cs @@ -0,0 +1,45 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Intermediary Representation of Dream World Data +/// +public readonly record struct DreamWorldEntry(ushort Species, byte Level, ushort Move1 = 0, ushort Move2 = 0, ushort Move3 = 0, byte Form = 0, sbyte Gender = -1) +{ + private int EntryCount => Move1 == 0 ? 1 : Move2 == 0 ? 1 : Move3 == 0 ? 2 : 3; + + private void AddTo(GameVersion game, Span result, ref int ctr) + { + var p = PersonalTable.B2W2[Species]; + var a = p.HasHiddenAbility ? AbilityPermission.OnlyHidden : AbilityPermission.OnlyFirst; + if (Move1 == 0) + { + result[ctr++] = new EncounterStatic5Entree(game, Species, Level, Form, Gender, a); + return; + } + + result[ctr++] = new EncounterStatic5Entree(game, Species, Level, Form, Gender, a, Move1); + if (Move2 == 0) + return; + result[ctr++] = new EncounterStatic5Entree(game, Species, Level, Form, Gender, a, Move2); + if (Move3 == 0) + return; + result[ctr++] = new EncounterStatic5Entree(game, Species, Level, Form, Gender, a, Move3); + } + + public static EncounterStatic5Entree[] GetArray(GameVersion game, ReadOnlySpan t) + { + // Split encounters with multiple permitted special moves -- a pk can only be obtained with 1 of the special moves! + int count = 0; + foreach (var e in t) + count += e.EntryCount; + var result = new EncounterStatic5Entree[count]; + + int ctr = 0; + var tmp = result.AsSpan(); + foreach (var s in t) + s.AddTo(game, tmp, ref ctr); + return result; + } +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea5.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterArea5.cs similarity index 59% rename from PKHeX.Core/Legality/Areas/EncounterArea5.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterArea5.cs index b16dc9a54..8e884d3af 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea5.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterArea5.cs @@ -1,16 +1,20 @@ using System; -using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -/// /// /// encounter area /// -public sealed record EncounterArea5 : EncounterArea +public sealed record EncounterArea5 : IEncounterArea, IAreaLocation { - public readonly EncounterSlot5[] Slots; + public EncounterSlot5[] Slots { get; } + public GameVersion Version { get; } + + public readonly ushort Location; + public readonly SlotType Type; + + public bool IsMatchLocation(int location) => Location == location; public static EncounterArea5[] GetAreas(BinLinkerAccessor input, GameVersion game) { @@ -20,10 +24,11 @@ public sealed record EncounterArea5 : EncounterArea return result; } - private EncounterArea5(ReadOnlySpan data, GameVersion game) : base(game) + private EncounterArea5(ReadOnlySpan data, GameVersion game) { Location = ReadUInt16LittleEndian(data); Type = (SlotType)data[2]; + Version = game; Slots = ReadSlots(data); } @@ -52,27 +57,4 @@ public sealed record EncounterArea5 : EncounterArea byte max = entry[3]; return new EncounterSlot5(this, species, form, min, max); } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(pk.Met_Level)) - break; - - // Deerling and Sawsbuck can change forms when seasons change, thus can be any of the [0,3] form values. - // no other wild forms can change - if (slot.Form != evo.Form && slot.Species is not ((int)Species.Deerling or (int)Species.Sawsbuck)) - break; - - yield return slot; - break; - } - } - } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterSlot5.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterSlot5.cs new file mode 100644 index 000000000..49d1d0d98 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterSlot5.cs @@ -0,0 +1,116 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot5(EncounterArea5 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax) + : IEncounterable, IEncounterMatch, IEncounterConvertible +{ + public int Generation => 5; + public EntityContext Context => EntityContext.Gen5; + public bool EggEncounter => false; + public Ball FixedBall => Ball.None; + public Shiny Shiny => IsHiddenGrotto ? Shiny.Never : Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + + public bool IsHiddenGrotto => Type == SlotType.HiddenGrotto; + + private HiddenAbilityPermission IsHiddenAbilitySlot() => Type == SlotType.HiddenGrotto ? HiddenAbilityPermission.Always : HiddenAbilityPermission.Never; + + public AbilityPermission Ability => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => AbilityPermission.Any12, + HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden, + _ => AbilityPermission.Any12H, + }; + + private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => IsHidden, + HiddenAbilityPermission.Always => !IsHidden, + _ => false, + }; + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK5 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PK5 + { + Species = Species, + Form = GetWildForm(Form), + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.B2W2[Species].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + Ball = (byte)Ball.Poke, + MetDate = EncounterDate.GetDateNDS(), + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + pk.ResetPartyStats(); + return pk; + } + + private byte GetWildForm(byte form) + { + if (form != EncounterUtil1.FormRandom) + return form; + // flagged as totally random + return (byte)Util.Rand.Next(PersonalTable.B2W2[Species].FormCount); + } + + private void SetPINGA(PK5 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + PIDGenerator.SetRandomWildPID5(pk, nature, ability, gender); + } + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + + // Deerling and Sawsbuck can change forms when seasons change, thus can be any of the [0,3] form values. + // no other wild forms can change + if (evo.Form != Form && Species is not ((int)Core.Species.Deerling or (int)Core.Species.Sawsbuck)) + return false; + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + if (IsDeferredHiddenAbility(isHidden)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5.cs new file mode 100644 index 000000000..c4dcddf86 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5.cs @@ -0,0 +1,199 @@ +namespace PKHeX.Core; + +/// +/// Generation 5 Static Encounter +/// +public sealed record EncounterStatic5(GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFixedGender +{ + public int Generation => 5; + public EntityContext Context => EntityContext.Gen5; + public bool Roaming { get; init; } + int ILocation.Location => Location; + int ILocation.EggLocation => EggLocation; + public bool IsShiny => false; + public bool EggEncounter => EggLocation != 0; + private bool Gift => FixedBall == Ball.Poke; + + public Ball FixedBall { get; init; } + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public required byte Location { get; init; } + public AbilityPermission Ability { get; init; } + public byte Form { get; init; } + public Shiny Shiny { get; init; } + public ushort EggLocation { get; init; } + public sbyte Gender { get; init; } = -1; + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + public bool IsWildCorrelationPID => !Roaming && Shiny == Shiny.Random && Species != (int)Core.Species.Crustle && !Gift && Ability != AbilityPermission.OnlyHidden; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK5 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK5 + { + Species = Species, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)(FixedBall is Ball.None ? Ball.Poke : FixedBall), + + ID32 = tr.ID32, + Version = (byte)version, + Language = lang, + OT_Gender = tr.Gender, + OT_Name = tr.OT, + + OT_Friendship = PersonalTable.B2W2[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + if (EggEncounter) + { + // Fake as hatched. + pk.Met_Location = Locations.HatchLocation5; + pk.Met_Level = EggStateLegality.EggMetLevel; + pk.Egg_Location = EggLocation; + pk.EggMetDate = pk.MetDate; + } + + EncounterUtil1.SetEncounterMoves(pk, version, LevelMin); + + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PK5 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + var type = Shiny == Shiny.Always ? PIDType.G5MGShiny : PIDType.None; + PIDGenerator.SetRandomWildPID5(pk, nature, ability, gender, type); + criteria.SetRandomIVs(pk); + if (Shiny == Shiny.Always) + return; + if (pk.IsShiny) + { + if ((Shiny == Shiny.Random && !criteria.Shiny.IsShiny()) || Shiny == Shiny.Never) + { + var pid = pk.PID; + pid ^= 0x1000_0000; + var result = (pid & 1) ^ (pid >> 31) ^ (pk.TID16 & 1) ^ (pk.SID16 & 1); + if (result == 1) + pid ^= 1; + pk.PID = pid; + } + } + else + { + if (Shiny == Shiny.Random && criteria.Shiny.IsShiny()) + { + var low = pk.PID & 0xFFFF; + var pid = ((low ^ pk.TID16 ^ pk.SID16) << 16) | low; + var result = (pid & 1) ^ (pid >> 31) ^ (pk.TID16 & 1) ^ (pk.SID16 & 1); + if (result == 1) + pid ^= 1; + pk.PID = pid; + } + } + } + + #endregion + + #region Matching + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (pk.Met_Level != Level) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + private bool IsMatchPartial(PKM pk) + { + // BW/2 Jellicent collision with wild surf slot, resolved by duplicating the encounter with any abil + if (Ability == AbilityPermission.OnlyHidden && pk.AbilityNumber != 4 && pk.Format <= 7) + return true; + if (EggLocation == Locations.Daycare5 && pk.RelearnMove1 != 0) + return true; + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return false; + } + + private bool IsMatchLocation(PKM pk) + { + var met = pk.Met_Location; + if (EggEncounter) + return true; + if (!Roaming) + return met == Location; + return IsRoamerMet(met); + } + + private bool IsMatchEggLocation(PKM pk) + { + if (!EggEncounter) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + var eggloc = pk.Egg_Location; + if (!pk.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade5; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk.Met_Location is not (0 or Locations.LinkTrade5)) + return false; + return true; + } + + // 25,26,27,28, // Route 12, 13, 14, 15 Night latter half + // 15,16,31, // Route 2, 3, 18 Morning + // 17,18,29, // Route 4, 5, 16 Daytime + // 19,20,21, // Route 6, 7, 8 Evening + // 22,23,24, // Route 9, 10, 11 Night former half + private static bool IsRoamerMet(int location) + { + if ((uint)location >= 32) + return false; + return (0b10111111111111111000000000000000 & (1 << location)) != 0; + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5Entree.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5Entree.cs new file mode 100644 index 000000000..0c3dc2ff3 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5Entree.cs @@ -0,0 +1,108 @@ +namespace PKHeX.Core; + +/// +/// Generation 5 Entree Forest static encounter +/// +public sealed record EncounterStatic5Entree(GameVersion Version, ushort Species, byte Level, byte Form, sbyte Gender, AbilityPermission Ability) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMoveset +{ + public int Generation => 5; + public EntityContext Context => EntityContext.Gen5; + public Ball FixedBall => Ball.None; + public Shiny Shiny => Shiny.Never; + public bool IsShiny => false; + public bool EggEncounter => false; + public int EggLocation => 0; + public int Location => 075; + public string Name => $"Entree Forest Encounter ({Version})"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public Moveset Moves { get; } + + public EncounterStatic5Entree(GameVersion version, ushort species, byte level, byte form, sbyte gender, AbilityPermission ability, ushort Move) + : this(version, species, level, form, gender, ability) => Moves = new Moveset(Move); + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK5 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK5 + { + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)Ball.Dream, + + ID32 = tr.ID32, + Version = (byte)version, + Language = lang, + OT_Gender = tr.Gender, + OT_Name = tr.OT, + + OT_Friendship = PersonalTable.B2W2[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, version, Level); + + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PK5 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + PIDGenerator.SetRandomWildPID5(pk, nature, ability, gender); + if (pk.IsShiny) + pk.PID ^= 0x1000_0000; + criteria.SetRandomIVs(pk); + } + + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (pk.Met_Location != Location) + return false; + if (pk.Met_Level != Level) + return false; + if (Gender != -1 && pk.Gender != Gender) + return true; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5N.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5N.cs new file mode 100644 index 000000000..436e007dd --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5N.cs @@ -0,0 +1,133 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 5 Static Encounter from N +/// +public sealed record EncounterStatic5N(uint PID) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFixedTrainer +{ + public int Generation => 5; + public EntityContext Context => EntityContext.Gen5; + public GameVersion Version => GameVersion.B2W2; + public const bool NSparkle = true; + public bool IsFixedTrainer => true; + private const uint ID32 = 2; + private const byte IV = 30; + + public byte Form => 0; + int ILocation.Location => Location; + public bool IsShiny => false; + public Shiny Shiny => Shiny.FixedValue; + public bool EggEncounter => false; + public int EggLocation => 0; + public Ball FixedBall => Species == (int)Core.Species.Zorua ? Ball.Poke : Ball.None; // Zorua can't be captured; others can. + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public required byte Location { get; init; } + public required Nature Nature { get; init; } + public required AbilityPermission Ability { get; init; } + + public string Name => "Dream Radar Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK5 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pi = PersonalTable.B2W2.GetFormEntry(Species, Form); + var pk = new PK5 + { + Species = Species, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke), + + Version = (byte)version, + Language = lang, + + OT_Friendship = pi.BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + IV_HP = IV, + IV_ATK = IV, + IV_DEF = IV, + IV_SPA = IV, + IV_SPD = IV, + IV_SPE = IV, + + NSparkle = NSparkle, + OT_Name = GetOT(lang), + OT_Gender = 0, + ID32 = ID32, + PID = PID, + Nature = (byte)Nature, + Gender = pi.Genderless ? 2 : 0, + Ability = Ability switch + { + AbilityPermission.OnlyFirst => pi.Ability1, + AbilityPermission.OnlySecond => pi.Ability2, + _ => pi.AbilityH, + }, + }; + + EncounterUtil1.SetEncounterMoves(pk, version, LevelMin); + + pk.ResetPartyStats(); + + return pk; + } + + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (PID != pk.PID) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (pk.Met_Location != Location) + return false; + if (pk.Met_Level != Level) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + var pi = PersonalTable.B2W2.GetFormEntry(Species, Form); + if (pk.Gender != (pi.Genderless ? 2 : 0)) + return false; + if (pk.OT_Gender != 0) + return false; + if (pk is not { IV_HP: IV, IV_ATK: IV, IV_DEF: IV, IV_SPA: IV, IV_SPD: IV, IV_SPE: IV }) + return false; + if (pk.ID32 != ID32) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + private static string GetOT(int lang) => lang == (int)LanguageID.Japanese ? "N" : "N"; + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int lang) => trainer.SequenceEqual(GetOT(lang)); + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5Radar.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5Radar.cs new file mode 100644 index 000000000..fed687cdc --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterStatic5Radar.cs @@ -0,0 +1,106 @@ +namespace PKHeX.Core; + +/// +/// Generation 5 Dream Radar gift encounters +/// +public sealed record EncounterStatic5Radar(ushort Species, byte Form, AbilityPermission Ability = AbilityPermission.OnlyHidden) + : IEncounterable, IEncounterMatch, IEncounterConvertible +{ + public int Generation => 5; + public EntityContext Context => EntityContext.Gen5; + public GameVersion Version => GameVersion.B2W2; + public int Location => 30015; + public Ball FixedBall => Ball.Dream; + public bool IsShiny => false; + public Shiny Shiny => Shiny.Never; + public bool EggEncounter => false; + public int EggLocation => 0; + public byte LevelMin => 5; + public byte LevelMax => 40; + public string Name => "Dream Radar Encounter"; + public string LongName => Name; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK5 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK5 + { + Species = Species, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)FixedBall, + + ID32 = tr.ID32, + Version = (byte)version, + Language = lang, + OT_Gender = tr.Gender, + OT_Name = tr.OT, + + OT_Friendship = PersonalTable.B2W2[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + EncounterUtil1.SetEncounterMoves(pk, version, LevelMin); + + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PK5 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + PIDGenerator.SetRandomWildPID5(pk, nature, ability, gender); + if (pk.IsShiny) + pk.PID ^= 0x1000_0000; + criteria.SetRandomIVs(pk); + } + + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (pk.Met_Location != Location) + return false; + if (!IsMatchLevel(pk.Met_Level)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + private static bool IsMatchLevel(int met) + { + // Level from 5->40 depends on the number of badges + if (met % 5 != 0) + return false; // must be a multiple of 5 + return (uint)(met - 5) <= 35; // 5 <= x <= 40 + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterTrade5B2W2.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterTrade5B2W2.cs new file mode 100644 index 000000000..0a36a78d1 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterTrade5B2W2.cs @@ -0,0 +1,160 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 5 Trade Encounter +/// +public sealed record EncounterTrade5B2W2 : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible, IFixedGender +{ + public int Generation => 5; + public EntityContext Context => EntityContext.Gen5; + public int Location => Locations.LinkTrade5NPC; + public bool IsFixedNickname { get; init; } + public GameVersion Version { get; } + public Shiny Shiny => Shiny.Never; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public required AbilityPermission Ability { get; init; } + public required byte OTGender { get; init; } + public required uint ID32 { get; init; } + + public byte Form { get; init; } + public sbyte Gender { get; init; } + public IndividualValueSet IVs { get; init; } + public Nature Nature { get; init; } = Nature.Random; + + private readonly string[] TrainerNames; + private readonly string[] Nicknames; + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public EncounterTrade5B2W2(ReadOnlySpan names, byte index, GameVersion version) + { + Version = version; + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + IsFixedNickname = true; + } + public EncounterTrade5B2W2(string[] names, GameVersion version) + { + Version = version; + Gender = -1; + Nature = Nature.Random; + Nicknames = Array.Empty(); + TrainerNames = names; + IsFixedNickname = false; + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK5 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK5 + { + PID = Util.Rand32(), + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateNDS(), + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang, + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Friendship = PersonalTable.B2W2[Species, 0].BaseFriendship, + + IsNicknamed = IsFixedNickname, + Nickname = IsFixedNickname ? Nicknames[lang] : SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + + HT_Name = tr.OT, + HT_Gender = tr.Gender, + }; + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PK5 pk, EncounterCriteria criteria) + { + if (pk.IsShiny) + pk.PID ^= 0x1000_0000; + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, PersonalTable.B2W2.GetFormEntry(Species, Form)); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + pk.SetRandomIVsTemplate(IVs, 0); + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) => (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) + return false; + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (pk.ID32 != ID32) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is PB8) + expect = Locations.Default8bNone; + return pk.Egg_Location == expect; + } + private bool IsMatchNatureGenderShiny(PKM pk) + { + if (!Shiny.IsValid(pk)) + return false; + if (Gender != -1 && Gender != pk.Gender) + return false; + if (Nature != Nature.Random && pk.Nature != (int)Nature) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterTrade5BW.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterTrade5BW.cs new file mode 100644 index 000000000..a19a92a18 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterTrade5BW.cs @@ -0,0 +1,153 @@ +using System; + +namespace PKHeX.Core; + +/// Generation 5 Trade with Fixed PID +public sealed record EncounterTrade5BW : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible +{ + public int Generation => 5; + public EntityContext Context => EntityContext.Gen5; + public int Location => Locations.LinkTrade5NPC; + public bool IsFixedNickname => true; + public GameVersion Version { get; } + public Shiny Shiny => Shiny.Never; + public bool EggEncounter => false; + public int EggLocation => 0; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public bool IsFixedTrainer => true; + public byte LevelMin => Level; + public byte LevelMax => Level; + + private string[] TrainerNames { get; } + private string[] Nicknames { get; } + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public required AbilityPermission Ability { get; init; } + public required byte OTGender { get; init; } + public required ushort ID32 { get; init; } + public required byte Gender { get; init; } + public required IndividualValueSet IVs { get; init; } + public required Nature Nature { get; init; } + public byte Form { get; init; } + + /// Fixed value the encounter must have. + public uint PID { get; } + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + + public EncounterTrade5BW(ReadOnlySpan names, byte index, GameVersion version, uint pid) + { + Version = version; + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + PID = pid; + } + + /// + /// Checks if the language can be missing. + /// + /// + /// Generation 5 trades from B/W forgot to set the Language ID, so it remains as 0. + ///
This value is corrected when the entity is transferred from PK5->PK6. + ///
B2/W2 is unaffected by this game data bug. + ///
+ public static bool IsValidMissingLanguage(PKM pk) => pk.Format == 5; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK5 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK5 + { + PID = PID, + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateNDS(), + Gender = Gender, + Nature = (byte)Nature, + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang == 1 ? 0 : lang, // Trades for JPN games have language ID of 0, not 1. + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Friendship = PersonalTable.BW[Species, Form].BaseFriendship, + + IsNicknamed = IsFixedNickname, + Nickname = IsFixedNickname ? Nicknames[lang] : SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + + HT_Name = tr.OT, + HT_Gender = tr.Gender, + }; + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + pk.SetRandomIVsTemplate(IVs, 0); + pk.RefreshAbility((byte)Ability >> 1); + pk.ResetPartyStats(); + + return pk; + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) => (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (!Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) + return false; + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (pk.ID32 != ID32) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + return true; + } + + private bool IsMatchNatureGenderShiny(PKM pk) + { + if (PID != pk.EncryptionConstant) + return false; + if ((int)Nature != pk.Nature) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is PB8) + expect = Locations.Default8bNone; + return pk.Egg_Location == expect; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterArea6AO.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterArea6AO.cs new file mode 100644 index 000000000..f31ee71d6 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterArea6AO.cs @@ -0,0 +1,60 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// +/// encounter area +/// +public sealed record EncounterArea6AO : IEncounterArea, IAreaLocation +{ + public EncounterSlot6AO[] Slots { get; } + public GameVersion Version { get; } + + public readonly ushort Location; + public readonly SlotType Type; + + public bool IsMatchLocation(int location) => Location == location; + + public static EncounterArea6AO[] GetAreas(BinLinkerAccessor input, GameVersion game) + { + var result = new EncounterArea6AO[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea6AO(input[i], game); + return result; + } + + private EncounterArea6AO(ReadOnlySpan data, GameVersion game) + { + Location = ReadUInt16LittleEndian(data); + Type = (SlotType)data[2]; + Version = game; + + Slots = ReadSlots(data); + } + + private EncounterSlot6AO[] ReadSlots(ReadOnlySpan data) + { + const int size = 4; + int count = (data.Length - 4) / size; + var slots = new EncounterSlot6AO[count]; + for (int i = 0; i < slots.Length; i++) + { + int offset = 4 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadSlot(entry); + } + + return slots; + } + + private EncounterSlot6AO ReadSlot(ReadOnlySpan entry) + { + ushort species = ReadUInt16LittleEndian(entry); + byte form = (byte)(species >> 11); + species &= 0x3FF; + byte min = entry[2]; + byte max = entry[3]; + return new EncounterSlot6AO(this, species, form, min, max); + } +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterArea6XY.cs similarity index 62% rename from PKHeX.Core/Legality/Areas/EncounterArea6XY.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterArea6XY.cs index 29affb0df..297a92307 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterArea6XY.cs @@ -1,16 +1,20 @@ using System; -using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -/// /// /// encounter area /// -public sealed record EncounterArea6XY : EncounterArea, IMemorySpeciesArea +public sealed record EncounterArea6XY : IEncounterArea, IAreaLocation { - public readonly EncounterSlot6XY[] Slots; + public EncounterSlot6XY[] Slots { get; } + public GameVersion Version { get; } + + public readonly ushort Location; + public readonly SlotType Type; + + public bool IsMatchLocation(int location) => Location == location; public static EncounterArea6XY[] GetAreas(BinLinkerAccessor input, GameVersion game, EncounterArea6XY safari) { @@ -22,18 +26,20 @@ public sealed record EncounterArea6XY : EncounterArea, IMemorySpeciesArea return result; } - public EncounterArea6XY() : base(GameVersion.XY) + public EncounterArea6XY() { Location = 148; // Friend Safari Type = SlotType.FriendSafari; + Version = GameVersion.XY; Slots = LoadSafariSlots(); } - private EncounterArea6XY(ReadOnlySpan data, GameVersion game) : base(game) + private EncounterArea6XY(ReadOnlySpan data, GameVersion game) { - Location = ReadInt16LittleEndian(data); + Location = ReadUInt16LittleEndian(data); Type = (SlotType)data[2]; + Version = game; Slots = ReadSlots(data); } @@ -55,7 +61,7 @@ public sealed record EncounterArea6XY : EncounterArea, IMemorySpeciesArea slots[i++] = new EncounterSlot6XY(this, (int)Species.Floette, 3, 30, 30); // Region Random Vivillon - slots[i] = new EncounterSlot6XY(this, (int)Species.Vivillon, EncounterSlot.FormVivillon, 30, 30); + slots[i] = new EncounterSlot6XY(this, (int)Species.Vivillon, EncounterUtil1.FormVivillon, 30, 30); return slots; } @@ -104,66 +110,4 @@ public sealed record EncounterArea6XY : EncounterArea, IMemorySpeciesArea return slots; } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(pk.Met_Level)) - break; - - if (slot.Form != evo.Form && slot is { IsRandomUnspecificForm: false, Species: not ((int)Species.Burmy or (int)Species.Furfrou) }) - { - // Only slot that can be form-mismatched via Pressure is Flabébé - if (slot.Species != (int)Species.Flabébé) - break; - - var maxLevel = slot.LevelMax; - if (!ExistsPressureSlot(evo, ref maxLevel)) - break; - - if (maxLevel != pk.Met_Level) - break; - - yield return slot.CreatePressureFormCopy(evo.Form); - break; - } - - yield return slot; - break; - } - } - } - - private bool ExistsPressureSlot(EvoCriteria evo, ref byte level) - { - bool existsForm = false; - foreach (var z in Slots) - { - if (z.Species != evo.Species) - continue; - if (z.Form == evo.Form) - continue; - if (z.LevelMax < level) - continue; - level = z.LevelMax; - existsForm = true; - } - return existsForm; - } - - public bool HasSpecies(ushort species) - { - foreach (var slot in Slots) - { - if (slot.Species == species) - return true; - } - return false; - } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6AO.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6AO.cs new file mode 100644 index 000000000..7f1bee1fb --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6AO.cs @@ -0,0 +1,149 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot6AO(EncounterArea6AO Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IEncounterFormRandom +{ + public int Generation => 6; + public EntityContext Context => EntityContext.Gen6; + public bool EggEncounter => false; + public Ball FixedBall => Ball.None; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsRandomUnspecificForm => Form >= EncounterUtil1.FormDynamic; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + public bool CanDexNav => Type != SlotType.Rock_Smash; + public bool IsHorde => Type == SlotType.Horde; + + private HiddenAbilityPermission IsHiddenAbilitySlot() => CanDexNav || IsHorde ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; + + private ReadOnlySpan GetDexNavMoves() + { + var et = EvolutionTree.Evolves6; + var baby = et.GetBaseSpeciesForm(Species, Form); + return LearnSource6AO.Instance.GetEggMoves(baby.Species, baby.Form); + } + + public bool CanBeDexNavMove(ushort move) => GetDexNavMoves().Contains(move); + + public AbilityPermission Ability => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => AbilityPermission.Any12, + HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden, + _ => AbilityPermission.Any12H, + }; + private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => IsHidden, + HiddenAbilityPermission.Always => !IsHidden, + _ => false, + }; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK6 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PK6 + { + Species = Species, + Form = GetWildForm(Form), + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.AO[Species].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + Ball = (byte)Ball.Poke, + MetDate = EncounterDate.GetDate3DS(), + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + if (tr is IRegionOrigin r) + r.CopyRegionOrigin(pk); + else + pk.SetDefaultRegionOrigins(lang); + + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + if (CanDexNav) + { + var eggMoves = GetDexNavMoves(); + if (eggMoves.Length > 0) + pk.RelearnMove1 = eggMoves[Util.Rand.Next(eggMoves.Length)]; + } + pk.SetRandomMemory6(); + pk.ResetPartyStats(); + return pk; + } + + private byte GetWildForm(byte form) + { + if (form != EncounterUtil1.FormRandom) + return form; + // flagged as totally random + return (byte)Util.Rand.Next(PersonalTable.AO[Species].FormCount); + } + + private void SetPINGA(PK6 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, PersonalTable.AO.GetFormEntry(Species, Form)); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + pk.SetRandomIVs(); + } + #endregion + + #region Matching + + private const int FluteBoostMin = 4; // White Flute decreases levels. + private const int FluteBoostMax = 4; // Black Flute increases levels. + private const int DexNavBoost = 30; // Maximum DexNav chain + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + var boostMax = Type != SlotType.Rock_Smash ? DexNavBoost : FluteBoostMax; + const int boostMin = FluteBoostMin; + if (!this.IsLevelWithinRange(pk.Met_Level, boostMin, boostMax)) + return false; + + if (evo.Form != Form && !IsRandomUnspecificForm) + return false; + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsDeferredWurmple(pk)) + return EncounterMatchRating.PartialMatch; + + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + if (IsDeferredHiddenAbility(isHidden)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; + } + + private bool IsDeferredWurmple(PKM pk) => Species == (int)Core.Species.Wurmple && pk.Species != (int)Core.Species.Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk); + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6XY.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6XY.cs new file mode 100644 index 000000000..84e3d74e1 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6XY.cs @@ -0,0 +1,138 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot6XY(EncounterArea6XY Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IEncounterFormRandom, IFlawlessIVCount +{ + public int Generation => 6; + public EntityContext Context => EntityContext.Gen6; + public bool EggEncounter => false; + public Ball FixedBall => Ball.None; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsRandomUnspecificForm => Form >= EncounterUtil1.FormDynamic; + + public byte FlawlessIVCount + { + get + { + var pi = PersonalTable.XY.GetFormEntry(Species, Form); + return pi.EggGroup1 == 15 ? (byte)3 : IsFriendSafari ? (byte)2 : (byte)0; + } + } + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + + public bool Pressure { get; init; } + public bool IsFriendSafari => Type == SlotType.FriendSafari; + public bool IsHorde => Type == SlotType.Horde; + + public EncounterSlot6XY CreatePressureFormCopy(byte form) => this with {Form = form, Pressure = true}; + + private HiddenAbilityPermission IsHiddenAbilitySlot() => IsHorde || IsFriendSafari ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; + + public AbilityPermission Ability => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => AbilityPermission.Any12, + HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden, + _ => AbilityPermission.Any12H, + }; + private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => IsHidden, + HiddenAbilityPermission.Always => !IsHidden, + _ => false, + }; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK6 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = Version != GameVersion.XY ? Version : GameVersion.XY.Contains(tr.Game) ? (GameVersion)tr.Game : GameVersion.X; + var form = GetWildForm(Form); + var pk = new PK6 + { + Species = Species, + Form = form, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + Ball = (byte)Ball.Poke, + MetDate = EncounterDate.GetDate3DS(), + + Version = (byte)version, + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + OT_Friendship = PersonalTable.XY[Species, form].BaseFriendship, + }; + if (tr is IRegionOrigin r) + r.CopyRegionOrigin(pk); + else + pk.SetDefaultRegionOrigins(lang); + + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + pk.SetRandomMemory6(); + pk.ResetPartyStats(); + return pk; + } + + private byte GetWildForm(byte form) + { + if (form != EncounterUtil1.FormRandom) + return form; + // flagged as totally random + return (byte)Util.Rand.Next(PersonalTable.XY[Species].FormCount); + } + + private void SetPINGA(PK6 pk, EncounterCriteria criteria) + { + var pi = PersonalTable.XY.GetFormEntry(Species, Form); + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, pi); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + pk.SetRandomIVs(FlawlessIVCount); + } + + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + + if (Form != evo.Form && !IsRandomUnspecificForm && Species is not ((int)Core.Species.Burmy or (int)Core.Species.Furfrou)) + return false; + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + if (IsDeferredHiddenAbility(isHidden)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterStatic6.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterStatic6.cs new file mode 100644 index 000000000..a082a2448 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterStatic6.cs @@ -0,0 +1,199 @@ +namespace PKHeX.Core; + +/// +/// Generation 6 Static Encounter +/// +public sealed record EncounterStatic6(GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IContestStatsReadOnly, IHatchCycle, IFlawlessIVCount, IFatefulEncounterReadOnly, IFixedGender, IMoveset +{ + public int Generation => 6; + public EntityContext Context => EntityContext.Gen6; + int ILocation.Location => Location; + int ILocation.EggLocation => EggLocation; + public bool IsShiny => false; + public bool EggEncounter => EggLocation != 0; + public Ball FixedBall { get; init; } + public bool FatefulEncounter { get; init; } + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public required ushort Location { get; init; } + public AbilityPermission Ability { get; init; } + public byte Form { get; init; } + public Shiny Shiny { get; init; } + public ushort EggLocation { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + public sbyte Gender { get; init; } = -1; + + public byte CNT_Cool { get; init; } + public byte CNT_Beauty { get; init; } + public byte CNT_Cute { get; init; } + public byte CNT_Smart { get; init; } + public byte CNT_Tough { get; init; } + public byte CNT_Sheen { get; init; } + + public byte EggCycles { get; init; } + public byte FlawlessIVCount { get; init; } + public IndividualValueSet IVs { get; init; } + public Nature Nature { get; init; } = Nature.Random; + public Moveset Moves { get; init; } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK6 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK6 + { + EncryptionConstant = Util.Rand32(), + Species = Species, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + MetDate = EncounterDate.GetDate3DS(), + Ball = (byte)(FixedBall is Ball.None ? Ball.Poke : FixedBall), + + ID32 = tr.ID32, + Version = (byte)version, + Language = lang, + OT_Gender = tr.Gender, + OT_Name = tr.OT, + + OT_Friendship = PersonalTable.AO[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + + if (EggEncounter) + { + // Fake as hatched. + pk.Met_Location = version is GameVersion.X or GameVersion.Y ? Locations.HatchLocation6XY : Locations.HatchLocation6AO; + pk.Met_Level = EggStateLegality.EggMetLevel; + pk.Egg_Location = EggLocation; + pk.EggMetDate = pk.MetDate; + } + + if (tr is IRegionOrigin r) + r.CopyRegionOrigin(pk); + else + pk.SetDefaultRegionOrigins(lang); + + if (Moves.HasMoves) + pk.SetMoves(Moves); + else + EncounterUtil1.SetEncounterMoves(pk, version, LevelMin); + this.CopyContestStatsTo(pk); + + pk.SetRandomMemory6(); + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PK6 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + if (pk.IsShiny) + { + if (Shiny == Shiny.Never || (Shiny != Shiny.Always && !criteria.Shiny.IsShiny())) + pk.PID ^= 0x1000_0000; + } + else if (Shiny == Shiny.Always || (Shiny != Shiny.Never && criteria.Shiny.IsShiny())) + { + var low = pk.PID & 0xFFFF; + pk.PID = ((low ^ pk.TID16 ^ pk.SID16) << 16) | low; + } + + if (IVs.IsSpecified) + criteria.SetRandomIVs(pk, IVs); + else + criteria.SetRandomIVs(pk, FlawlessIVCount); + + var pi = pk.PersonalInfo; + var ability = criteria.GetAbilityFromNumber(Ability); + pk.Nature = (int)criteria.GetNature(Nature); + pk.Gender = criteria.GetGender(Gender, pi); + pk.AbilityNumber = 1 << ability; + pk.Ability = pi.GetAbilityAtIndex(ability); + } + + #endregion + + #region Matching + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (pk.Met_Level != Level) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk is IContestStatsReadOnly s && s.IsContestBelow(this)) + return false; + if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + return true; + } + + private bool IsMatchPartial(PKM pk) + { + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return false; + } + + private bool IsMatchLocation(PKM pk) + { + if (EggEncounter) + return true; + var met = pk.Met_Location; + if (met == Location) + return true; + + if (Species != (int)Core.Species.Pikachu) + return false; + + // Cosplay Pikachu is given from multiple locations + return met is 180 or 186 or 194; + } + + private bool IsMatchEggLocation(PKM pk) + { + if (!EggEncounter) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + var eggloc = pk.Egg_Location; + if (!pk.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade6; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk.Met_Location is not (0 or Locations.LinkTrade6)) + return false; + return true; + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterTrade6.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterTrade6.cs new file mode 100644 index 000000000..593f65a04 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterTrade6.cs @@ -0,0 +1,172 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 6 Trade Encounter +/// +public sealed record EncounterTrade6 : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible, IMemoryOTReadOnly +{ + public int Generation => 6; + public EntityContext Context => EntityContext.Gen6; + public int Location => Locations.LinkTrade6NPC; + public Shiny Shiny => Shiny.Never; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + public bool IsFixedNickname { get; init; } = true; + + private string[] TrainerNames { get; } + private string[] Nicknames { get; } + + public required Nature Nature { get; init; } + public required ushort ID32 { get; init; } + public required AbilityPermission Ability { get; init; } + public required byte Gender { get; init; } + public required byte OTGender { get; init; } + + public required IndividualValueSet IVs { get; init; } + public required ushort Species { get; init; } + public byte Form => 0; + public required byte Level { get; init; } + public GameVersion Version { get; } + public byte OT_Memory { get; } + public byte OT_Intensity { get; } + public byte OT_Feeling { get; } + public ushort OT_TextVar { get; } + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public EncounterTrade6(ReadOnlySpan names, byte index, GameVersion version, byte m, byte i, byte f, ushort v) + { + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + Version = version; + OT_Memory = m; + OT_Intensity = i; + OT_Feeling = f; + OT_TextVar = v; + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK6 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK6 + { + PID = Util.Rand32(), + EncryptionConstant = Util.Rand32(), + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDate3DS(), + Gender = Gender, + Nature = (byte)Nature, + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang, + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Memory = OT_Memory, + OT_Intensity = OT_Intensity, + OT_Feeling = OT_Feeling, + OT_TextVar = OT_TextVar, + OT_Friendship = PersonalTable.AO[Species, 0].BaseFriendship, + + IsNicknamed = IsFixedNickname, + Nickname = IsFixedNickname ? Nicknames[lang] : SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + + HT_Name = tr.OT, + HT_Gender = tr.Gender, + CurrentHandler = 1, + HT_Friendship = PersonalTable.AO[Species, 0].BaseFriendship, + }; + if (tr is IRegionOrigin r) + r.CopyRegionOrigin(pk); + else + pk.SetDefaultRegionOrigins(lang); + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + if (pk.IsShiny) + pk.PID ^= 0x1000_0000; + pk.SetRandomIVsTemplate(IVs, 0); + pk.RefreshAbility((byte)Ability >> 1); + pk.ResetPartyStats(); + + return pk; + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) + { + if (Species is (ushort)Core.Species.Farfetchd && nickname is "Quacklin’" or "Quacklin'") + return true; + return (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + } + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (IVs.IsSpecified) + { + if (!Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + } + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (pk.ID32 != ID32) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is PB8 && expect is 0) + expect = Locations.Default8bNone; + return pk.Egg_Location == expect; + } + private bool IsMatchNatureGenderShiny(PKM pk) + { + if (!Shiny.IsValid(pk)) + return false; + if (Gender != pk.Gender) + return false; + if (Nature != Nature.Random && pk.Nature != (int)Nature) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterArea7.cs similarity index 59% rename from PKHeX.Core/Legality/Areas/EncounterArea7.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterArea7.cs index 85fcc149e..0ebc9dca8 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea7.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterArea7.cs @@ -1,16 +1,20 @@ using System; -using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -/// /// /// encounter area /// -public sealed record EncounterArea7 : EncounterArea +public sealed record EncounterArea7 : IEncounterArea, IAreaLocation { - public readonly EncounterSlot7[] Slots; + public EncounterSlot7[] Slots { get; } + public GameVersion Version { get; } + + public readonly ushort Location; + public readonly SlotType Type; + + public bool IsMatchLocation(int location) => Location == location; public static EncounterArea7[] GetAreas(BinLinkerAccessor input, GameVersion game) { @@ -20,10 +24,11 @@ public sealed record EncounterArea7 : EncounterArea return result; } - private EncounterArea7(ReadOnlySpan data, GameVersion game) : base(game) + private EncounterArea7(ReadOnlySpan data, GameVersion game) { - Location = ReadInt16LittleEndian(data); + Location = ReadUInt16LittleEndian(data); Type = (SlotType)data[2]; + Version = game; Slots = ReadSlots(data); } @@ -52,28 +57,4 @@ public sealed record EncounterArea7 : EncounterArea byte max = entry[3]; return new EncounterSlot7(this, species, form, min, max); } - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(pk.Met_Level)) - break; - - if (slot.Form != evo.Form && slot.Species is not ((int)Species.Furfrou or (int)Species.Oricorio)) - { - if (!slot.IsRandomUnspecificForm) // Minior, etc - break; - } - - yield return slot; - break; - } - } - } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterSlot7.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterSlot7.cs new file mode 100644 index 000000000..2bbbc6629 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterSlot7.cs @@ -0,0 +1,140 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot7(EncounterArea7 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IEncounterFormRandom, IFlawlessIVCountConditional +{ + public int Generation => 7; + public EntityContext Context => EntityContext.Gen7; + public bool EggEncounter => false; + public Ball FixedBall => Location == Locations.Pelago7 ? Ball.Poke : Ball.None; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsRandomUnspecificForm => Form >= EncounterUtil1.FormDynamic; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + + public bool IsSOS => Type == SlotType.SOS; + + public AbilityPermission Ability => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => AbilityPermission.Any12, + HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden, + _ => AbilityPermission.Any12H, + }; + + private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => IsHidden, + HiddenAbilityPermission.Always => !IsHidden, + _ => false, + }; + + private HiddenAbilityPermission IsHiddenAbilitySlot() => IsSOS ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var form = GetWildForm(Form); + var pk = new PK7 + { + Species = Species, + Form = form, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + MetDate = EncounterDate.GetDate3DS(), + Ball = (byte)Ball.Poke, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + OT_Friendship = PersonalTable.USUM[Species, form].BaseFriendship, + }; + if (tr is IRegionOrigin r) + r.CopyRegionOrigin(pk); + else + pk.SetDefaultRegionOrigins(lang); + + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + pk.ResetPartyStats(); + return pk; + } + + private byte GetWildForm(byte form) + { + if (form != EncounterUtil1.FormRandom) + return form; // flagged as totally random + if (Species == (int)Core.Species.Minior) + return (byte)Util.Rand.Next(7, 14); + return (byte)Util.Rand.Next(PersonalTable.USUM[Species].FormCount); + } + + private void SetPINGA(PK7 pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, pi); + criteria.SetRandomIVs(pk); + + var num = Ability; + if (IsSOS && pk.FlawlessIVCount < 2) + num = 0; // let's fake it as an insufficient chain, no HA possible. + var ability = criteria.GetAbilityFromNumber(num); + pk.RefreshAbility(ability); + } + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + + if (Form != evo.Form && Species is not ((int)Core.Species.Furfrou or (int)Core.Species.Oricorio)) + { + if (!IsRandomUnspecificForm) // Minior, etc + return false; + } + + return true; + } + public EncounterMatchRating GetMatchRating(PKM pk) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + if (IsDeferredHiddenAbility(isHidden)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; + } + #endregion + + public (byte Min, byte Max) GetFlawlessIVCount(PKM pk) + { + if (!IsSOS) + return default; + // Chain of 10 yields 5% HA and 2 flawless IVs + if (pk is { Context: EntityContext.Gen7, AbilityNumber: 4 }) + return (2, 2); + return default; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterStatic7.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterStatic7.cs new file mode 100644 index 000000000..26592015f --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterStatic7.cs @@ -0,0 +1,227 @@ +namespace PKHeX.Core; + +/// +/// Generation 7 Static Encounter +/// +public sealed record EncounterStatic7(GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IRelearn, IEncounterFormRandom, IFlawlessIVCount, IFatefulEncounterReadOnly, IFixedGender +{ + public int Generation => 7; + public EntityContext Context => EntityContext.Gen7; + int ILocation.Location => Location; + int ILocation.EggLocation => EggLocation; + public bool RibbonWishing => Species == (int)Core.Species.Magearna; + + public bool EggEncounter => EggLocation != 0; + public bool IsShiny => false; + + public Moveset Relearn { get; init; } + public IndividualValueSet IVs { get; init; } + public ushort Location { get; init; } + public ushort EggLocation { get; init; } + public required ushort Species { get; init; } + public byte Form { get; init; } + public required byte Level { get; init; } + public bool FatefulEncounter { get; init; } + public byte FlawlessIVCount { get; init; } + public Shiny Shiny { get; init; } + public AbilityPermission Ability { get; init; } + public sbyte Gender { get; init; } = -1; + public Nature Nature { get; init; } = Nature.Random; + public Ball FixedBall { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public bool IsTotem => FormInfo.IsTotemForm(Species, Form); + public bool IsTotemNoTransfer => Species is (int)Core.Species.Marowak or (int)Core.Species.Araquanid or (int)Core.Species.Togedemaru or (int)Core.Species.Ribombee; + public int GetTotemBaseForm() => FormInfo.GetTotemBaseForm(Species, Form); + + public bool IsRandomUnspecificForm => Form >= FormDynamic; + + private const int FormDynamic = FormVivillon; + internal const int FormVivillon = 30; + //protected const int FormRandom = 31; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PK7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK7 + { + EncryptionConstant = Util.Rand32(), + Species = Species, + Form = GetWildForm(Form, tr), + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + MetDate = EncounterDate.GetDate3DS(), + Ball = (byte)(FixedBall is Ball.None ? Ball.Poke : FixedBall), + + ID32 = tr.ID32, + Version = (byte)version, + Language = lang, + OT_Gender = tr.Gender, + OT_Name = tr.OT, + + OT_Friendship = PersonalTable.USUM[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + if (RibbonWishing) + pk.RibbonWishing = true; + + if (tr is IRegionOrigin r) + r.CopyRegionOrigin(pk); + else + pk.SetDefaultRegionOrigins(lang); + + if (EggEncounter) + { + // Fake as hatched. + pk.Met_Location = Locations.HatchLocation7; + pk.Met_Level = EggStateLegality.EggMetLevel; + pk.Egg_Location = EggLocation; + pk.EggMetDate = pk.MetDate; + } + + if (Relearn.HasMoves) + pk.SetRelearnMoves(Relearn); + EncounterUtil1.SetEncounterMoves(pk, version, LevelMin); + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private static byte GetWildForm(byte form, ITrainerInfo tr) + { + if (form == FormVivillon) + { + if (tr is IRegionOrigin r) + return Vivillon3DS.GetPattern(r.Country, r.Region); + if (tr.Language == 1) + return Vivillon3DS.GetPattern(1, 0); + return Vivillon3DS.GetPattern(49, 7); // USA, California + } + return form; + } + + private void SetPINGA(PK7 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + if (pk.IsShiny) + { + if (Shiny == Shiny.Never || (Shiny != Shiny.Always && !criteria.Shiny.IsShiny())) + pk.PID ^= 0x1000_0000; + } + else if (Shiny == Shiny.Always || (Shiny != Shiny.Never && criteria.Shiny.IsShiny())) + { + var low = pk.PID & 0xFFFF; + pk.PID = ((low ^ pk.TID16 ^ pk.SID16) << 16) | low; + } + + if (IVs.IsSpecified) + criteria.SetRandomIVs(pk, IVs); + else + criteria.SetRandomIVs(pk, FlawlessIVCount); + + var pi = pk.PersonalInfo; + var ability = criteria.GetAbilityFromNumber(Ability); + pk.Nature = (int)criteria.GetNature(Nature); + pk.Gender = criteria.GetGender(Gender, pi); + pk.AbilityNumber = 1 << ability; + pk.Ability = pi.GetAbilityAtIndex(ability); + } + + #endregion + + #region Matching + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (pk.Met_Level != Level) + return false; + if (!IsMatchForm(pk, evo)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + return true; + } + + private bool IsMatchPartial(PKM pk) + { + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return false; + } + + private bool IsMatchLocation(PKM pk) + { + if (EggEncounter) + return true; + + return pk.Met_Location == Location; + } + + private bool IsMatchEggLocation(PKM pk) + { + if (!EggEncounter) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + // Gift Eevee edge case + if (EggLocation == Locations.Daycare5 && !Relearn.HasMoves && pk.RelearnMove1 != 0) + return false; + + var eggloc = pk.Egg_Location; + if (!pk.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade6; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk.Met_Location is not (0 or Locations.LinkTrade6)) + return false; + return true; + } + + private bool IsMatchForm(PKM pk, EvoCriteria evo) + { + if (IsRandomUnspecificForm) + return true; + + if (IsTotem) + { + var expectForm = pk.Format == 7 ? Form : FormInfo.GetTotemBaseForm(Species, Form); + return expectForm == evo.Form; + } + + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterTrade7.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterTrade7.cs new file mode 100644 index 000000000..c30ef7235 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterTrade7.cs @@ -0,0 +1,168 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 7 Trade Encounter +/// +public sealed record EncounterTrade7 : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible, IMemoryOTReadOnly +{ + public int Generation => 7; + public EntityContext Context => EntityContext.Gen7; + public int Location => Locations.LinkTrade6NPC; + public byte OT_Memory => 1; + public byte OT_Intensity => 3; + public byte OT_Feeling => 5; + public ushort OT_TextVar => 40; + public Shiny Shiny => Shiny.Never; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + public bool IsFixedNickname => true; + + private string[] TrainerNames { get; } + private string[] Nicknames { get; } + + public required Nature Nature { get; init; } + public required uint ID32 { get; init; } + public required AbilityPermission Ability { get; init; } + public required byte Gender { get; init; } + public required byte OTGender { get; init; } + + public required IndividualValueSet IVs { get; init; } + public required ushort Species { get; init; } + public required byte Form { get; init; } + + public required byte Level { get; init; } + public GameVersion Version { get; } + public bool EvolveOnTrade { get; init; } + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public EncounterTrade7(ReadOnlySpan names, byte index, GameVersion version) + { + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + Version = version; + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK7 + { + PID = Util.Rand32(), + EncryptionConstant = Util.Rand32(), + Species = Species, + Form = Form, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDate3DS(), + Gender = Gender, + Nature = (byte)Nature, + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang, + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Memory = OT_Memory, + OT_Intensity = OT_Intensity, + OT_Feeling = OT_Feeling, + OT_TextVar = OT_TextVar, + OT_Friendship = PersonalTable.USUM[Species, Form].BaseFriendship, + + IsNicknamed = true, + Nickname = Nicknames[lang], + + HT_Name = tr.OT, + HT_Gender = tr.Gender, + CurrentHandler = 1, + HT_Friendship = PersonalTable.USUM[Species, Form].BaseFriendship, + }; + if (tr is IRegionOrigin r) + r.CopyRegionOrigin(pk); + else + pk.SetDefaultRegionOrigins(lang); + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + if (pk.IsShiny) + pk.PID ^= 0x1000_0000; + pk.SetRandomIVsTemplate(IVs, 0); + pk.RefreshAbility((byte)Ability >> 1); + pk.ResetPartyStats(); + + return pk; + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) => (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (IVs.IsSpecified) + { + if (!Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + } + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (pk.ID32 != ID32) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (EvolveOnTrade && pk.Species == Species) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is PB8) + expect = Locations.Default8bNone; + return pk.Egg_Location == expect; + } + private bool IsMatchNatureGenderShiny(PKM pk) + { + if (!Shiny.IsValid(pk)) + return false; + if (Gender != pk.Gender) + return false; + if (Nature != Nature.Random && pk.Nature != (int)Nature) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterTransfer7.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterTransfer7.cs new file mode 100644 index 000000000..d0ca3904b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterTransfer7.cs @@ -0,0 +1,63 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Virtual Console Transfer Encounter Data +/// +/// Version group transferred from. +/// Species transferred. +/// Level transferred at. +public sealed record EncounterTransfer7(GameVersion Version, ushort Species, byte Level) : IEncounterable, IFlawlessIVCount, IFatefulEncounterReadOnly +{ + public int Generation => 7; + public EntityContext Context => EntityContext.Gen7; + public Ball FixedBall => Ball.Poke; + public bool EggEncounter => false; + public int EggLocation => 0; + + public byte Form => 0; + public int Location { get; private init; } + public Shiny Shiny { get; private init; } + public AbilityPermission Ability { get; private init; } + public bool FatefulEncounter { get; private init; } + public byte FlawlessIVCount { get; private init; } + + public byte LevelMin => Level; + public byte LevelMax => Level; + public bool IsShiny => false; + + public string Name => "Virtual Console Transfer"; + public string LongName => Name; + + public static EncounterTransfer7 GetVC1(ushort species, byte metLevel) + { + bool mew = species == (int)Core.Species.Mew; + return new EncounterTransfer7(GameVersion.RBY, species, metLevel) + { + Species = species, + Ability = TransporterLogic.IsHiddenDisallowedVC1(species) ? AbilityPermission.OnlyFirst : AbilityPermission.OnlyHidden, // Hidden by default, else first + Shiny = mew ? Shiny.Never : Shiny.Random, + FatefulEncounter = mew, + Location = Locations.Transfer1, + FlawlessIVCount = mew ? (byte)5 : (byte)3, + }; + } + + public static EncounterTransfer7 GetVC2(ushort species, byte metLevel) + { + bool mew = species == (int)Core.Species.Mew; + bool fateful = mew || species == (int)Core.Species.Celebi; + return new EncounterTransfer7(GameVersion.GSC, species, metLevel) + { + Ability = TransporterLogic.IsHiddenDisallowedVC2(species) ? AbilityPermission.OnlyFirst : AbilityPermission.OnlyHidden, // Hidden by default, else first + Shiny = mew ? Shiny.Never : Shiny.Random, + FatefulEncounter = fateful, + Location = Locations.Transfer2, + FlawlessIVCount = fateful ? (byte)5 : (byte)3, + }; + } + + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => throw new InvalidOperationException("Conversion not supported."); +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterArea7b.cs similarity index 60% rename from PKHeX.Core/Legality/Areas/EncounterArea7b.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterArea7b.cs index b5649e0e9..dc7e5f8fe 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea7b.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterArea7b.cs @@ -1,16 +1,19 @@ using System; -using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -/// /// /// encounter area /// -public sealed record EncounterArea7b : EncounterArea +public sealed record EncounterArea7b : IEncounterArea, IAreaLocation { - public readonly EncounterSlot7b[] Slots; + public EncounterSlot7b[] Slots { get; } + public GameVersion Version { get; } + + public readonly ushort Location; + + public bool IsMatchLocation(int location) => Location == location; public static EncounterArea7b[] GetAreas(BinLinkerAccessor input, GameVersion game) { @@ -20,9 +23,10 @@ public sealed record EncounterArea7b : EncounterArea return result; } - private EncounterArea7b(ReadOnlySpan data, GameVersion game) : base(game) + private EncounterArea7b(ReadOnlySpan data, GameVersion game) { - Location = ReadInt16LittleEndian(data); + Location = ReadUInt16LittleEndian(data); + Version = game; Slots = ReadSlots(data); } @@ -49,27 +53,4 @@ public sealed record EncounterArea7b : EncounterArea byte max = entry[3]; return new EncounterSlot7b(this, species, min, max); } - - private const int CatchComboBonus = 1; - - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - var met = pk.Met_Level; - if (!slot.IsLevelWithinRange(met, 0, CatchComboBonus)) - break; - if (slot.Form != evo.Form) - break; - - yield return slot; - break; - } - } - } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterSlot7b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterSlot7b.cs new file mode 100644 index 000000000..66aa7ebf4 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterSlot7b.cs @@ -0,0 +1,85 @@ +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot7b(EncounterArea7b Parent, ushort Species, byte LevelMin, byte LevelMax) + : IEncounterable, IEncounterMatch, IEncounterConvertible +{ + public int Generation => 7; + public EntityContext Context => EntityContext.Gen7b; + public bool EggEncounter => false; + public Ball FixedBall => Ball.None; + public Shiny Shiny => Shiny.Random; + public AbilityPermission Ability => AbilityPermission.Any12; + public bool IsShiny => false; + public int EggLocation => 0; + + public byte Form => 0; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PB7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PB7 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.GG[Species].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)Ball.Poke, + + HeightScalar = PokeSizeUtil.GetRandomScalar(), + WeightScalar = PokeSizeUtil.GetRandomScalar(), + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + SetPINGA(pk, criteria); + pk.ResetHeight(); + pk.ResetWeight(); + pk.ResetCP(); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PB7 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, PersonalTable.GG.GetFormEntry(Species, Form)); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + + criteria.SetRandomIVs(pk, 0); + } + #endregion + + private const int CatchComboBonus = 1; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level, 0, CatchComboBonus)) + return false; + if (Form != evo.Form) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterStatic7b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterStatic7b.cs new file mode 100644 index 000000000..f560488b2 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterStatic7b.cs @@ -0,0 +1,104 @@ +namespace PKHeX.Core; + +/// +/// Generation 7 Static Encounter ( +/// +public sealed record EncounterStatic7b(GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible +{ + public int Generation => 7; + public EntityContext Context => EntityContext.Gen7b; + int ILocation.Location => Location; + public int EggLocation => 0; + public bool IsShiny => false; + public bool EggEncounter => false; + + public required ushort Species { get; init; } + public required byte Level { get; init; } + public byte Form { get; init; } + public byte Location { get; init;} + public AbilityPermission Ability => AbilityPermission.Any12; + public Ball FixedBall { get; init; } + public Shiny Shiny { get; init; } + public byte FlawlessIVCount { get; init; } + public IndividualValueSet IVs { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PB7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PB7 + { + Species = Species, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.GG[Species, Form].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)version, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)Ball.Poke, + + HeightScalar = PokeSizeUtil.GetRandomScalar(), + WeightScalar = PokeSizeUtil.GetRandomScalar(), + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + SetPINGA(pk, criteria); + pk.ResetHeight(); + pk.ResetWeight(); + pk.ResetCP(); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PB7 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, PersonalTable.GG.GetFormEntry(Species, Form)); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + + if (IVs.IsSpecified) + criteria.SetRandomIVs(pk, IVs); + else + criteria.SetRandomIVs(pk, FlawlessIVCount); + } + + #region Matching + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchEggLocation(pk)) + return false; + if (pk.Met_Location != Location) + return false; + if (pk.Met_Level != Level) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterTrade7b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterTrade7b.cs new file mode 100644 index 000000000..aa4b4d7e6 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen7b/EncounterTrade7b.cs @@ -0,0 +1,132 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 7 LGP/E Trade Encounter +/// +public sealed record EncounterTrade7b(GameVersion Version) : IEncounterable, IEncounterMatch, IFixedTrainer, IEncounterConvertible +{ + public int Generation => 7; + public EntityContext Context => EntityContext.Gen7b; + public int Location => Locations.LinkTrade6NPC; + public Shiny Shiny => Shiny.Random; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + public AbilityPermission Ability => AbilityPermission.Any12; + + public required string[] TrainerNames { get; init; } + + public required uint ID32 { get; init; } + public required byte OTGender { get; init; } + public required ushort Species { get; init; } + public required byte Form { get; init; } + public required byte Level { get; init; } + + public required IndividualValueSet IVs { get; init; } + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PB7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PB7 + { + Species = Species, + Form = Form, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang, + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Friendship = PersonalTable.GG[Species, Form].BaseFriendship, + + HeightScalar = PokeSizeUtil.GetRandomScalar(), + WeightScalar = PokeSizeUtil.GetRandomScalar(), + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + + HT_Name = tr.OT, + HT_Gender = tr.Gender, + CurrentHandler = 1, + HT_Friendship = PersonalTable.GG[Species, Form].BaseFriendship, + }; + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + pk.ResetHeight(); + pk.ResetWeight(); + pk.ResetCP(); + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PB7 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, PersonalTable.GG.GetFormEntry(Species, Form)); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + criteria.SetRandomIVs(pk, IVs); + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (IVs.IsSpecified) + { + if (!Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + } + if (pk.ID32 != ID32) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/AreaSlotType8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/AreaSlotType8.cs new file mode 100644 index 000000000..db36081f3 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/AreaSlotType8.cs @@ -0,0 +1,38 @@ +using static PKHeX.Core.AreaSlotType8; +using static PKHeX.Core.AreaWeather8; + +namespace PKHeX.Core; + +/// +/// Encounter Slot Types for +/// +public enum AreaSlotType8 : byte +{ + SymbolMain, + SymbolMain2, + SymbolMain3, + + HiddenMain, // Both HiddenMain tables include the tree/fishing slots for the area. + HiddenMain2, + + Surfing, + Surfing2, + Sky, + Sky2, + Ground, + Ground2, + Sharpedo, + + OnlyFishing, // More restricted hidden table that ignores the weather slots like grass Tentacool. + Inaccessible, // Shouldn't show up since these tables are not dumped. +} + +/// +/// Extension methods for . +/// +public static class AreaSlotType8Extensions +{ + public static bool CanCrossover(this AreaSlotType8 type) => type is not (HiddenMain or HiddenMain2 or OnlyFishing); + public static bool CanEncounterViaFishing(this AreaSlotType8 type, AreaWeather8 weather) => type is OnlyFishing || weather.HasFlag(Fishing); + public static bool CanEncounterViaCurry(this AreaSlotType8 type) => type is HiddenMain or HiddenMain2; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/AreaWeather8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/AreaWeather8.cs new file mode 100644 index 000000000..8cb1ba54a --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/AreaWeather8.cs @@ -0,0 +1,55 @@ +using System; +using static PKHeX.Core.AreaWeather8; + +namespace PKHeX.Core; + +/// +/// Encounter Conditions for +/// +/// Values above are for Shaking/Fishing hidden encounters only. +[Flags] +public enum AreaWeather8 : ushort +{ + None, + Normal = 1, + Overcast = 1 << 1, + Raining = 1 << 2, + Thunderstorm = 1 << 3, + Intense_Sun = 1 << 4, + Snowing = 1 << 5, + Snowstorm = 1 << 6, + Sandstorm = 1 << 7, + Heavy_Fog = 1 << 8, + + All = Normal | Overcast | Raining | Thunderstorm | Intense_Sun | Snowing | Snowstorm | Sandstorm | Heavy_Fog, + Stormy = Raining | Thunderstorm, + Icy = Snowing | Snowstorm, + All_IoA = Normal | Overcast | Stormy | Intense_Sun | Sandstorm | Heavy_Fog, // IoA can have everything but snow + All_CT = Normal | Overcast | Stormy | Intense_Sun | Icy | Heavy_Fog, // CT can have everything but sand + No_Sun_Sand = Normal | Overcast | Stormy | Icy | Heavy_Fog, // Everything but sand and sun + All_Ballimere = Normal | Overcast | Stormy | Intense_Sun | Snowing | Heavy_Fog, // All Ballimere Lake weather + + Shaking_Trees = 1 << 9, + Fishing = 1 << 10, + + NotWeather = Shaking_Trees | Fishing, +} + +/// +/// Extension methods for . +/// +public static class AreaWeather8Extensions +{ + public static bool IsMarkCompatible(this AreaWeather8 weather, IRibbonSetMark8 m) + { + if (m.RibbonMarkCloudy) return (weather & Overcast) != 0; + if (m.RibbonMarkRainy) return (weather & Raining) != 0; + if (m.RibbonMarkStormy) return (weather & Thunderstorm) != 0; + if (m.RibbonMarkSnowy) return (weather & Snowing) != 0; + if (m.RibbonMarkBlizzard) return (weather & Snowstorm) != 0; + if (m.RibbonMarkDry) return (weather & Intense_Sun) != 0; + if (m.RibbonMarkSandstorm) return (weather & Sandstorm) != 0; + if (m.RibbonMarkMisty) return (weather & Heavy_Fog) != 0; + return true; // no mark / etc is fine; check later. + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/Crossover8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/Crossover8.cs new file mode 100644 index 000000000..7bb46a778 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/Crossover8.cs @@ -0,0 +1,8 @@ +namespace PKHeX.Core; + +public readonly record struct Crossover8(byte L1 = 0, byte L2 = 0, byte L3 = 0, byte L4 = 0, byte L5 = 0, byte L6 = 0, byte L7 = 0) +{ + public bool IsMatchLocation(byte location) => location != 0 && L1 != 0 && ( + location == L1 || location == L2 || location == L3 || + location == L4 || location == L5 || location == L6 || location == L7); +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterArea8.cs similarity index 65% rename from PKHeX.Core/Legality/Areas/EncounterArea8.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterArea8.cs index 9fc80199c..dcc3de6b7 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterArea8.cs @@ -6,13 +6,15 @@ using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -/// /// /// encounter area /// -public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea +public sealed record EncounterArea8 : IEncounterArea, IAreaLocation { - public readonly EncounterSlot8[] Slots; + public EncounterSlot8[] Slots { get; } + public GameVersion Version { get; } + + public readonly byte Location; /// /// Slots from this area can cross over to another area, resulting in a different met location. @@ -22,7 +24,7 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea /// public readonly bool PermitCrossover; - public override bool IsMatchLocation(int location) + public bool IsMatchLocation(int location) { if (Location == location) return true; @@ -38,70 +40,7 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea return Array.IndexOf(others, (byte)location) != -1; } - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) - { - var metLocation = pk.Met_Location; - // wild area gets boosted up to level 60 post-game - var met = pk.Met_Level; - bool isBoosted = met == BoostLevel && IsBoostedArea60(Location); - if (isBoosted) - return GetBoostedMatches(chain, metLocation); - return GetUnboostedMatches(chain, met, metLocation); - } - - private IEnumerable GetUnboostedMatches(EvoCriteria[] chain, int metLevel, int metLocation) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(metLevel)) - break; - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Rotom) - break; - - if (slot.Weather is Heavy_Fog && IsWildArea8(Location)) - break; - - if (Location != metLocation && !CanCrossoverTo(Location, metLocation, slot.SlotType)) - break; - - yield return slot; - break; - } - } - } - - private IEnumerable GetBoostedMatches(EvoCriteria[] chain, int metLocation) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - // Ignore max met level comparison; we already know it is permissible to boost to level 60. - if (slot.LevelMin > BoostLevel) - break; // Can't downlevel, only boost to 60. - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Rotom) - break; - - if (Location != metLocation && !CanCrossoverTo(Location, metLocation, slot.SlotType)) - break; - - yield return slot; - break; - } - } - } - - private static bool CanCrossoverTo(int fromLocation, int toLocation, AreaSlotType8 type) + public static bool CanCrossoverTo(int fromLocation, int toLocation, AreaSlotType8 type) { if (!type.CanCrossover()) return false; @@ -122,7 +61,7 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea public static bool IsWildArea8Crown(int location) => location is >= 204 and <= 234 and not 206; // Slippery Slope -> Dyna Tree Hill, skip Freezington // Location, and areas that it can feed encounters to. - public static readonly IReadOnlyDictionary ConnectingArea8 = new Dictionary + public static readonly IReadOnlyDictionary ConnectingArea8 = new Dictionary { // Route 3 // City of Motostoke @@ -249,7 +188,7 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea /// /// Location IDs matched with possible weather types. Unlisted locations may only have Normal weather. /// - internal static readonly Dictionary WeatherbyArea = new() + internal static readonly Dictionary WeatherbyArea = new() { { 68, Intense_Sun }, // Route 6 { 88, Snowing }, // Route 8 (Steamdrift Way) @@ -305,7 +244,7 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea /// /// Weather types that may bleed into each location from adjacent locations for standard symbol encounter slots. /// - internal static readonly Dictionary WeatherBleedSymbol = new() + internal static readonly Dictionary WeatherBleedSymbol = new() { { 166, All_IoA }, // Soothing Wetlands from Forest of Focus { 170, All_IoA }, // Challenge Beach from Forest of Focus @@ -320,7 +259,7 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea /// /// Weather types that may bleed into each location from adjacent locations for surfing symbol encounter slots. /// - private static readonly Dictionary WeatherBleedSymbolSurfing = new() + private static readonly Dictionary WeatherBleedSymbolSurfing = new() { { 192, All_IoA }, // Honeycalm Sea from Training Lowlands { 224, All_CT }, // Roaring-Sea Caves from Giant's Foot @@ -329,7 +268,7 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea /// /// Weather types that may bleed into each location from adjacent locations for Sharpedo symbol encounter slots. /// - private static readonly Dictionary WeatherBleedSymbolSharpedo = new() + private static readonly Dictionary WeatherBleedSymbolSharpedo = new() { { 192, All_IoA }, // Honeycalm Sea from Training Lowlands }; @@ -337,7 +276,7 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea /// /// Weather types that may bleed into each location from adjacent locations, for standard hidden grass encounter slots. /// - private static readonly Dictionary WeatherBleedHiddenGrass = new() + private static readonly Dictionary WeatherBleedHiddenGrass = new() { { 166, All_IoA }, // Soothing Wetlands from Forest of Focus { 170, All_IoA }, // Challenge Beach from Forest of Focus @@ -345,9 +284,9 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea { 230, All_CT }, // Ballimere Lake from Giant's Bed }; - public static bool IsCrossoverBleedPossible(AreaSlotType8 type, int fromLocation, int toLocation) => true; + public static bool IsCrossoverBleedPossible(AreaSlotType8 type, int fromLocation, byte toLocation) => true; - public static bool IsWeatherBleedPossible(AreaSlotType8 type, AreaWeather8 permit, int location) => type switch + public static bool IsWeatherBleedPossible(AreaSlotType8 type, AreaWeather8 permit, byte location) => type switch { SymbolMain or SymbolMain2 or SymbolMain3 => WeatherBleedSymbol .TryGetValue(location, out var weather) && weather.HasFlag(permit), HiddenMain or HiddenMain2 => WeatherBleedHiddenGrass .TryGetValue(location, out var weather) && weather.HasFlag(permit), @@ -364,10 +303,11 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea return result; } - private EncounterArea8(ReadOnlySpan areaData, bool symbol, GameVersion game) : base(game) + private EncounterArea8(ReadOnlySpan areaData, bool symbol, GameVersion game) { PermitCrossover = symbol; Location = areaData[0]; + Version = game; Slots = ReadSlots(areaData, areaData[1]); } @@ -402,99 +342,4 @@ public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea return slots; } - - public bool HasSpecies(ushort species) - { - foreach (var slot in Slots) - { - if (slot.Species == species) - return true; - } - return false; - } -} - -/// -/// Encounter Conditions for -/// -/// Values above are for Shaking/Fishing hidden encounters only. -[Flags] -public enum AreaWeather8 : ushort -{ - None, - Normal = 1, - Overcast = 1 << 1, - Raining = 1 << 2, - Thunderstorm = 1 << 3, - Intense_Sun = 1 << 4, - Snowing = 1 << 5, - Snowstorm = 1 << 6, - Sandstorm = 1 << 7, - Heavy_Fog = 1 << 8, - - All = Normal | Overcast | Raining | Thunderstorm | Intense_Sun | Snowing | Snowstorm | Sandstorm | Heavy_Fog, - Stormy = Raining | Thunderstorm, - Icy = Snowing | Snowstorm, - All_IoA = Normal | Overcast | Stormy | Intense_Sun | Sandstorm | Heavy_Fog, // IoA can have everything but snow - All_CT = Normal | Overcast | Stormy | Intense_Sun | Icy | Heavy_Fog, // CT can have everything but sand - No_Sun_Sand = Normal | Overcast | Stormy | Icy | Heavy_Fog, // Everything but sand and sun - All_Ballimere = Normal | Overcast | Stormy | Intense_Sun | Snowing | Heavy_Fog, // All Ballimere Lake weather - - Shaking_Trees = 1 << 9, - Fishing = 1 << 10, - - NotWeather = Shaking_Trees | Fishing, -} - -/// -/// Extension methods for . -/// -public static class AreaWeather8Extensions -{ - public static bool IsMarkCompatible(this AreaWeather8 weather, IRibbonSetMark8 m) - { - if (m.RibbonMarkCloudy) return (weather & Overcast) != 0; - if (m.RibbonMarkRainy) return (weather & Raining) != 0; - if (m.RibbonMarkStormy) return (weather & Thunderstorm) != 0; - if (m.RibbonMarkSnowy) return (weather & Snowing) != 0; - if (m.RibbonMarkBlizzard) return (weather & Snowstorm) != 0; - if (m.RibbonMarkDry) return (weather & Intense_Sun) != 0; - if (m.RibbonMarkSandstorm) return (weather & Sandstorm) != 0; - if (m.RibbonMarkMisty) return (weather & Heavy_Fog) != 0; - return true; // no mark / etc is fine; check later. - } -} - -/// -/// Encounter Slot Types for -/// -public enum AreaSlotType8 : byte -{ - SymbolMain, - SymbolMain2, - SymbolMain3, - - HiddenMain, // Both HiddenMain tables include the tree/fishing slots for the area. - HiddenMain2, - - Surfing, - Surfing2, - Sky, - Sky2, - Ground, - Ground2, - Sharpedo, - - OnlyFishing, // More restricted hidden table that ignores the weather slots like grass Tentacool. - Inaccessible, // Shouldn't show up since these tables are not dumped. -} - -/// -/// Extension methods for . -/// -public static class AreaSlotType8Extensions -{ - public static bool CanCrossover(this AreaSlotType8 type) => type is not (HiddenMain or HiddenMain2 or OnlyFishing); - public static bool CanEncounterViaFishing(this AreaSlotType8 type, AreaWeather8 weather) => type is OnlyFishing || weather.HasFlag(Fishing); - public static bool CanEncounterViaCurry(this AreaSlotType8 type) => type is HiddenMain or HiddenMain2; } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterSlot8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterSlot8.cs new file mode 100644 index 000000000..b776268a7 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterSlot8.cs @@ -0,0 +1,206 @@ +using static PKHeX.Core.OverworldCorrelation8Requirement; + +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot8(EncounterArea8 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax, AreaWeather8 Weather, AreaSlotType8 Type) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IOverworldCorrelation8 +{ + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8; + public bool EggEncounter => false; + public Ball FixedBall => Ball.None; + public AbilityPermission Ability => AbilityPermission.Any12; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} [{Type}] - {Weather.ToString().Replace("_", string.Empty)}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + + // Fishing are only from the hidden table (not symbol). + public bool CanEncounterViaFishing => Type.CanEncounterViaFishing(Weather); + public bool CanEncounterViaCurry + { + get + { + if (!Type.CanEncounterViaCurry()) + return false; + + if ((Weather & AreaWeather8.All) == 0) + return false; + + if (EncounterArea8.IsWildArea(Location)) + return false; + + return true; + } + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var form = GetWildForm(Form); + var pk = new PK8 + { + Species = Species, + Form = form, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)Ball.Poke, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + OT_Friendship = PersonalTable.SWSH[Species, form].BaseFriendship, + }; + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + + bool symbol = Parent.PermitCrossover; + if (!symbol && Location is 30 or 54 && (Weather & AreaWeather8.Fishing) == 0) + pk.RibbonMarkCurry = true; + + if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) + pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel; + pk.ResetPartyStats(); + return pk; + } + + private byte GetWildForm(byte form) + { + if (form == EncounterUtil1.FormRandom) // flagged as totally random + return (byte)Util.Rand.Next(PersonalTable.SWSH[Species].FormCount); + return form; + } + + #endregion + + private void SetPINGA(PK8 pk, EncounterCriteria criteria) + { + bool symbol = Parent.PermitCrossover; + var c = symbol ? EncounterCriteria.Unrestricted : criteria; + + var req = GetRequirement(pk); + if (req != MustHave) + { + pk.EncryptionConstant = Util.Rand32(); + return; + } + // Don't bother honoring shiny state. + Overworld8RNG.ApplyDetails(pk, c, Shiny.Random); + } + + public OverworldCorrelation8Requirement GetRequirement(PKM pk) + { + if (Parent.PermitCrossover) + return MustHave; // symbol walking overworld + + bool curry = pk is IRibbonSetMark8 {RibbonMarkCurry: true} || (pk.Species == (int)Core.Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon:(int)RibbonIndex.MarkCurry}); + if (curry) + return MustNotHave; + + // Tree encounters are generated via the global seed, not the u32 + if ((Weather & AreaWeather8.Shaking_Trees) != 0) + { + // Some tree encounters are present in the regular encounters. + return Weather == AreaWeather8.Shaking_Trees + ? MustNotHave + : CanBeEither; + } + + return MustHave; + } + + public bool IsOverworldCorrelationCorrect(PKM pk) + { + var flawless = GetFlawlessIVCount(pk.Met_Level); + return Overworld8RNG.ValidateOverworldEncounter(pk, flawless: flawless); + } + + private int GetFlawlessIVCount(int met) + { + const int none = 0; + const int any023 = -1; + + // Brilliant encounters are boosted to max level for the slot. + if (met < LevelMax) + return none; + + if (Parent.PermitCrossover) + return any023; // Symbol + if ((Weather & AreaWeather8.Fishing) != 0) + return any023; // Fishing + return none; // Hidden + } + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Form != evo.Form && Species is not (int)Core.Species.Rotom) + return false; + + var metLocation = pk.Met_Location; + if (Location != metLocation && !EncounterArea8.CanCrossoverTo(Location, metLocation, Type)) + return false; + + var met = pk.Met_Level; + if (met == EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60(Location)) + return true; + + if (!this.IsLevelWithinRange(met)) + return false; + + if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsWildArea8(Location)) + return false; // Heavy Fog not available until post-game, which would return true above. + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + + if (pk is IRibbonSetMark8 m) + { + if (m.RibbonMarkCurry && (Weather & AreaWeather8.All) == 0) + return EncounterMatchRating.DeferredErrors; + if (m.RibbonMarkFishing && (Weather & AreaWeather8.Fishing) == 0) + return EncounterMatchRating.DeferredErrors; + + // Check if it has a mark and the weather does not permit the mark. + // Tree/Fishing slots should be deferred here and are checked later. + if (!Weather.IsMarkCompatible(m)) + return EncounterMatchRating.DeferredErrors; + + // Galar Mine hidden encounters can only be found via Curry or Fishing. + if (Location is (30 or 54) && Type is AreaSlotType8.HiddenMain && !m.RibbonMarkCurry && !Type.CanEncounterViaFishing(Weather)) + return EncounterMatchRating.DeferredErrors; + } + + var req = GetRequirement(pk); + return req switch + { + MustHave when !IsOverworldCorrelationCorrect(pk) => EncounterMatchRating.DeferredErrors, + MustNotHave when IsOverworldCorrelationCorrect(pk) => EncounterMatchRating.DeferredErrors, + _ => EncounterMatchRating.Match, + }; + #endregion + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8.cs new file mode 100644 index 000000000..534ea5bbe --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8.cs @@ -0,0 +1,203 @@ +using static PKHeX.Core.OverworldCorrelation8Requirement; + +namespace PKHeX.Core; + +/// +/// Generation 8 Static Encounter +/// +public sealed record EncounterStatic8(GameVersion Version = GameVersion.SWSH) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMoveset, IRelearn, + IFlawlessIVCount, IFixedGender, IDynamaxLevelReadOnly, IGigantamaxReadOnly, IOverworldCorrelation8, IFatefulEncounterReadOnly +{ + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8; + int ILocation.Location => Location; + public bool Gift => FixedBall != Ball.None; + public bool IsShiny => Shiny == Shiny.Always; + public bool EggEncounter => false; + int ILocation.EggLocation => 0; + + public required ushort Location { get; init; } + public required ushort Species { get; init; } + public byte Form { get; init; } + public required byte Level { get; init; } + public Moveset Moves { get; init; } + public Moveset Relearn { get; init; } + public IndividualValueSet IVs { get; init; } + public Crossover8 Crossover { get; init; } + public AreaWeather8 Weather { get; init; } = AreaWeather8.Normal; + public byte DynamaxLevel { get; init; } + public Nature Nature { get; init; } + public Shiny Shiny { get; init; } + public AbilityPermission Ability { get; init; } + public sbyte Gender { get; init; } = -1; + public Ball FixedBall { get; init; } + public byte FlawlessIVCount { get; init; } + public bool ScriptedNoMarks { get; init; } + public bool CanGigantamax { get; init; } + public bool FatefulEncounter { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public bool IsOverworldCorrelation + { + get + { + if (Gift) + return false; // gifts can have any 128bit seed from overworld + if (ScriptedNoMarks) + return false; // scripted encounters don't act as saved spawned overworld encounters + return true; + } + } + + public OverworldCorrelation8Requirement GetRequirement(PKM pk) => IsOverworldCorrelation + ? MustHave + : MustNotHave; + + public bool IsOverworldCorrelationCorrect(PKM pk) + { + return Overworld8RNG.ValidateOverworldEncounter(pk, Shiny == Shiny.Random ? Shiny.FixedValue : Shiny, FlawlessIVCount); + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK8 + { + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke), + FatefulEncounter = FatefulEncounter, + + ID32 = tr.ID32, + Version = (byte)version, + Language = lang, + OT_Gender = tr.Gender, + OT_Name = tr.OT, + OT_Friendship = PersonalTable.SWSH[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + HeightScalar = PokeSizeUtil.GetRandomScalar(), + WeightScalar = PokeSizeUtil.GetRandomScalar(), + }; + + SetPINGA(pk, criteria); + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PK8 pk, EncounterCriteria criteria) + { + if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) + pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel; + + var req = GetRequirement(pk); + if (req != MustHave) + { + pk.EncryptionConstant = Util.Rand32(); + return; + } + var shiny = Shiny == Shiny.Random ? Shiny.FixedValue : Shiny; + Overworld8RNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount); + } + + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchLevel(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) + return false; + if (pk.Met_Level < EncounterArea8.BoostLevel && Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + return true; + } + + private static bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchLocation(PKM pk) + { + var met = pk.Met_Location; + if (met == Location) + return true; + if ((uint)met > byte.MaxValue) + return false; + return Crossover.IsMatchLocation((byte)met); + } + + private bool IsMatchLevel(PKM pk) + { + var met = pk.Met_Level; + var lvl = Level; + if (met == lvl) + return true; + if (lvl < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60(Location)) + return met == EncounterArea8.BoostLevel; + return false; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + + var req = GetRequirement(pk); + bool correlation = IsOverworldCorrelationCorrect(pk); + if ((req == MustHave) != correlation) + return EncounterMatchRating.DeferredErrors; + + // Only encounter slots can have these marks; defer for collisions. + if (pk.Species == (int)Core.Species.Shedinja) + { + // Loses Mark on evolution to Shedinja, but not affixed ribbon value. + return pk switch + { + IRibbonSetMark8 { RibbonMarkCurry: true } => EncounterMatchRating.DeferredErrors, + PK8 { AffixedRibbon: (int)RibbonIndex.MarkCurry } => EncounterMatchRating.Deferred, + _ => EncounterMatchRating.Match, + }; + } + + if (pk is IRibbonSetMark8 m && (m.RibbonMarkCurry || m.RibbonMarkFishing || !Weather.IsMarkCompatible(m))) + return EncounterMatchRating.DeferredErrors; + + return EncounterMatchRating.Match; + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8N.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8N.cs similarity index 89% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8N.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8N.cs index f89708ef5..a3e75dbd9 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8N.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8N.cs @@ -1,5 +1,6 @@ using System; using static PKHeX.Core.Encounters8Nest; +using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; @@ -30,7 +31,7 @@ public sealed record EncounterStatic8N : EncounterStatic8Nest public static EncounterStatic8N Read(ReadOnlySpan data, GameVersion game) => new(data[6], data[7], data[8], data[9], game) { - Species = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data), + Species = ReadUInt16LittleEndian(data), Form = data[2], Gender = (sbyte)data[3], Ability = (AbilityPermission)data[4], @@ -46,7 +47,7 @@ public sealed record EncounterStatic8N : EncounterStatic8Nest 55, 60, // 4 }; - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + protected override bool IsMatchLevel(PKM pk) { var met = pk.Met_Level; var metLevel = met - 15; @@ -102,12 +103,4 @@ public sealed record EncounterStatic8N : EncounterStatic8Nest return false; return Array.IndexOf(NestLocations, (byte)loc) >= 0; } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (pk.FlawlessIVCount < FlawlessIVCount) - return false; - - return base.IsMatchExact(pk, evo); - } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8NC.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8NC.cs similarity index 76% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8NC.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8NC.cs index 429bb1ce9..32df6fdbf 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8NC.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8NC.cs @@ -6,15 +6,20 @@ namespace PKHeX.Core; /// Generation 8 Nest Encounter (Distributed Crystal Data) /// /// -public sealed record EncounterStatic8NC(GameVersion Version) : EncounterStatic8Nest(Version) +public sealed record EncounterStatic8NC(GameVersion Version) : EncounterStatic8Nest(Version), ILocation { + int ILocation.Location => Watchtower; + public const ushort Location = Watchtower; + + protected override ushort GetLocation() => Location; + protected override bool IsMatchLocation(PKM pk) { var loc = pk.Met_Location; return loc is SharedNest or Watchtower; } - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + protected override bool IsMatchLevel(PKM pk) { var lvl = pk.Met_Level; if (lvl == Level) diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8ND.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8ND.cs similarity index 78% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8ND.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8ND.cs index 4f59121b6..a9a100a94 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8ND.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8ND.cs @@ -1,5 +1,6 @@ using System; using static PKHeX.Core.Encounters8Nest; +using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; @@ -36,15 +37,15 @@ public sealed record EncounterStatic8ND : EncounterStatic8Nest Shiny.Random, }; - var move1 = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data[4..]); - var move2 = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data[6..]); - var move3 = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data[8..]); - var move4 = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data[10..]); + var move1 = ReadUInt16LittleEndian(data[4..]); + var move2 = ReadUInt16LittleEndian(data[6..]); + var move3 = ReadUInt16LittleEndian(data[8..]); + var move4 = ReadUInt16LittleEndian(data[10..]); var moves = new Moveset(move1, move2, move3, move4); return new EncounterStatic8ND(data[12], dlvl, flawless, data[15], game) { - Species = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data), + Species = ReadUInt16LittleEndian(data), Form = data[2], Ability = (AbilityPermission)data[3], CanGigantamax = gmax, @@ -53,7 +54,7 @@ public sealed record EncounterStatic8ND : EncounterStatic8Nest EncounterArea8.IsWildArea8(loc), }; } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (pk.FlawlessIVCount < FlawlessIVCount) - return false; - - return base.IsMatchExact(pk, evo); - } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs new file mode 100644 index 000000000..6304eee2f --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs @@ -0,0 +1,232 @@ +using System; +using static PKHeX.Core.Encounters8Nest; +using static PKHeX.Core.AbilityPermission; + +namespace PKHeX.Core; + +/// +/// Generation 8 Nest Encounter (Raid) +/// +public abstract record EncounterStatic8Nest(GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMoveset, + IFlawlessIVCount, IFixedGender, IDynamaxLevelReadOnly, IGigantamaxReadOnly where T : EncounterStatic8Nest +{ + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8; + public static Func? VerifyCorrelation { private get; set; } + public static Action? GenerateData { private get; set; } + + int ILocation.Location => SharedNest; + private const ushort Location = SharedNest; + + public bool IsShiny => Shiny == Shiny.Always; + public bool EggEncounter => false; + int ILocation.EggLocation => 0; + public Ball FixedBall => Ball.None; + + public ushort Species { get; init; } + public byte Form { get; init; } + public virtual byte Level { get; init; } + public Moveset Moves { get; init; } + public IndividualValueSet IVs { get; init; } + public byte DynamaxLevel { get; init; } + public Shiny Shiny { get; init; } + public AbilityPermission Ability { get; init; } + public sbyte Gender { get; init; } = -1; + public byte FlawlessIVCount { get; init; } + public bool CanGigantamax { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + public virtual byte LevelMin => Level; + public virtual byte LevelMax => Level; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK8 + { + Species = Species, + CurrentLevel = Level, + Met_Location = GetLocation(), + Met_Level = Level, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)Ball.Poke, + + ID32 = tr.ID32, + Version = (byte)version, + Language = lang, + OT_Gender = tr.Gender, + OT_Name = tr.OT, + OT_Friendship = PersonalTable.SWSH[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + HeightScalar = PokeSizeUtil.GetRandomScalar(), + WeightScalar = PokeSizeUtil.GetRandomScalar(), + }; + + SetPINGA(pk, criteria); + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + pk.ResetPartyStats(); + + return pk; + } + + protected virtual ushort GetLocation() => Location; + + private void SetPINGA(PK8 pk, EncounterCriteria criteria) + { + if (GenerateData != null) + { + GenerateData(pk, (T)this, criteria); + return; + } + + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature.Random); + int ability = criteria.GetAbilityFromNumber(Ability); + PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender); + criteria.SetRandomIVs(pk); + pk.StatNature = pk.Nature; + pk.EncryptionConstant = Util.Rand32(); + if (Species == (int)Core.Species.Toxtricity) + { + while (true) + { + var result = EvolutionMethod.GetAmpLowKeyResult(pk.Nature); + if (result == pk.Form) + break; + pk.Nature = Util.Rand.Next(25); + } + + // Might be originally generated with a Neutral nature, then above logic changes to another. + // Realign the stat nature to Serious mint. + if (pk.Nature != pk.StatNature && ((Nature)pk.StatNature).IsNeutral()) + pk.StatNature = (int)Nature.Serious; + } + var pid = pk.PID; + RaidRNG.ForceShinyState(pk, Shiny == Shiny.Always, ref pid); + pk.PID = pid; + } + + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) + return false; + + // Required Ability + if (Ability == OnlyHidden && pk.AbilityNumber != 4) + return false; // H + + if (Version != GameVersion.SWSH && pk.Version != (int)Version && pk.Met_Location != SharedNest) + return false; + + if (VerifyCorrelation != null && !VerifyCorrelation(pk, (T)this)) + return false; + + if (pk is IRibbonSetMark8 { HasMarkEncounter8: true }) + return false; + if (pk.Species == (int)Core.Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon: >= (int)RibbonIndex.MarkLunchtime }) + return false; + + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk)) + return false; + if (!IsMatchGender(pk)) + return false; + if (!IsMatchForm(pk, evo)) + return false; + if (!IsMatchIVs(pk)) + return false; + + if (pk.FlawlessIVCount < FlawlessIVCount) + return false; + + return true; + } + + protected virtual bool IsMatchLocation(PKM pk) => Location == pk.Met_Location; + private static bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + protected virtual bool IsMatchLevel(PKM pk) => pk.Met_Level == Level; + private bool IsMatchGender(PKM pk) => Gender == -1 || Gender == pk.Gender; + private bool IsMatchForm(PKM pk, EvoCriteria evo) => Form == evo.Form || FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context); + private bool IsMatchIVs(PKM pk) + { + if (!IVs.IsSpecified) + return true; // nothing to check, IVs are random + return Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk); + } + + public virtual EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return IsMatchDeferred(pk); + } + + private EncounterMatchRating IsMatchDeferred(PKM pk) + { + if (Ability != Any12H) + { + // HA-Only is a strict match. Ability Capsule and Patch can potentially change these. + var num = pk.AbilityNumber; + if (num == 4) + { + if (Ability is not OnlyHidden && !AbilityVerifier.CanAbilityPatch(8, PersonalTable.SWSH.GetFormEntry(Species, Form), pk.Species)) + return EncounterMatchRating.DeferredErrors; + } + else if (Ability.IsSingleValue(out int index) && 1 << index != num) // Fixed regular ability + { + if (Ability is OnlyFirst or OnlySecond && !AbilityVerifier.CanAbilityCapsule(8, PersonalTable.SWSH.GetFormEntry(Species, Form))) + return EncounterMatchRating.DeferredErrors; + } + } + + return EncounterMatchRating.Match; + } + + protected bool IsMatchPartial(PKM pk) + { + if (pk is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pk.Species, pk.Form, Species, Form)) + return true; + if (Species == (int)Core.Species.Alcremie && pk is IFormArgument { FormArgument: not 0 }) + return true; + if (Species == (int)Core.Species.Runerigus && pk is IFormArgument { FormArgument: not 0 }) + return true; + + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + + switch (Shiny) + { + case Shiny.Never when pk.IsShiny: + case Shiny.Always when !pk.IsShiny: + return true; + } + + return false; + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8U.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs similarity index 53% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8U.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs index 3e78f7320..235b68f0b 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8U.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs @@ -1,5 +1,6 @@ using System; using static PKHeX.Core.Encounters8Nest; +using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; @@ -7,9 +8,10 @@ namespace PKHeX.Core; /// Generation 8 Nest Encounter (Max Raid) Underground ///
/// -public sealed record EncounterStatic8U : EncounterStatic8Nest +public sealed record EncounterStatic8U : EncounterStatic8Nest, ILocation { - public override int Location => MaxLair; + int ILocation.Location => MaxLair; + private const ushort Location = MaxLair; public EncounterStatic8U(ushort species, byte form, byte level) : base(GameVersion.SWSH) // no difference in met location for hosted raids { @@ -22,11 +24,11 @@ public sealed record EncounterStatic8U : EncounterStatic8Nest public static EncounterStatic8U Read(ReadOnlySpan data) { - var spec = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data); - var move1 = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data[4..]); - var move2 = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data[6..]); - var move3 = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data[8..]); - var move4 = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(data[10..]); + var spec = ReadUInt16LittleEndian(data); + var move1 = ReadUInt16LittleEndian(data[4..]); + var move2 = ReadUInt16LittleEndian(data[6..]); + var move3 = ReadUInt16LittleEndian(data[8..]); + var move4 = ReadUInt16LittleEndian(data[10..]); var moves = new Moveset(move1, move2, move3, move4); return new EncounterStatic8U(spec, data[2], data[3]) @@ -36,15 +38,11 @@ public sealed record EncounterStatic8U : EncounterStatic8Nest Moves = moves, }; } - - public override bool IsMatchExact(PKM pk, EvoCriteria evo) - { - if (pk.FlawlessIVCount < FlawlessIVCount) - return false; - - return base.IsMatchExact(pk, evo); - } + protected override ushort GetLocation() => Location; // no downleveling, unlike all other raids - protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) => pk.Met_Level == Level; + protected override bool IsMatchLevel(PKM pk) => pk.Met_Level == Level; + protected override bool IsMatchLocation(PKM pk) => Location == pk.Met_Location; + + public bool IsShinyXorValid(ushort pkShinyXor) => pkShinyXor is > 15 or 1; } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterTrade8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterTrade8.cs new file mode 100644 index 000000000..07364eec0 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterTrade8.cs @@ -0,0 +1,210 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 8 Trade Encounter +/// +public sealed record EncounterTrade8 : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible, IDynamaxLevelReadOnly, IRelearn, IMemoryOTReadOnly, IFlawlessIVCount, IFixedGender +{ + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8; + public int Location => Locations.LinkTrade6NPC; + public Moveset Relearn { get; init; } + + public ushort OT_TextVar { get; } + public byte OT_Memory { get; } + public byte OT_Feeling { get; } + public byte OT_Intensity { get; } + public byte DynamaxLevel { get; init; } + public byte FlawlessIVCount { get; init; } + public Shiny Shiny { get; } + + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + public bool IsFixedNickname { get; } + + private string[] TrainerNames { get; } + private string[] Nicknames { get; } + + public Nature Nature { get; init; } // always set by either constructor or initializer + public required uint ID32 { get; init; } + public required AbilityPermission Ability { get; init; } + public sbyte Gender { get; init; } + public required byte OTGender { get; init; } + + public required IndividualValueSet IVs { get; init; } + + public ushort Species { get; } + public byte Form { get; init; } + + public byte Level { get; } + public GameVersion Version { get; } + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public EncounterTrade8(ReadOnlySpan names, byte index, GameVersion game, ushort species, byte level, byte memory, ushort arg, byte feel, byte intensity) + { + Version = game; + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + Species = species; + Level = level; + Shiny = Shiny.Never; + + OT_Memory = memory; + OT_TextVar = arg; + OT_Feeling = feel; + OT_Intensity = intensity; + IsFixedNickname = true; + } + + public EncounterTrade8(string[] trainerNames, GameVersion game, ushort species, byte level, byte memory, ushort arg, byte feel, byte intensity) + { + Version = game; + Nicknames = Array.Empty(); + TrainerNames = trainerNames; + Species = species; + Level = level; + Shiny = Shiny.Random; + + OT_Memory = memory; + OT_TextVar = arg; + OT_Feeling = feel; + OT_Intensity = intensity; + IsFixedNickname = false; + Gender = -1; + Nature = Nature.Random; + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK8 + { + PID = Util.Rand32(), + EncryptionConstant = Util.Rand32(), + Species = Species, + Form = Form, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang, + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Memory = OT_Memory, + OT_Intensity = OT_Intensity, + OT_Feeling = OT_Feeling, + OT_TextVar = OT_TextVar, + OT_Friendship = PersonalTable.SWSH[Species, Form].BaseFriendship, + + IsNicknamed = IsFixedNickname, + Nickname = IsFixedNickname ? Nicknames[lang] : SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + DynamaxLevel = DynamaxLevel, + HT_Name = tr.OT, + HT_Gender = tr.Gender, + HT_Language = (byte)tr.Language, + CurrentHandler = 1, + HT_Friendship = PersonalTable.SWSH[Species, Form].BaseFriendship, + }; + if (Shiny == Shiny.Never && pk.IsShiny) + pk.PID ^= 0x1000_0000u; + pk.SetRelearnMoves(Relearn); + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + SetPINGA(pk, criteria); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PK8 pk, EncounterCriteria criteria) + { + var pi = PersonalTable.SWSH[Species, Form]; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature); + int ability = criteria.GetAbilityFromNumber(Ability); + PIDGenerator.SetRandomWildPID(pk, Generation, nature, ability, gender); + pk.Nature = pk.StatNature = nature; + pk.Gender = gender; + pk.RefreshAbility(ability); + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) => (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (IVs.IsSpecified) + { + if (!Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + } + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (pk.ID32 != ID32) + return false; + if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) + return false; + if (pk.FlawlessIVCount < FlawlessIVCount) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is PB8) + expect = Locations.Default8bNone; + return pk.Egg_Location == expect; + } + + private bool IsMatchNatureGenderShiny(PKM pk) + { + if (!Shiny.IsValid(pk)) + return false; + if (Nature != Nature.Random && pk.Nature != (int)Nature) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/IOverworldCorrelation8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/IOverworldCorrelation8.cs new file mode 100644 index 000000000..0672fcb18 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/IOverworldCorrelation8.cs @@ -0,0 +1,7 @@ +namespace PKHeX.Core; + +public interface IOverworldCorrelation8 +{ + OverworldCorrelation8Requirement GetRequirement(PKM pk); + bool IsOverworldCorrelationCorrect(PKM pk); +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/OverworldCorrelation8Requirement.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/OverworldCorrelation8Requirement.cs new file mode 100644 index 000000000..78a410271 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/OverworldCorrelation8Requirement.cs @@ -0,0 +1,8 @@ +namespace PKHeX.Core; + +public enum OverworldCorrelation8Requirement +{ + CanBeEither, + MustHave, + MustNotHave, +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8a.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterArea8a.cs similarity index 58% rename from PKHeX.Core/Legality/Areas/EncounterArea8a.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterArea8a.cs index 266c842c0..07b6e870b 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8a.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterArea8a.cs @@ -1,60 +1,39 @@ using System; -using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -/// /// /// encounter area /// -public sealed record EncounterArea8a : EncounterArea +public sealed record EncounterArea8a : IEncounterArea, IAreaLocation { - public readonly EncounterSlot8a[] Slots; - private readonly byte[] Locations; + public EncounterSlot8a[] Slots { get; } + public GameVersion Version => GameVersion.PLA; - public override bool IsMatchLocation(int location) + private readonly byte[] Locations; + public readonly SlotType Type; + + public int Location => Locations[0]; + + public bool IsMatchLocation(int location) { return Array.IndexOf(Locations, (byte)location) != -1; } - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) => GetMatches(chain, pk.Met_Level); - - private IEnumerable GetMatches(EvoCriteria[] chain, int metLevel) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(metLevel)) - break; - - if (slot.Form != evo.Form && slot.Species is not ((int)Species.Rotom or (int)Species.Burmy or (int)Species.Wormadam)) - break; - - yield return slot; - break; - } - } - } - - public static EncounterArea8a[] GetAreas(BinLinkerAccessor input, GameVersion game) + public static EncounterArea8a[] GetAreas(BinLinkerAccessor input) { var result = new EncounterArea8a[input.Length]; for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea8a(input[i], game); + result[i] = new EncounterArea8a(input[i]); return result; } - private EncounterArea8a(ReadOnlySpan areaData, GameVersion game) : base(game) + private EncounterArea8a(ReadOnlySpan areaData) { // Area Metadata int locationCount = areaData[0]; Locations = areaData.Slice(1, locationCount).ToArray(); - Location = Locations[0]; int align = (locationCount + 1); if ((align & 1) == 1) diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs similarity index 57% rename from PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs index cfad79987..45c39c093 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs @@ -5,49 +5,70 @@ namespace PKHeX.Core; /// /// Encounter Slot found in . /// -/// -public sealed record EncounterSlot8a : EncounterSlot, IAlphaReadOnly, IMasteryInitialMoveShop8 +/// 0=Never, 1=Random, 2=Guaranteed +/// +public sealed record EncounterSlot8a(EncounterArea8a Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax, byte AlphaType, byte FlawlessIVCount, Gender Gender) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IAlphaReadOnly, IMasteryInitialMoveShop8, IFlawlessIVCount { - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8a; - public SlotType Type => Area.Type; + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8a; + public bool EggEncounter => false; + public AbilityPermission Ability => AbilityPermission.Any12; + public Ball FixedBall => Ball.None; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; - public bool IsAlpha { get => AlphaType is not 0; set => throw new InvalidOperationException("Do not mutate this field."); } - public byte FlawlessIVCount { get; } - public Gender Gender { get; } - public byte AlphaType { get; } // 0=Never, 1=Random, 2=Guaranteed + public bool IsAlpha => AlphaType is not 0; - public EncounterSlot8a(EncounterArea8a area, ushort species, byte form, byte min, byte max, byte alphaType, byte flawlessIVs, Gender gender) : base(area, species, form, min, max) - { - AlphaType = alphaType; - FlawlessIVCount = flawlessIVs; - Gender = gender; - } + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; public bool HasAlphaMove => IsAlpha && Type is not SlotType.Landmark; - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PA8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PA8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) { - base.ApplyDetails(sav, criteria, pk); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PA8 + { + Language = lang, + Species = Species, + Form = Form, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.LA[Species, Form].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (int)Version, + IsAlpha = IsAlpha, + Ball = (int)Ball.LAPoke, - var pa = (PA8)pk; - if (IsAlpha) - pa.Scale = pa.HeightScalar = pa.WeightScalar = 255; - pa.ResetHeight(); - pa.ResetWeight(); + HeightScalar = IsAlpha ? (byte)255 : PokeSizeUtil.GetRandomScalar(), + WeightScalar = IsAlpha ? (byte)255 : PokeSizeUtil.GetRandomScalar(), + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + SetPINGA(pk, criteria); + pk.Scale = pk.HeightScalar; + pk.ResetHeight(); + pk.ResetWeight(); + SetEncounterMoves(pk, LevelMin); + pk.ResetPartyStats(); + return pk; } - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + private void SetPINGA(PA8 pk, EncounterCriteria criteria) { - base.SetPINGA(pk, criteria); - if (Gender != Gender.Random) - pk.Gender = (int)Gender; - var para = GetParams(); while (true) { var (_, slotSeed) = Overworld8aRNG.ApplyDetails(pk, criteria, para, HasAlphaMove); - if (LevelMin != LevelMax) + if (this.IsRandomLevel()) { var lvl = Overworld8aRNG.GetRandomLevel(slotSeed, LevelMin, LevelMax); if (criteria.ForceMinLevelRange && lvl != LevelMin) @@ -58,14 +79,37 @@ public sealed record EncounterSlot8a : EncounterSlot, IAlphaReadOnly, IMasteryIn } } - protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) + private OverworldParam8a GetParams() => new() + { + Shiny = Shiny, + IsAlpha = IsAlpha, + FlawlessIVs = FlawlessIVCount, + RollCount = GetRollCount(Type), + GenderRatio = Gender switch + { + Gender.Male => PersonalInfo.RatioMagicMale, + Gender.Female => PersonalInfo.RatioMagicFemale, + _ => PersonalTable.LA[Species, Form].Gender, + }, + }; + + // hardcoded 7 to assume max dex progress + shiny charm. + private const int MaxRollCount = 7; + + private static byte GetRollCount(SlotType type) => (byte)(MaxRollCount + type switch + { + SlotType.OverworldMMO => 12, + SlotType.OverworldMass => 25, + _ => 0, + }); + + private void SetEncounterMoves(PKM pk, int level) { var pa8 = (PA8)pk; Span moves = stackalloc ushort[4]; var (learn, mastery) = GetLevelUpInfo(); LoadInitialMoveset(pa8, moves, learn, level); pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); pa8.SetEncounterMasteryFlags(moves, mastery, level); if (pa8.AlphaMove != 0) pa8.SetMasteryFlagMove(pa8.AlphaMove); @@ -85,29 +129,27 @@ public sealed record EncounterSlot8a : EncounterSlot, IAlphaReadOnly, IMasteryIn } public (Learnset Learn, Learnset Mastery) GetLevelUpInfo() => LearnSource8LA.GetLearnsetAndMastery(Species, Form); + #endregion - protected override void SetFormatSpecificData(PKM pk) + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) { - var pa8 = (PA8)pk; - if (IsAlpha) - pa8.IsAlpha = true; - pa8.Scale = pa8.HeightScalar; + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + if (Form != evo.Form && Species is not ((int)Core.Species.Rotom or (int)Core.Species.Burmy or (int)Core.Species.Wormadam)) + return false; + return true; } - protected override void ApplyDetailsBall(PKM pk) => pk.Ball = (int)Ball.LAPoke; + private bool IsDeferredWurmple(PKM pk) => Species == (int)Core.Species.Wurmple && pk.Species != (int)Core.Species.Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk); - public override EncounterMatchRating GetMatchRating(PKM pk) + public EncounterMatchRating GetMatchRating(PKM pk) { if (Gender is not Gender.Random && pk.Gender != (int)Gender) return EncounterMatchRating.PartialMatch; - - var result = GetMatchRatingInternal(pk); - var orig = base.GetMatchRating(pk); - return result > orig ? result : orig; - } - - private EncounterMatchRating GetMatchRatingInternal(PKM pk) - { + if (IsDeferredWurmple(pk)) + return EncounterMatchRating.PartialMatch; if (!MarkRules.IsMarkValidAlpha(pk, IsAlpha)) return EncounterMatchRating.DeferredErrors; if (FlawlessIVCount is not 0 && pk.FlawlessIVCount < FlawlessIVCount) @@ -166,7 +208,7 @@ public sealed record EncounterSlot8a : EncounterSlot, IAlphaReadOnly, IMasteryIn if (pk is not IMoveShop8Mastery p) return true; // Can't check. - bool allowAlphaPurchaseBug = Area.Type is not SlotType.OverworldMMO; // Everything else Alpha is pre-1.1 + bool allowAlphaPurchaseBug = Type is not SlotType.OverworldMMO; // Everything else Alpha is pre-1.1 var level = pk.Met_Level; var (learn, mastery) = GetLevelUpInfo(); ushort alpha = pk is PA8 pa ? pa.AlphaMove : (ushort)0; @@ -186,41 +228,5 @@ public sealed record EncounterSlot8a : EncounterSlot, IAlphaReadOnly, IMasteryIn return p.IsValidMasteredEncounter(moves, learn, mastery, level, alpha, allowAlphaPurchaseBug); } - - private OverworldParam8a GetParams() - { - var gender = GetGenderRatio(); - return new OverworldParam8a - { - IsAlpha = IsAlpha, - FlawlessIVs = FlawlessIVCount, - Shiny = Shiny, - RollCount = GetRollCount(Type), - GenderRatio = gender, - }; - } - - private byte GetGenderRatio() => Gender switch - { - Gender.Male => PersonalInfo.RatioMagicMale, - Gender.Female => PersonalInfo.RatioMagicFemale, - _ => GetGenderRatioPersonal(), - }; - - private byte GetGenderRatioPersonal() - { - var pt = PersonalTable.LA; - var entry = pt.GetFormEntry(Species, Form); - return entry.Gender; - } - - // hardcoded 7 to assume max dex progress + shiny charm. - private const int MaxRollCount = 7; - - private static byte GetRollCount(SlotType type) => (byte)(MaxRollCount + type switch - { - SlotType.OverworldMMO => 12, - SlotType.OverworldMass => 25, - _ => 0, - }); + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8a.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8a.cs new file mode 100644 index 000000000..1d7c06b22 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8a.cs @@ -0,0 +1,272 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 8 Static Encounter +/// +public sealed record EncounterStatic8a + : IEncounterable, IEncounterMatch, IEncounterConvertible, IAlphaReadOnly, IMasteryInitialMoveShop8, IScaledSizeReadOnly, IMoveset, IFlawlessIVCount, IFatefulEncounterReadOnly, IFixedGender +{ + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8a; + public GameVersion Version => GameVersion.PLA; + public int EggLocation => 0; + int ILocation.Location => Location; + public bool IsShiny => Shiny == Shiny.Always; + public bool EggEncounter => false; + public AbilityPermission Ability => AbilityPermission.Any12; + + public ushort Species { get; } + public byte Form { get; } + public byte HeightScalar { get; } + public byte WeightScalar { get; } + public byte LevelMin { get; } + public byte LevelMax { get; init; } + public sbyte Gender { get; init; } = -1; + public required byte Location { get; init; } + public byte FlawlessIVCount { get; init; } + public Shiny Shiny { get; init; } = Shiny.Never; + public bool IsAlpha { get; init; } + public bool FatefulEncounter { get; init; } + public Ball FixedBall { get; init; } + public Moveset Moves { get; init; } + public EncounterStatic8aCorrelation Method { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + + public bool HasFixedHeight => HeightScalar != NoScalar; + public bool HasFixedWeight => WeightScalar != NoScalar; + + private const byte NoScalar = 0; + + public EncounterStatic8a(ushort species, byte form, byte level, byte heightScalar = NoScalar, byte weightScalar = NoScalar) + { + Species = species; + Form = form; + HeightScalar = heightScalar; + WeightScalar = weightScalar; + LevelMin = LevelMax = level; + } + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PA8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PA8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PA8 + { + Language = lang, + Species = Species, + Form = Form, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.LA[Species, Form].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (int)Version, + IsAlpha = IsAlpha, + Ball = (byte)(FixedBall == Ball.None ? Ball.LAPoke : FixedBall), + + HeightScalar = HasFixedHeight ? HeightScalar : PokeSizeUtil.GetRandomScalar(), + WeightScalar = HasFixedWeight ? WeightScalar : PokeSizeUtil.GetRandomScalar(), + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + SetPINGA(pk, criteria); + pk.Scale = pk.HeightScalar; + pk.ResetHeight(); + pk.ResetWeight(); + SetEncounterMoves(pk, (byte)pk.Met_Level); + + if (IsAlpha) + pk.IsAlpha = true; + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var para = GetParams(); + var (_, slotSeed) = Overworld8aRNG.ApplyDetails(pk, criteria, para, IsAlpha); + // Phione and Zorua have random levels; follow the correlation instead of giving the lowest level. + if (LevelMin != LevelMax) + pk.CurrentLevel = pk.Met_Level = Overworld8aRNG.GetRandomLevel(slotSeed, LevelMin, LevelMax); + + if (Method == EncounterStatic8aCorrelation.Fixed) + pk.EncryptionConstant = Util.Rand32(); + } + + private void SetEncounterMoves(PA8 pk, int level) + { + Span moves = stackalloc ushort[4]; + var (learn, mastery) = GetLevelUpInfo(); + LoadInitialMoveset(pk, moves, learn, level); + pk.SetMoves(moves); + pk.SetEncounterMasteryFlags(moves, mastery, level); + if (pk.AlphaMove != 0) + pk.SetMasteryFlagMove(pk.AlphaMove); + } + + public (Learnset Learn, Learnset Mastery) GetLevelUpInfo() => LearnSource8LA.GetLearnsetAndMastery(Species, Form); + + public void LoadInitialMoveset(PA8 pa8, Span moves, Learnset learn, int level) + { + if (Moves.HasMoves) + Moves.CopyTo(moves); + else + learn.SetEncounterMoves(level, moves); + if (IsAlpha) + pa8.AlphaMove = moves[0]; + } + + private OverworldParam8a GetParams() + { + var gender = GetGenderRatio(); + return new OverworldParam8a + { + IsAlpha = IsAlpha, + FlawlessIVs = FlawlessIVCount, + Shiny = Shiny, + RollCount = 1, // Everything is shiny locked anyways + GenderRatio = gender, + }; + } + + private byte GetGenderRatio() => Gender switch + { + 0 => PersonalInfo.RatioMagicMale, + 1 => PersonalInfo.RatioMagicFemale, + _ => GetGenderRatioPersonal(), + }; + + private byte GetGenderRatioPersonal() + { + var pt = PersonalTable.LA; + var entry = pt.GetFormEntry(Species, Form); + return entry.Gender; + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (pk is IAlpha a && a.IsAlpha != IsAlpha) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + + if (pk is not IScaledSize s) + return true; + + // 3 of the Alpha statics were mistakenly set as 127 scale. If they enter HOME on 3.0.1, they'll get bumped to 255. + if (IsAlpha && this is { HeightScalar: 127, WeightScalar: 127 }) // Average Size Alphas + { + // HOME >=3.0.1 ensures 255 scales for the 127's + // PLA and S/V could have safe-harbored them via <=3.0.0 + if (pk.Context is EntityContext.Gen8a or EntityContext.Gen9) + { + if (s is not { HeightScalar: 127, WeightScalar: 127 }) // Original? + { + // Must match the HOME updated values AND must have the Alpha ribbon (visited HOME). + if (s is not { HeightScalar: 255, WeightScalar: 255 }) + return false; + if (pk is IRibbonSetMark9 { RibbonMarkAlpha: false }) + return false; + if (pk.IsUntraded) + return false; + } + } + else + { + // Must match the HOME updated values + if (s is not { HeightScalar: 255, WeightScalar: 255 }) + return false; + } + } + else + { + if (HasFixedHeight && s.HeightScalar != HeightScalar) + return false; + if (HasFixedWeight && s.WeightScalar != WeightScalar) + return false; + } + + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + private bool IsMatchLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return pk.Met_Location == Location; + if (metState == LocationRemapState.Remapped) + return IsMetRemappedSWSH(pk); + return pk.Met_Location == Location || IsMetRemappedSWSH(pk); + } + + private static bool IsMetRemappedSWSH(PKM pk) => pk.Met_Location == LocationsHOME.SWLA; + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (Shiny != Shiny.Random && !Shiny.IsValid(pk)) + return EncounterMatchRating.DeferredErrors; + if (FixedBall != Ball.None && pk.Ball != (byte)FixedBall) + return EncounterMatchRating.DeferredErrors; + + if (!IsForcedMasteryCorrect(pk)) + return EncounterMatchRating.DeferredErrors; + + if (!MarkRules.IsMarkValidAlpha(pk, IsAlpha)) + return EncounterMatchRating.DeferredErrors; + + if (IsAlpha && pk is PA8 { AlphaMove: 0 }) + return EncounterMatchRating.Deferred; + + return EncounterMatchRating.Match; + } + + public bool IsForcedMasteryCorrect(PKM pk) + { + ushort alpha = 0; + if (IsAlpha && Moves.HasMoves) + { + if (pk is PA8 pa && (alpha = pa.AlphaMove) != Moves.Move1) + return false; + } + + if (pk is not IMoveShop8Mastery p) + return true; + + const bool allowAlphaPurchaseBug = true; // Everything else Alpha is pre-1.1 + var level = pk.Met_Level; + var (learn, mastery) = GetLevelUpInfo(); + if (!p.IsValidPurchasedEncounter(learn, level, alpha, allowAlphaPurchaseBug)) + return false; + + Span moves = stackalloc ushort[4]; + if (Moves.HasMoves) + Moves.CopyTo(moves); + else + learn.SetEncounterMoves(level, moves); + + return p.IsValidMasteredEncounter(moves, learn, mastery, level, alpha, allowAlphaPurchaseBug); + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8aCorrelation.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8aCorrelation.cs new file mode 100644 index 000000000..5867c3f6f --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8aCorrelation.cs @@ -0,0 +1,7 @@ +namespace PKHeX.Core; + +public enum EncounterStatic8aCorrelation : byte +{ + WildGroup, + Fixed, +} diff --git a/PKHeX.Core/Legality/Structures/IMasteryInitialMoveShop8.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/IMasteryInitialMoveShop8.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/IMasteryInitialMoveShop8.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8a/IMasteryInitialMoveShop8.cs diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterArea8b.cs similarity index 64% rename from PKHeX.Core/Legality/Areas/EncounterArea8b.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterArea8b.cs index d1f8f8210..562bd46b5 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8b.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterArea8b.cs @@ -1,16 +1,18 @@ using System; -using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -/// /// /// encounter area /// -public sealed record EncounterArea8b : EncounterArea +public sealed record EncounterArea8b : IEncounterArea, IAreaLocation { - public readonly EncounterSlot8b[] Slots; + public EncounterSlot8b[] Slots { get; } + public GameVersion Version { get; } + + public readonly ushort Location; + public readonly SlotType Type; public static EncounterArea8b[] GetAreas(BinLinkerAccessor input, GameVersion game) { @@ -20,10 +22,11 @@ public sealed record EncounterArea8b : EncounterArea return result; } - private EncounterArea8b(ReadOnlySpan data, GameVersion game) : base(game) + private EncounterArea8b(ReadOnlySpan data, GameVersion game) { - Location = ReadInt16LittleEndian(data); + Location = ReadUInt16LittleEndian(data); Type = (SlotType)data[2]; + Version = game; Slots = ReadSlots(data); } @@ -52,9 +55,9 @@ public sealed record EncounterArea8b : EncounterArea return new EncounterSlot8b(this, species, form, min, max); } - public override bool IsMatchLocation(int location) + public bool IsMatchLocation(int location) { - if (base.IsMatchLocation(location)) + if (location == Location) return true; return CanCrossoverTo(location); } @@ -79,44 +82,22 @@ public sealed record EncounterArea8b : EncounterArea return false; } - public IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + public bool IsMunchlaxTree(ITrainerID32 pk) => IsMunchlaxTree(pk, Location); + + private static bool IsMunchlaxTree(ITrainerID32 pk, ushort location) { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(pk.Met_Level)) - break; - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) - break; - - if (Type is SlotType.HoneyTree && IsInaccessibleHoneySlotLocation(slot, pk)) - break; - - yield return slot; - break; - } - } - } - private static bool IsInaccessibleHoneySlotLocation(EncounterSlot8b slot, PKM pk) - { - // A/B/C tables, only Munchlax is a 'C' encounter, and A/B are accessible from any tree. - // C table encounters are only available from 4 trees, which are determined by TID16/SID16 of the save file. - if (slot.Species is not (int)Species.Munchlax) - return false; - // We didn't encode the honey tree index to the encounter slot resource. // Check if any of the slot's location doesn't match any of the groupC trees' area location ID. - var location = pk.Met_Location; var trees = SAV4Sinnoh.CalculateMunchlaxTrees(pk.TID16, pk.SID16); - return LocationID_HoneyTree[trees.Tree1] != location - && LocationID_HoneyTree[trees.Tree2] != location - && LocationID_HoneyTree[trees.Tree3] != location - && LocationID_HoneyTree[trees.Tree4] != location; + return IsMunchlaxTree(trees, location); + } + + private static bool IsMunchlaxTree(in MunchlaxTreeSet4 trees, ushort location) + { + return LocationID_HoneyTree[trees.Tree1] == location + && LocationID_HoneyTree[trees.Tree2] == location + && LocationID_HoneyTree[trees.Tree3] == location + && LocationID_HoneyTree[trees.Tree4] == location; } private static ReadOnlySpan LocationID_HoneyTree => new ushort[] diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterSlot8b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterSlot8b.cs new file mode 100644 index 000000000..97fb4dbdf --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterSlot8b.cs @@ -0,0 +1,178 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot8b(EncounterArea8b Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax) + : IEncounterable, IEncounterMatch, IEncounterConvertible +{ + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8b; + public bool EggEncounter => false; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsUnderground => Parent.Location is (>= 508 and <= 617); + public bool IsMarsh => Parent.Location is (>= 219 and <= 224); + public Ball FixedBall => GetRequiredBall(); + private Ball GetRequiredBall(Ball fallback = Ball.None) => IsMarsh ? Ball.Safari : fallback; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name} {Type.ToString().Replace('_', ' ')}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.Location; + public SlotType Type => Parent.Type; + + public bool CanUseRadar => Type is SlotType.Grass && !IsUnderground && !IsMarsh && CanUseRadarOverworld(Location); + + private static bool CanUseRadarOverworld(int location) => location switch + { + 195 or 196 => false, // Oreburgh Mine + 203 or 204 or 205 or 208 or 209 or 210 or 211 or 212 or 213 or 214 or 215 => false, // Mount Coronet, 206/207 exterior + >= 225 and <= 243 => false, // Solaceon Ruins + 244 or 245 or 246 or 247 or 248 or 249 => false, // Victory Road + 252 => false, // Ravaged Path + 255 or 256 => false, // Oreburgh Gate + 260 or 261 or 262 => false, // Stark Mountain, 259 exterior + >= 264 and <= 284 => false, // Turnback Cave + 286 or 287 or 288 or 289 or 290 or 291 => false, // Snowpoint Temple + 292 or 293 => false, // Wayward Cave + 294 or 295 => false, // Ruin Maniac Cave + 296 => false, // Maniac Tunnel + 299 or 300 or 301 or 302 or 303 or 304 or 305 => false, // Iron Island, 298 exterior + 306 or 307 or 308 or 309 or 310 or 311 or 312 or 313 or 314 => false, // Old Chateau + 368 or 369 or 370 or 371 or 372 => false, // Route 209 (Lost Tower) + _ => true, + }; + + private HiddenAbilityPermission IsHiddenAbilitySlot() => CanUseRadar + ? HiddenAbilityPermission.Possible + : HiddenAbilityPermission.Never; + + public AbilityPermission Ability => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => AbilityPermission.Any12, + HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden, + _ => AbilityPermission.Any12H, + }; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PB8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PB8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var pk = new PB8 + { + Species = Species, + Form = Form, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)Ball.Poke, + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + OT_Friendship = PersonalTable.BDSP[Species, Form].BaseFriendship, + }; + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + if (IsUnderground && GetBaseEggMove(out var move1)) + pk.RelearnMove1 = move1; + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PB8 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + criteria.SetRandomIVs(pk); + pk.Nature = pk.StatNature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, PersonalTable.BDSP.GetFormEntry(Species, Form)); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + } + + public bool GetBaseEggMove(out ushort move) + { + var pt = PersonalTable.BDSP; + var sf = pt.GetFormEntry(Species, Form); + var species = sf.HatchSpecies; + var baseEgg = LearnSource8BDSP.Instance.GetEggMoves(species, 0); + if (baseEgg.Length == 0) + { + move = 0; + return false; + } + + // Official method creates a new List() with all the egg moves, removes all ignored, then picks a random index. + // However, the "excluded egg moves" list was unreferenced in v1.0, so all egg moves are allowed. + // We can't know which patch the encounter originated from, because they never added any new content. + var rnd = Util.Rand; + var index = rnd.Next(baseEgg.Length); + move = baseEgg[index]; + return true; + } + + #endregion + + #region Matching + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + + if (Form != evo.Form && Species is not (int)Core.Species.Burmy) + return false; + + // A/B/C tables, only Munchlax is a 'C' encounter, and A/B are accessible from any tree. + // C table encounters are only available from 4 trees, which are determined by TID16/SID16 of the save file. + if (Type is SlotType.HoneyTree && Species == (int)Core.Species.Munchlax && !Parent.IsMunchlaxTree(pk)) + return false; + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + if (IsDeferredWurmple(pk)) + return EncounterMatchRating.PartialMatch; + if (IsDeferredHiddenAbility(isHidden)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; + } + + private bool IsDeferredWurmple(PKM pk) => Species == (int)Core.Species.Wurmple && pk.Species != (int)Core.Species.Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk); + + private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => IsHidden, + HiddenAbilityPermission.Always => !IsHidden, + _ => false, + }; + + public bool CanBeUndergroundMove(ushort move) + { + var et = PersonalTable.BDSP; + var sf = et.GetFormEntry(Species, Form); + var species = sf.HatchSpecies; + var baseEgg = LearnSource8BDSP.Instance.GetEggMoves(species, 0); + if (baseEgg.Length == 0) + return move == 0; + return baseEgg.Contains(move); + } + #endregion + +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterStatic8b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterStatic8b.cs new file mode 100644 index 000000000..52fa5c0c9 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterStatic8b.cs @@ -0,0 +1,205 @@ +using System; +using static PKHeX.Core.StaticCorrelation8bRequirement; + +namespace PKHeX.Core; + +/// +/// Generation 8 Static Encounter +/// +public sealed record EncounterStatic8b(GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IFlawlessIVCount, IFatefulEncounterReadOnly, IStaticCorrelation8b +{ + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8b; + int ILocation.EggLocation => EggLocation; + int ILocation.Location => Location; + public bool EggEncounter => EggLocation != None; + private const ushort None = Locations.Default8bNone; + public byte Form => 0; + public bool IsShiny => Shiny != Shiny.Never; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public required ushort Species { get; init; } + public required ushort Location { get; init; } + public ushort EggLocation { get; init; } = None; + public required byte Level { get; init; } + public Ball FixedBall { get; init; } + public byte FlawlessIVCount { get; init; } + public bool Roaming { get; init; } + public AbilityPermission Ability { get; init; } + public Shiny Shiny { get; init; } + public bool FatefulEncounter { get; init; } + + public string Name => "Static Encounter"; + public string LongName => Name; + + public StaticCorrelation8bRequirement GetRequirement(PKM pk) => Roaming + ? MustHave + : MustNotHave; + + public bool IsStaticCorrelationCorrect(PKM pk) + { + return Roaming8bRNG.ValidateRoamingEncounter(pk, Shiny == Shiny.Random ? Shiny.FixedValue : Shiny, FlawlessIVCount); + } + + // defined by mvpoke in encounter data + private static ReadOnlySpan Roaming_MetLocation_BDSP => new ushort[] + { + 197, 201, 354, 355, 356, 357, 358, 359, 361, 362, 364, 365, 367, 373, 375, 377, + 378, 379, 383, 385, 392, 394, 395, 397, 400, 403, 404, 407, + 485, + }; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PB8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PB8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PB8 + { + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Egg_Location = EggLocation, + Met_Level = Level, + MetDate = EncounterDate.GetDateSwitch(), + Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke), + FatefulEncounter = FatefulEncounter, + + ID32 = tr.ID32, + Version = (byte)version, + Language = lang, + OT_Gender = tr.Gender, + OT_Name = tr.OT, + OT_Friendship = PersonalTable.BDSP[Species, Form].BaseFriendship, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + HeightScalar = PokeSizeUtil.GetRandomScalar(), + WeightScalar = PokeSizeUtil.GetRandomScalar(), + }; + + if (EggEncounter) + { + // Fake as hatched. + pk.Met_Location = Locations.HatchLocation8b; + pk.Met_Level = EggStateLegality.EggMetLevel; + pk.Egg_Location = EggLocation; + pk.EggMetDate = pk.MetDate; + } + + SetPINGA(pk, criteria); + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PB8 pk, EncounterCriteria criteria) + { + var req = GetRequirement(pk); + if (req == MustHave) // Roamers + { + var shiny = Shiny == Shiny.Random ? Shiny.FixedValue : Shiny; + Roaming8bRNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount); + } + else + { + var shiny = Shiny == Shiny.Never ? Shiny.Never : Shiny.Random; + Wild8bRNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount, Ability); + } + } + + #endregion + + #region Matching + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (!IsMatchLocation(pk)) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (!IsMatchEggLocation(pk)) + return false; + return true; + } + + private bool IsMatchLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchLocationRemapped(pk); + return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); + } + + private bool IsMatchLocationExact(PKM pk) + { + if (EggEncounter) + return !pk.IsEgg || pk.Met_Location == Location || pk.Met_Location == Locations.LinkTrade6NPC; + if (!Roaming) + return pk.Met_Location == Location; + return IsRoamingLocation(pk); + } + + private bool IsMatchEggLocationExact(PKM pk) + { + var eggloc = pk.Egg_Location; + if (!EggEncounter) + return eggloc == EggLocation; + + if (!pk.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade6NPC; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk.Met_Location is not (Locations.Default8bNone or Locations.LinkTrade6NPC)) + return false; + return true; + } + + private bool IsMatchLocationRemapped(PKM pk) + { + var met = (ushort)pk.Met_Location; + var version = pk.Version; + if (pk.Context == EntityContext.Gen8) + return LocationsHOME.IsValidMetBDSP(met, version); + return LocationsHOME.GetMetSWSH(Location, version) == met; + } + + private bool IsMatchEggLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchEggLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchEggLocationRemapped(pk); + // Either + return IsMatchEggLocationExact(pk) || IsMatchEggLocationRemapped(pk); + } + + private bool IsMatchEggLocationRemapped(PKM pk) + { + if (!EggEncounter) + return pk.Egg_Location == 0; + return LocationsHOME.IsLocationSWSHEgg(pk.Version, pk.Met_Location, pk.Egg_Location, EggLocation); + } + + private static bool IsRoamingLocation(PKM pk) => Roaming_MetLocation_BDSP.Contains((ushort)pk.Met_Location); + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterTrade8b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterTrade8b.cs new file mode 100644 index 000000000..a27bfea67 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterTrade8b.cs @@ -0,0 +1,239 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 8 BD/SP Trade Encounter +/// +public sealed record EncounterTrade8b : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible, IScaledSizeReadOnly, IFixedOTFriendship, IMoveset, IContestStatsReadOnly +{ + public int Generation => 8; + public EntityContext Context => EntityContext.Gen8b; + public int Location => Locations.LinkTrade6NPC; + public Shiny Shiny => Shiny.Never; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public int EggLocation => Locations.Default8bNone; + public bool IsFixedTrainer => true; + public bool IsFixedNickname => true; + public GameVersion Version { get; } + + private string[] TrainerNames { get; } + private string[] Nicknames { get; } + + public required Nature Nature { get; init; } + public required ushort ID32 { get; init; } + public required AbilityPermission Ability { get; init; } + public required byte Gender { get; init; } + public required byte OTGender { get; init; } + public required uint PID { get; init; } + public required uint EncryptionConstant { get; init; } + public required byte HeightScalar { get; init; } + public required byte WeightScalar { get; init; } + public required Moveset Moves { get; init; } + public required IndividualValueSet IVs { get; init; } + public required ushort Species { get; init; } + public required byte Level { get; init; } + + public byte OT_Friendship => Species == (int)Core.Species.Chatot ? (byte)35 : (byte)50; + private byte BaseContest => Species == (int)Core.Species.Chatot ? (byte)20 : (byte)0; + + public byte CNT_Cool => BaseContest; + public byte CNT_Beauty => BaseContest; + public byte CNT_Cute => BaseContest; + public byte CNT_Smart => BaseContest; + public byte CNT_Tough => BaseContest; + public byte CNT_Sheen => 0; + + public byte Form => 0; + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public EncounterTrade8b(ReadOnlySpan names, byte index, GameVersion game) + { + Version = game; + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PB8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PB8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PB8 + { + PID = PID, + EncryptionConstant = EncryptionConstant, + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateSwitch(), + Gender = Gender, + Nature = (byte)Nature, + StatNature = (byte)Nature, + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang, + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Friendship = OT_Friendship, + + IsNicknamed = IsFixedNickname, + Nickname = IsFixedNickname ? Nicknames[lang] : SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + HeightScalar = HeightScalar, + WeightScalar = WeightScalar, + HT_Name = tr.OT, + HT_Language = (byte)tr.Language, + HT_Friendship = PersonalTable.BDSP[Species, Form].BaseFriendship, + }; + + // Has German Language ID for all except German origin, which is Japanese + if (Species == (int)Core.Species.Magikarp) + pk.Language = (int)(pk.Language == (int)LanguageID.German ? LanguageID.Japanese : LanguageID.German); + + this.CopyContestStatsTo(pk); + EncounterUtil1.SetEncounterMoves(pk, version, Level); + pk.SetRandomIVsTemplate(IVs, 0); + pk.RefreshAbility((byte)Ability >> 1); + pk.ResetPartyStats(); + + return pk; + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) => (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (pk.EncryptionConstant != EncryptionConstant) + return false; + if (pk.PID != PID) + return false; + if (pk is IContestStatsReadOnly s && s.IsContestBelow(this)) + return false; + if (pk is IScaledSize h && h.HeightScalar != HeightScalar) + return false; + if (pk is IScaledSize w && w.WeightScalar != WeightScalar) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) + return false; + if (pk.Gender != Gender) + return false; + if (pk.Nature != (int)Nature) + return false; + if (pk.ID32 != ID32) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + return true; + } + + private bool IsMatchLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchLocationRemapped(pk); + return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); + } + + private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; + + private bool IsMatchLocationRemapped(PKM pk) + { + var met = (ushort)pk.Met_Location; + var version = pk.Version; + if (pk.Context == EntityContext.Gen8) + return LocationsHOME.IsValidMetBDSP(met, version); + return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; + } + + private bool IsMatchEggLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchEggLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchEggLocationRemapped(pk); + // Either + return IsMatchEggLocationExact(pk) || IsMatchEggLocationRemapped(pk); + } + + private static bool IsMatchEggLocationRemapped(PKM pk) => pk.Egg_Location == 0; + private bool IsMatchEggLocationExact(PKM pk) => pk.Egg_Location == EggLocation; + + public int DetectMeisterMagikarpLanguage(ReadOnlySpan nick, ReadOnlySpan ot, int currentLanguageID) + { + // Receiving the trade on a German game -> Japanese LanguageID. + // Receiving the trade on any other language -> German LanguageID. + if (currentLanguageID is not ((int)LanguageID.Japanese or (int)LanguageID.German)) + return -1; + + for (int i = 1; i < (int)LanguageID.ChineseT; i++) + { + if (!nick.SequenceEqual(Nicknames[i])) + continue; + if (!ot.SequenceEqual(TrainerNames[i])) + continue; + + // Language gets flipped to another language ID; can't be equal. + var shouldNotBe = currentLanguageID == (int)LanguageID.German ? LanguageID.German : LanguageID.Japanese; + return i != (int)shouldNotBe ? i : 0; + } + return -1; + } + + #endregion + + /// + /// Traded between players within BD/SP, the original OT is replaced with the above OT (version dependent) as the original OT is >6 chars in length. + /// + /// Entity to check. + /// True if matches the pattern of a traded Magikarp. + public bool IsMagikarpJapaneseTradedBDSP(PKM pk) + { + return Species is (int)Core.Species.Magikarp && pk is { Language: (int)LanguageID.Japanese, OT_Name: "Diamond." or "Pearl." }; + } + + /// + /// Nintendo Switch updates NgWord disallows the French nickname (Pijouk) and resets it back to default (Pijako). + /// + /// Entity to check. + /// True if matches the pattern of a reverted Nickname Pijako. + public bool IsPijako(PKM pk) + { + return Species == (int)Core.Species.Chatot && pk is { Language: (int)LanguageID.French, IsNicknamed: false }; + } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8b/IStaticCorrelation8b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/IStaticCorrelation8b.cs new file mode 100644 index 000000000..38af15c4e --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/IStaticCorrelation8b.cs @@ -0,0 +1,7 @@ +namespace PKHeX.Core; + +public interface IStaticCorrelation8b +{ + StaticCorrelation8bRequirement GetRequirement(PKM pk); + bool IsStaticCorrelationCorrect(PKM pk); +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8b/StaticCorrelation8bRequirement.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/StaticCorrelation8bRequirement.cs new file mode 100644 index 000000000..2d5498e9e --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/StaticCorrelation8bRequirement.cs @@ -0,0 +1,8 @@ +namespace PKHeX.Core; + +public enum StaticCorrelation8bRequirement +{ + CanBeEither, + MustHave, + MustNotHave, +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/AreaWeather9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/AreaWeather9.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/EncounterSlot/AreaWeather9.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen9/AreaWeather9.cs diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterArea9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterArea9.cs new file mode 100644 index 000000000..1344e518b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterArea9.cs @@ -0,0 +1,55 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// +/// encounter area +/// +public sealed record EncounterArea9 : IEncounterArea, IAreaLocation +{ + public EncounterSlot9[] Slots { get; } + public GameVersion Version { get; } + + public readonly ushort Location; + + public bool IsMatchLocation(int location) => Location == location; + + public ushort CrossFrom { get; } + + public static EncounterArea9[] GetAreas(BinLinkerAccessor input, GameVersion game) + { + var result = new EncounterArea9[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea9(input[i], game); + return result; + } + + private EncounterArea9(ReadOnlySpan areaData, GameVersion game) + { + Location = areaData[0]; + CrossFrom = areaData[2]; + Version = game; + Slots = ReadSlots(areaData[4..]); + } + + private EncounterSlot9[] ReadSlots(ReadOnlySpan areaData) + { + const int size = 8; + var result = new EncounterSlot9[areaData.Length / size]; + for (int i = 0; i < result.Length; i++) + { + var slot = areaData[(i * size)..]; + var species = ReadUInt16LittleEndian(slot); + var form = slot[2]; + var gender = (sbyte)slot[3]; + + var min = slot[4]; + var max = slot[5]; + var time = slot[6]; + + result[i] = new EncounterSlot9(this, species, form, min, max, gender, time); + } + return result; + } +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterDist9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterDist9.cs similarity index 63% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterDist9.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterDist9.cs index 2cfa865b2..b579b85e6 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterDist9.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterDist9.cs @@ -1,15 +1,34 @@ using System; +using static PKHeX.Core.AbilityPermission; +using static PKHeX.Core.EncounterMatchRating; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -public sealed record EncounterDist9 : EncounterStatic, ITeraRaid9 +public sealed record EncounterDist9 + : IEncounterable, IEncounterMatch, IEncounterConvertible, ITeraRaid9, IMoveset, IFlawlessIVCount, IFixedGender { - public override int Generation => 9; - public override int Location => Locations.TeraCavern9; - public override EntityContext Context => EntityContext.Gen9; + public int Generation => 9; + int ILocation.Location => Location; + public const ushort Location = Locations.TeraCavern9; + public EntityContext Context => EntityContext.Gen9; + public GameVersion Version => GameVersion.SV; public bool IsDistribution => Index != 0; - public GemType TeraType { get; private init; } + public Ball FixedBall => Ball.None; + public bool EggEncounter => false; + public bool IsShiny => Shiny == Shiny.Always; + public int EggLocation => 0; + + public required ushort Species { get; init; } + public required byte Form { get; init; } + public required sbyte Gender { get; init; } + public required AbilityPermission Ability { get; init; } + public required byte FlawlessIVCount { get; init; } + public required Shiny Shiny { get; init; } + public required byte Level { get; init; } + public required Moveset Moves { get; init; } + public required IndividualValueSet IVs { get; init; } + public required GemType TeraType { get; init; } public byte Index { get; private init; } public byte Stars { get; private init; } public byte RandRate { get; private init; } // weight chance of this encounter @@ -39,6 +58,11 @@ public sealed record EncounterDist9 : EncounterStatic, ITeraRaid9 public ushort RandRate3TotalScarlet { get; private init; } public ushort RandRate3TotalViolet { get; private init; } + public string Name => "Distribution Tera Raid Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + public ushort GetRandRateTotalScarlet(int stage) => stage switch { 0 => RandRate0TotalScarlet, @@ -152,8 +176,6 @@ public sealed record EncounterDist9 : EncounterStatic, ITeraRaid9 return result; } - private EncounterDist9() : base(GameVersion.SV) { } - private const int SerializedSize = WeightStart + (sizeof(ushort) * 2 * 2 * 4) + 10; private const int WeightStart = 0x14; private static EncounterDist9 ReadEncounter(ReadOnlySpan data) => new() @@ -203,36 +225,138 @@ public sealed record EncounterDist9 : EncounterStatic, ITeraRaid9 private static AbilityPermission GetAbility(byte b) => b switch { - 0 => AbilityPermission.Any12, - 1 => AbilityPermission.Any12H, - 2 => AbilityPermission.OnlyFirst, - 3 => AbilityPermission.OnlySecond, - 4 => AbilityPermission.OnlyHidden, + 0 => Any12, + 1 => Any12H, + 2 => OnlyFirst, + 3 => OnlySecond, + 4 => OnlyHidden, _ => throw new ArgumentOutOfRangeException(nameof(b), b, null), }; - protected override EncounterMatchRating IsMatchDeferred(PKM pk) + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK9 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) { - if (Ability != AbilityPermission.Any12H) + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PK9 + { + Language = lang, + Species = Species, + Form = Form, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.SV[Species, Form].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (int)version, + Ball = (byte)Ball.Poke, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + Obedience_Level = LevelMin, + }; + SetPINGA(pk, criteria); + pk.SetMoves(Moves); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PK9 pk, EncounterCriteria criteria) + { + const byte rollCount = 1; + const byte undefinedSize = 0; + var pi = PersonalTable.SV.GetFormEntry(Species, Form); + var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, rollCount, + undefinedSize, undefinedSize, ScaleType, Scale, + Ability, Shiny, IVs: IVs); + + var init = Util.Rand.Rand64(); + var success = this.TryApply32(pk, init, param, criteria); + if (!success) + this.TryApply32(pk, init, param, EncounterCriteria.Unrestricted); + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + private bool IsMatchLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchLocationRemapped(pk); + return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return PartialMatch; + return IsMatchDeferred(pk); + } + + private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; + + private bool IsMatchLocationRemapped(PKM pk) + { + var met = (ushort)pk.Met_Location; + var version = pk.Version; + if (pk.Context == EntityContext.Gen8) + return LocationsHOME.IsValidMetSV(met, version); + return LocationsHOME.GetMetSWSH(Location, version) == met; + } + + private EncounterMatchRating IsMatchDeferred(PKM pk) + { + if (Shiny != Shiny.Random && !Shiny.IsValid(pk)) + return DeferredErrors; + + if (Ability != Any12H) { // HA-Only is a strict match. Ability Capsule and Patch can potentially change these. var num = pk.AbilityNumber; if (num == 4) { - if (Ability is not AbilityPermission.OnlyHidden && !AbilityVerifier.CanAbilityPatch(9, PersonalTable.SV.GetFormEntry(Species, Form), pk.Species)) - return EncounterMatchRating.DeferredErrors; + if (Ability is not OnlyHidden && !AbilityVerifier.CanAbilityPatch(9, PersonalTable.SV.GetFormEntry(Species, Form), pk.Species)) + return DeferredErrors; } else if (Ability.IsSingleValue(out int index) && 1 << index != num) // Fixed regular ability { - if (Ability is AbilityPermission.OnlyFirst or AbilityPermission.OnlySecond && !AbilityVerifier.CanAbilityCapsule(9, PersonalTable.SV.GetFormEntry(Species, Form))) - return EncounterMatchRating.DeferredErrors; + if (Ability is OnlyFirst or OnlySecond && !AbilityVerifier.CanAbilityCapsule(9, PersonalTable.SV.GetFormEntry(Species, Form))) + return DeferredErrors; } } - return base.IsMatchDeferred(pk); + return Match; } - protected override bool IsMatchPartial(PKM pk) + private bool IsMatchPartial(PKM pk) { switch (Shiny) { @@ -251,33 +375,13 @@ public sealed record EncounterDist9 : EncounterStatic, ITeraRaid9 return true; var pi = PersonalTable.SV.GetFormEntry(Species, Form); - var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, 1, 0, 0, ScaleType, Scale, Ability, Shiny, Nature, IVs); + var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, 1, 0, 0, ScaleType, Scale, Ability, Shiny, IVs: IVs); if (!Encounter9RNG.IsMatch(pk, param, seed)) return true; - return base.IsMatchPartial(pk); - } - - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - var pk9 = (PK9)pk; - pk9.Obedience_Level = (byte)pk9.Met_Level; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pk9 = (PK9)pk; - - const byte rollCount = 1; - const byte undefinedSize = 0; - var pi = PersonalTable.SV.GetFormEntry(Species, Form); - var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, rollCount, - undefinedSize, undefinedSize, ScaleType, Scale, - Ability, Shiny, Nature, IVs); - - var init = Util.Rand.Rand64(); - var success = this.TryApply32(pk9, init, param, criteria); - if (!success) - this.TryApply32(pk9, init, param, EncounterCriteria.Unrestricted); + + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return false; } + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterFixed9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterFixed9.cs new file mode 100644 index 000000000..06030bc48 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterFixed9.cs @@ -0,0 +1,243 @@ +using System; +using static PKHeX.Core.AbilityPermission; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// +/// Generation 9 Fixed Spawn Encounter +/// +public sealed record EncounterFixed9 + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMoveset, IFlawlessIVCount, IGemType, IFixedGender +{ + public int Generation => 9; + int ILocation.Location => Location; + public byte Location => Location0; + public EntityContext Context => EntityContext.Gen9; + public GameVersion Version => GameVersion.SV; + public Shiny Shiny => Shiny.Random; + public bool EggEncounter => false; + public Ball FixedBall => Ball.None; + public bool IsShiny => false; + public int EggLocation => 0; + public AbilityPermission Ability => AbilityPermission.Any12; + + public required ushort Species { get; init; } + public required byte Form { get; init; } + public required byte Level { get; init; } + public required byte FlawlessIVCount { get; init; } + public required GemType TeraType { get; init; } + public required sbyte Gender { get; init; } + public required Moveset Moves { get; init; } + private byte Location0 { get; init; } + private byte Location1 { get; init; } + private byte Location2 { get; init; } + private byte Location3 { get; init; } + + private const int MinScaleStrongTera = 200; // [200,255] + + public static EncounterFixed9[] GetArray(ReadOnlySpan data) + { + const int size = 0x14; + var count = data.Length / size; + var result = new EncounterFixed9[count]; + for (int i = 0; i < result.Length; i++) + result[i] = ReadEncounter(data.Slice(i * size, size)); + return result; + } + + private static EncounterFixed9 ReadEncounter(ReadOnlySpan data) => new() + { + Species = ReadUInt16LittleEndian(data), + Form = data[0x02], + Level = data[0x03], + FlawlessIVCount = data[0x04], + TeraType = (GemType)data[0x05], + Gender = (sbyte)data[0x06], + // 1 byte reserved + Moves = new Moveset( + ReadUInt16LittleEndian(data[0x08..]), + ReadUInt16LittleEndian(data[0x0A..]), + ReadUInt16LittleEndian(data[0x0C..]), + ReadUInt16LittleEndian(data[0x0E..])), + Location0 = data[0x10], + Location1 = data[0x11], + Location2 = data[0x12], + Location3 = data[0x13], + }; + + public string Name => "Fixed Behavior Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK9 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PK9 + { + Language = lang, + Species = Species, + Form = Form, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.SV[Species, Form].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (int)version, + Ball = (byte)Ball.Poke, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + Obedience_Level = LevelMin, + }; + + var type = Tera9RNG.GetTeraType(Util.Rand.Rand64(), TeraType, Species, Form); + pk.TeraTypeOriginal = (MoveType)type; + if (criteria.TeraType != -1 && type != criteria.TeraType) + pk.SetTeraType(type); // sets the override type + + pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.Scale = TeraType != 0 ? (byte)(MinScaleStrongTera + Util.Rand.Next(byte.MaxValue - MinScaleStrongTera + 1)) : PokeSizeUtil.GetRandomScalar(); + + SetPINGA(pk, criteria); + pk.SetMoves(Moves); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PK9 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, PersonalTable.SV.GetFormEntry(Species, Form)); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + + criteria.SetRandomIVs(pk, FlawlessIVCount); + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchForm(pk, evo)) + return false; + if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal)) + return false; + if (TeraType != 0) + { + if (pk is IScaledSize3 size3) + { + if (size3.Scale < MinScaleStrongTera) + return false; + } + else if (pk is IScaledSize s2) + { + if (s2.HeightScalar < MinScaleStrongTera) + return false; + } + } + if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount) + return false; + + return true; + } + + private bool IsMatchForm(PKM pk, EvoCriteria evo) + { + if (evo.Form == Form) + return true; + if (Species is (int)Core.Species.Deerling or (int)Core.Species.Sawsbuck) + return pk.Form <= 3; + return false; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + private bool IsMatchLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchLocationRemapped(pk); + return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); + } + + private bool IsMatchLocationRemapped(PKM pk) + { + var met = (ushort)pk.Met_Location; + var version = pk.Version; + if (pk.Context == EntityContext.Gen8) + return LocationsHOME.IsValidMetSV(met, version); + return LocationsHOME.GetMetSWSH(Location, version) == met; + } + + private bool IsMatchLocationExact(PKM pk) + { + var loc = pk.Met_Location; + if (loc == Location0) + return true; + if (loc == 0) + return false; + return loc == Location1 || loc == Location2 || loc == Location3; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return IsMatchDeferred(pk); + } + + private EncounterMatchRating IsMatchDeferred(PKM pk) + { + if (Shiny != Shiny.Random && !Shiny.IsValid(pk)) + return EncounterMatchRating.DeferredErrors; + + if (Ability != Any12H) + { + // HA-Only is a strict match. Ability Capsule and Patch can potentially change these. + var num = pk.AbilityNumber; + if (num == 4) + { + if (Ability is not OnlyHidden && !AbilityVerifier.CanAbilityPatch(9, PersonalTable.SV.GetFormEntry(Species, Form), pk.Species)) + return EncounterMatchRating.DeferredErrors; + } + else if (Ability.IsSingleValue(out int index) && 1 << index != num) // Fixed regular ability + { + if (Ability is OnlyFirst or OnlySecond && !AbilityVerifier.CanAbilityCapsule(9, PersonalTable.SV.GetFormEntry(Species, Form))) + return EncounterMatchRating.DeferredErrors; + } + } + + return EncounterMatchRating.Match; + } + + private bool IsMatchPartial(PKM pk) + { + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return false; + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterMight9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterMight9.cs similarity index 72% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterMight9.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterMight9.cs index de3538e9f..b9e9b118b 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterMight9.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterMight9.cs @@ -3,16 +3,34 @@ using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9 +public sealed record EncounterMight9 + : IEncounterable, IEncounterMatch, IEncounterConvertible, ITeraRaid9, IMoveset, IFlawlessIVCount, IFixedGender { - public override int Generation => 9; - public override int Location => Locations.TeraCavern9; - public override EntityContext Context => EntityContext.Gen9; + public int Generation => 9; + int ILocation.Location => Location; + public const ushort Location = Locations.TeraCavern9; + public EntityContext Context => EntityContext.Gen9; + public GameVersion Version => GameVersion.SV; public bool IsDistribution => Index != 0; - public GemType TeraType { get; private init; } - public byte Index { get; private init; } - public byte Stars { get; private init; } - public byte RandRate { get; private init; } // weight chance of this encounter + public Ball FixedBall => Ball.None; + public bool EggEncounter => false; + public bool IsShiny => Shiny == Shiny.Always; + public int EggLocation => 0; + + public required Moveset Moves { get; init; } + public required IndividualValueSet IVs { get; init; } + public required ushort Species { get; init; } + public required byte Form { get; init; } + public required byte Level { get; init; } + public required sbyte Gender { get; init; } + public required byte FlawlessIVCount { get; init; } + public required AbilityPermission Ability { get; init; } + public required Shiny Shiny { get; init; } + public required Nature Nature { get; init; } + public required GemType TeraType { get; init; } + public required byte Index { get; init; } + public required byte Stars { get; init; } + public required byte RandRate { get; init; } // weight chance of this encounter /// Indicates how the value is used, if at all. public SizeType9 ScaleType { get; private init; } @@ -39,6 +57,11 @@ public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9 public ushort RandRate3TotalScarlet { get; private init; } public ushort RandRate3TotalViolet { get; private init; } + public string Name => "7-Star Tera Raid Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + public ushort GetRandRateTotalScarlet(int stage) => stage switch { 0 => RandRate0TotalScarlet, @@ -152,8 +175,6 @@ public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9 return result; } - private EncounterMight9() : base(GameVersion.SV) { } - private const int SerializedSize = WeightStart + (sizeof(ushort) * 2 * 2 * 4) + 10; private const int WeightStart = 0x14; private static EncounterMight9 ReadEncounter(ReadOnlySpan data) => new() @@ -211,7 +232,85 @@ public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9 _ => throw new ArgumentOutOfRangeException(nameof(b), b, null), }; - protected override bool IsMatchLocation(PKM pk) + private byte GetGender() => Gender switch + { + 0 => PersonalInfo.RatioMagicMale, + 1 => PersonalInfo.RatioMagicFemale, + 2 => PersonalInfo.RatioMagicGenderless, + _ => PersonalTable.SV.GetFormEntry(Species, Form).Gender, + }; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK9 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PK9 + { + Language = lang, + Species = Species, + Form = Form, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.SV[Species, Form].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (int)version, + Ball = (byte)Ball.Poke, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + Obedience_Level = LevelMin, + RibbonMarkMightiest = true, + }; + SetPINGA(pk, criteria); + pk.SetMoves(Moves); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PK9 pk, EncounterCriteria criteria) + { + const byte rollCount = 1; + const byte undefinedSize = 0; + byte gender = GetGender(); + var param = new GenerateParam9(Species, gender, FlawlessIVCount, rollCount, + undefinedSize, undefinedSize, ScaleType, Scale, + Ability, Shiny, Nature, IVs); + + var init = Util.Rand.Rand64(); + var success = this.TryApply32(pk, init, param, criteria); + if (!success) + this.TryApply32(pk, init, param, EncounterCriteria.Unrestricted); + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + private bool IsMatchLocation(PKM pk) { var metState = LocationsHOME.GetRemapState(Context, pk.Context); if (metState == LocationRemapState.Original) @@ -221,6 +320,13 @@ public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9 return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); } + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return IsMatchDeferred(pk); + } + private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; private bool IsMatchLocationRemapped(PKM pk) @@ -229,10 +335,10 @@ public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9 var version = pk.Version; if (pk.Context == EntityContext.Gen8) return LocationsHOME.IsValidMetSV(met, version); - return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; + return LocationsHOME.GetMetSWSH(Location, version) == met; } - protected override EncounterMatchRating IsMatchDeferred(PKM pk) + private EncounterMatchRating IsMatchDeferred(PKM pk) { if (Ability != AbilityPermission.Any12H) { @@ -250,10 +356,10 @@ public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9 } } - return base.IsMatchDeferred(pk); + return EncounterMatchRating.Match; } - protected override bool IsMatchPartial(PKM pk) + private bool IsMatchPartial(PKM pk) { switch (Shiny) { @@ -275,39 +381,12 @@ public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9 var param = new GenerateParam9(Species, gender, FlawlessIVCount, 1, 0, 0, ScaleType, Scale, Ability, Shiny, Nature, IVs); if (!Encounter9RNG.IsMatch(pk, param, seed)) return true; - return base.IsMatchPartial(pk); + + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return false; } - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - var pk9 = (PK9)pk; - pk9.Obedience_Level = (byte)pk9.Met_Level; - pk9.RibbonMarkMightiest = true; - } + #endregion - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pk9 = (PK9)pk; - - const byte rollCount = 1; - const byte undefinedSize = 0; - byte gender = GetGender(); - var param = new GenerateParam9(Species, gender, FlawlessIVCount, rollCount, - undefinedSize, undefinedSize, ScaleType, Scale, - Ability, Shiny, Nature, IVs); - - var init = Util.Rand.Rand64(); - var success = this.TryApply32(pk9, init, param, criteria); - if (!success) - this.TryApply32(pk9, init, param, EncounterCriteria.Unrestricted); - } - - private byte GetGender() => Gender switch - { - 0 => PersonalInfo.RatioMagicMale, - 1 => PersonalInfo.RatioMagicFemale, - 2 => PersonalInfo.RatioMagicGenderless, - _ => PersonalTable.SV.GetFormEntry(Species, Form).Gender, - }; } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterSlot9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterSlot9.cs new file mode 100644 index 000000000..1e5d22700 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterSlot9.cs @@ -0,0 +1,190 @@ +using System.Collections.Generic; +using static PKHeX.Core.AreaWeather9; + +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +public sealed record EncounterSlot9(EncounterArea9 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax, sbyte Gender, byte Time) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IEncounterFormRandom +{ + public int Generation => 9; + public EntityContext Context => EntityContext.Gen9; + public bool EggEncounter => false; + public AbilityPermission Ability => AbilityPermission.Any12; + public Ball FixedBall => Ball.None; + public Shiny Shiny => Shiny.Random; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsRandomUnspecificForm => Form >= EncounterUtil1.FormDynamic; + + public string Name => $"Wild Encounter ({Version})"; + public string LongName => $"{Name}"; + public GameVersion Version => Parent.Version; + public int Location => Parent.CrossFrom == 0 ? Parent.Location : Parent.CrossFrom; + + private static int GetTime(RibbonIndex mark) => mark switch + { + RibbonIndex.MarkLunchtime => 0, + RibbonIndex.MarkSleepyTime => 1, + RibbonIndex.MarkDusk => 2, + RibbonIndex.MarkDawn => 3, + _ => 4, + }; + + public bool CanSpawnAtTime(RibbonIndex mark) => (Time & (1 << GetTime(mark))) == 0; + + public bool CanSpawnInWeather(RibbonIndex mark) + { + if (AreaWeather.TryGetValue((byte)Location, out var areaWeather)) + return areaWeather.IsMarkCompatible(mark); + return false; + } + + /// + /// Location IDs matched with possible weather types. + /// + internal static readonly Dictionary AreaWeather = new() + { + { 6, Standard }, // South Province (Area One) + { 10, Standard }, // Pokémon League + { 12, Standard }, // South Province (Area Two) + { 14, Standard }, // South Province (Area Four) + { 16, Standard }, // South Province (Area Six) + { 18, Standard }, // South Province (Area Five) + { 20, Standard }, // South Province (Area Three) + { 22, Standard }, // West Province (Area One) + { 24, Sand }, // Asado Desert + { 26, Standard }, // West Province (Area Two) + { 28, Standard }, // West Province (Area Three) + { 30, Standard }, // Tagtree Thicket + { 32, Standard }, // East Province (Area Three) + { 34, Standard }, // East Province (Area One) + { 36, Standard }, // East Province (Area Two) + { 38, Snow }, // Glaseado Mountain (1) + { 40, Standard }, // Casseroya Lake + { 44, Standard }, // North Province (Area Three) + { 46, Standard }, // North Province (Area One) + { 48, Standard }, // North Province (Area Two) + { 50, Standard }, // Great Crater of Paldea + { 56, Standard }, // South Paldean Sea + { 58, Standard }, // West Paldean Sea + { 60, Standard }, // East Paldean Sea + { 62, Standard }, // North Paldean Sea + { 64, Inside }, // Inlet Grotto + { 67, Inside }, // Alfornada Cavern + { 69, Standard | Inside | Snow | Snow },// Dalizapa Passage (Near Medali, Tunnels, Near Pokémon Center, Near Zapico) + { 70, Standard }, // Poco Path + { 80, Standard }, // Cabo Poco + { 109, Standard }, // Socarrat Trail + { 124, Inside }, // Area Zero (5) + }; + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK9 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var form = GetWildForm(Form); + var pk = new PK9 + { + Species = Species, + Form = form, + CurrentLevel = LevelMin, + Met_Location = Location, + Met_Level = LevelMin, + Version = (byte)Version, + Ball = (byte)Ball.Poke, + MetDate = EncounterDate.GetDateSwitch(), + + Language = lang, + OT_Name = tr.OT, + OT_Gender = tr.Gender, + ID32 = tr.ID32, + Obedience_Level = LevelMin, + OT_Friendship = PersonalTable.SV[Species, form].BaseFriendship, + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + }; + SetPINGA(pk, criteria); + EncounterUtil1.SetEncounterMoves(pk, Version, LevelMin); + pk.ResetPartyStats(); + return pk; + } + + private byte GetWildForm(byte form) + { + if (form < EncounterUtil1.FormDynamic) + return form; + if (form == EncounterUtil1.FormVivillon) + return 18; // Fancy Vivillon + // flagged as totally random + return (byte)Util.Rand.Next(PersonalTable.SV[Species].FormCount); + } + + private void SetPINGA(PK9 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.SetRandomIVs(); + + pk.Nature = pk.StatNature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(Gender, PersonalTable.SV[pk.Species, pk.Form]); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + + var rand = new Xoroshiro128Plus(Util.Rand.Rand64()); + var type = Tera9RNG.GetTeraTypeFromPersonal(Species, Form, rand.Next()); + pk.TeraTypeOriginal = (MoveType)type; + if (criteria.TeraType != -1 && type != criteria.TeraType) + pk.SetTeraType(type); // sets the override type + if (Species == (int)Core.Species.Toxtricity) + pk.Nature = ToxtricityUtil.GetRandomNature(ref rand, Form); + + pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.Scale = PokeSizeUtil.GetRandomScalar(); + } + + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Form != evo.Form && !IsRandomUnspecificForm && !IsFormOkayWild(Species, evo.Form)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + + if (pk is ITeraType t) + { + var orig = (byte)t.TeraTypeOriginal; + var pi = PersonalTable.SV[Species, Form]; + if (pi.Type1 != orig && pi.Type2 != orig) + return false; + } + + return true; + } + + private static bool IsFormOkayWild(ushort species, byte form) => species switch + { + (int)Core.Species.Rotom => form <= 5, + (int)Core.Species.Deerling or (int)Core.Species.Sawsbuck => form < 4, + (int)Core.Species.Oricorio => form < 4, + _ => false, + }; + + public EncounterMatchRating GetMatchRating(PKM pk) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + return EncounterMatchRating.Match; + } + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterStatic9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterStatic9.cs new file mode 100644 index 000000000..96061c722 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterStatic9.cs @@ -0,0 +1,234 @@ +using static PKHeX.Core.AbilityPermission; + +namespace PKHeX.Core; + +/// +/// Generation 9 Static Encounter +/// +public sealed record EncounterStatic9(GameVersion Version) + : IEncounterable, IEncounterMatch, IEncounterConvertible, IMoveset, IFlawlessIVCount, IGemType, IFixedGender +{ + public int Generation => 9; + public EntityContext Context => EntityContext.Gen9; + public int EggLocation => 0; + public bool IsShiny => Shiny == Shiny.Always; + public bool EggEncounter => false; + int ILocation.Location => Location; + + public Ball FixedBall { get; init; } + public required ushort Location { get; init; } + public required ushort Species { get; init; } + public required byte Level { get; init; } + public byte Form { get; init; } + public sbyte Gender { get; init; } = -1; + public AbilityPermission Ability { get; init; } + public byte FlawlessIVCount { get; init; } + public Shiny Shiny { get; init; } + public Moveset Moves { get; init; } + public IndividualValueSet IVs { get; init; } + public Nature Nature { get; init; } = Nature.Random; + public GemType TeraType { get; init; } + public byte Size { get; init; } + public bool IsTitan { get; init; } + + private bool Gift => FixedBall != Ball.None; + + private bool NoScalarsDefined => Size == 0; + public bool GiftWithLanguage => Gift && !ScriptedYungoos; // Nice error by GameFreak -- all gifts (including eggs) set the HT_Language memory value in addition to OT_Language. + public bool StarterBoxLegend => Gift && Species is (int)Core.Species.Koraidon or (int)Core.Species.Miraidon; + public bool ScriptedYungoos => Species == (int)Core.Species.Yungoos && Level == 2; + + public SizeType9 ScaleType => NoScalarsDefined ? SizeType9.RANDOM : SizeType9.VALUE; + public string Name => "Static Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK9 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PK9 + { + Language = lang, + Species = Species, + Form = Form, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.SV[Species, Form].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (int)version, + Ball = (byte)Ball.Poke, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + Obedience_Level = LevelMin, + }; + + if (Gift && !ScriptedYungoos) + pk.HT_Language = (byte)pk.Language; + if (StarterBoxLegend) + pk.FormArgument = 1; // Not Ride Form. + if (IsTitan) + pk.RibbonMarkTitan = true; + + SetPINGA(pk, criteria); + pk.SetMoves(Moves); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PK9 pk, EncounterCriteria criteria) + { + const byte undefinedSize = 0; + byte height, weight, scale; + if (NoScalarsDefined) + { + height = weight = scale = undefinedSize; + } + else + { + // Gifts have a defined H/W/S, while capture-able only have scale. + height = weight = Gift ? Size : undefinedSize; + scale = Size; + } + + const byte rollCount = 1; + var pi = PersonalTable.SV.GetFormEntry(Species, Form); + var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, rollCount, height, weight, ScaleType, scale, + Ability, Shiny); + + ulong init = Util.Rand.Rand64(); + var success = this.TryApply64(pk, init, param, criteria, IVs.IsSpecified); + if (!success) + this.TryApply64(pk, init, param, EncounterCriteria.Unrestricted, IVs.IsSpecified); + if (IVs.IsSpecified) + { + pk.IV_HP = IVs.HP; + pk.IV_ATK = IVs.ATK; + pk.IV_DEF = IVs.DEF; + pk.IV_SPA = IVs.SPA; + pk.IV_SPD = IVs.SPD; + pk.IV_SPE = IVs.SPE; + } + + if (Gender != -1) + pk.Gender = (byte)Gender; + if (Nature != Nature.Random) + pk.Nature = pk.StatNature = (int)Nature; + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk)) + return false; + if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal)) + return false; + + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + private bool IsMatchLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchLocationRemapped(pk); + return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return IsMatchDeferred(pk); + } + + private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; + + private bool IsMatchLocationRemapped(PKM pk) + { + var met = (ushort)pk.Met_Location; + var version = pk.Version; + if (pk.Context == EntityContext.Gen8) + return LocationsHOME.IsValidMetSV(met, version); + return LocationsHOME.GetMetSWSH(Location, version) == met; + } + + private EncounterMatchRating IsMatchDeferred(PKM pk) + { + if (Shiny != Shiny.Random && !Shiny.IsValid(pk)) + return EncounterMatchRating.DeferredErrors; + + if (Ability != Any12H) + { + // HA-Only is a strict match. Ability Capsule and Patch can potentially change these. + var num = pk.AbilityNumber; + if (num == 4) + { + if (Ability is not OnlyHidden && !AbilityVerifier.CanAbilityPatch(9, PersonalTable.SV.GetFormEntry(Species, Form), pk.Species)) + return EncounterMatchRating.DeferredErrors; + } + else if (Ability.IsSingleValue(out int index) && 1 << index != num) // Fixed regular ability + { + if (Ability is OnlyFirst or OnlySecond && !AbilityVerifier.CanAbilityCapsule(9, PersonalTable.SV.GetFormEntry(Species, Form))) + return EncounterMatchRating.DeferredErrors; + } + } + + return EncounterMatchRating.Match; + } + + private bool IsMatchPartial(PKM pk) + { + switch (Shiny) + { + case Shiny.Never when pk.IsShiny: + case Shiny.Always when !pk.IsShiny: + return true; + } + + if (pk is IScaledSize v && !NoScalarsDefined) + { + if (Gift) + { + if (v.HeightScalar != Size) + return true; + if (v.WeightScalar != Size) + return true; + } + var current = pk is IScaledSize3 size3 ? size3.Scale : v.HeightScalar; + if (current != Size) + return false; + } + + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return false; + } + + #endregion +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterTera9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterTera9.cs similarity index 51% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterTera9.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterTera9.cs index 0ab5ff25c..61e7d42db 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterTera9.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterTera9.cs @@ -1,5 +1,5 @@ using System; -using System.Buffers.Binary; +using static System.Buffers.Binary.BinaryPrimitives; using static PKHeX.Core.AbilityPermission; namespace PKHeX.Core; @@ -7,21 +7,42 @@ namespace PKHeX.Core; /// /// Generation 9 Tera Raid Encounter /// -public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9 +public sealed record EncounterTera9 + : IEncounterable, IEncounterMatch, IEncounterConvertible, ITeraRaid9, IMoveset, IFlawlessIVCount, IFixedGender { - public override int Generation => 9; - public override int Location => Locations.TeraCavern9; - public override EntityContext Context => EntityContext.Gen9; + public int Generation => 9; + public EntityContext Context => EntityContext.Gen9; + public GameVersion Version => GameVersion.SV; + int ILocation.Location => Location; + public const ushort Location = Locations.TeraCavern9; public bool IsDistribution => Index != 0; - public GemType TeraType { get; private init; } - public byte Index { get; private init; } - public byte Stars { get; private init; } - public byte RandRate { get; private init; } // weight chance of this encounter - public short RandRateMinScarlet { get; private init; } // weight chance total of all lower index encounters, for Scarlet - public short RandRateMinViolet { get; private init; } // weight chance total of all lower index encounters, for Violet + public Ball FixedBall => Ball.None; + public bool EggEncounter => false; + public bool IsShiny => Shiny == Shiny.Always; + public int EggLocation => 0; + + public required ushort Species { get; init; } + public required byte Form { get; init; } + public required sbyte Gender { get; init; } + public required AbilityPermission Ability { get; init; } + public required byte FlawlessIVCount { get; init; } + public required Shiny Shiny { get; init; } + public required byte Level { get; init; } + public required Moveset Moves { get; init; } + public required GemType TeraType { get; init; } + public required byte Index { get; init; } + public required byte Stars { get; init; } + public required byte RandRate { get; init; } // weight chance of this encounter + public required short RandRateMinScarlet { get; init; } // weight chance total of all lower index encounters, for Scarlet + public required short RandRateMinViolet { get; init; } // weight chance total of all lower index encounters, for Violet public bool IsAvailableHostScarlet => RandRateMinScarlet != -1; public bool IsAvailableHostViolet => RandRateMinViolet != -1; + public string Name => "Tera Raid Encounter"; + public string LongName => Name; + public byte LevelMin => Level; + public byte LevelMax => Level; + public bool CanBeEncountered(uint seed) => Tera9RNG.IsMatchStarChoice(seed, Stars, RandRate, RandRateMinScarlet, RandRateMinViolet); /// @@ -61,11 +82,9 @@ public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9 return result; } - private EncounterTera9() : base(GameVersion.SV) { } - private static EncounterTera9 ReadEncounter(ReadOnlySpan data) => new() { - Species = BinaryPrimitives.ReadUInt16LittleEndian(data), + Species = ReadUInt16LittleEndian(data), Form = data[0x02], Gender = (sbyte)(data[0x03] - 1), Ability = GetAbility(data[0x04]), @@ -73,16 +92,16 @@ public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9 Shiny = data[0x06] switch { 0 => Shiny.Random, 1 => Shiny.Never, 2 => Shiny.Always, _ => throw new ArgumentOutOfRangeException(nameof(data)) }, Level = data[0x07], Moves = new Moveset( - BinaryPrimitives.ReadUInt16LittleEndian(data[0x08..]), - BinaryPrimitives.ReadUInt16LittleEndian(data[0x0A..]), - BinaryPrimitives.ReadUInt16LittleEndian(data[0x0C..]), - BinaryPrimitives.ReadUInt16LittleEndian(data[0x0E..])), + ReadUInt16LittleEndian(data[0x08..]), + ReadUInt16LittleEndian(data[0x0A..]), + ReadUInt16LittleEndian(data[0x0C..]), + ReadUInt16LittleEndian(data[0x0E..])), TeraType = (GemType)data[0x10], Index = data[0x11], Stars = data[0x12], RandRate = data[0x13], - RandRateMinScarlet = BinaryPrimitives.ReadInt16LittleEndian(data[0x14..]), - RandRateMinViolet = BinaryPrimitives.ReadInt16LittleEndian(data[0x16..]), + RandRateMinScarlet = ReadInt16LittleEndian(data[0x14..]), + RandRateMinViolet = ReadInt16LittleEndian(data[0x16..]), }; private static AbilityPermission GetAbility(byte b) => b switch @@ -95,7 +114,76 @@ public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9 _ => throw new ArgumentOutOfRangeException(nameof(b), b, null), }; - protected override bool IsMatchLocation(PKM pk) + #region Generating + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + public PK9 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language); + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + var pk = new PK9 + { + Language = lang, + Species = Species, + Form = Form, + CurrentLevel = LevelMin, + OT_Friendship = PersonalTable.SV[Species, Form].BaseFriendship, + Met_Location = Location, + Met_Level = LevelMin, + Version = (int)version, + Ball = (byte)Ball.Poke, + + Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), + Obedience_Level = LevelMin, + }; + SetPINGA(pk, criteria); + pk.SetMoves(Moves); + + pk.ResetPartyStats(); + return pk; + } + + private void SetPINGA(PK9 pk, EncounterCriteria criteria) + { + const byte rollCount = 1; + const byte undefinedSize = 0; + var pi = PersonalTable.SV.GetFormEntry(Species, Form); + var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, rollCount, + undefinedSize, undefinedSize, undefinedSize, undefinedSize, + Ability, Shiny); + + var init = Util.Rand.Rand64(); + var success = this.TryApply32(pk, init, param, criteria); + if (!success) + this.TryApply32(pk, init, param, EncounterCriteria.Unrestricted); + } + #endregion + + #region Matching + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!this.IsLevelWithinRange(pk.Met_Level)) + return false; + if (Gender != -1 && pk.Gender != Gender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : EggLocation; + return pk.Egg_Location == expect; + } + + private bool IsMatchLocation(PKM pk) { var metState = LocationsHOME.GetRemapState(Context, pk.Context); if (metState == LocationRemapState.Original) @@ -105,6 +193,13 @@ public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9 return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); } + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return IsMatchDeferred(pk); + } + private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; private bool IsMatchLocationRemapped(PKM pk) @@ -113,11 +208,14 @@ public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9 var version = pk.Version; if (pk.Context == EntityContext.Gen8) return LocationsHOME.IsValidMetSV(met, version); - return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; + return LocationsHOME.GetMetSWSH(Location, version) == met; } - protected override EncounterMatchRating IsMatchDeferred(PKM pk) + private EncounterMatchRating IsMatchDeferred(PKM pk) { + if (Shiny != Shiny.Random && !Shiny.IsValid(pk)) + return EncounterMatchRating.DeferredErrors; + if (Ability != Any12H) { // HA-Only is a strict match. Ability Capsule and Patch can potentially change these. @@ -134,10 +232,10 @@ public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9 } } - return base.IsMatchDeferred(pk); + return EncounterMatchRating.Match; } - protected override bool IsMatchPartial(PKM pk) + private bool IsMatchPartial(PKM pk) { switch (Shiny) { @@ -156,30 +254,11 @@ public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9 var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, 1, 0, 0, 0, 0, Ability, Shiny); if (!Encounter9RNG.IsMatch(pk, param, seed)) return true; - return base.IsMatchPartial(pk); + + if (pk is { AbilityNumber: 4 } && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return false; } - protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(tr, criteria, pk); - var pk9 = (PK9)pk; - pk9.Obedience_Level = (byte)pk9.Met_Level; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pk9 = (PK9)pk; - - const byte rollCount = 1; - const byte undefinedSize = 0; - var pi = PersonalTable.SV.GetFormEntry(Species, Form); - var param = new GenerateParam9(Species, pi.Gender, FlawlessIVCount, rollCount, - undefinedSize, undefinedSize, undefinedSize, undefinedSize, - Ability, Shiny); - - var init = Util.Rand.Rand64(); - var success = this.TryApply32(pk9, init, param, criteria); - if (!success) - this.TryApply32(pk9, init, param, EncounterCriteria.Unrestricted); - } + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterTrade9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterTrade9.cs new file mode 100644 index 000000000..c69157f65 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9/EncounterTrade9.cs @@ -0,0 +1,210 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generation 9 Trade Encounter +/// +public sealed record EncounterTrade9 + : IEncounterable, IEncounterMatch, IFixedTrainer, IFixedNickname, IEncounterConvertible, IGemType +{ + public int Generation => 9; + public EntityContext Context => EntityContext.Gen9; + public int Location => Locations.LinkTrade6NPC; + public Shiny Shiny => Shiny.Never; + public bool EggEncounter => false; + public Ball FixedBall => Ball.Poke; + public bool IsShiny => false; + public int EggLocation => 0; + public bool IsFixedTrainer => true; + public bool IsFixedNickname => true; + public GameVersion Version { get; } + + private string[] TrainerNames { get; } + private string[] Nicknames { get; } + + public required Nature Nature { get; init; } + public required ushort ID32 { get; init; } + public required AbilityPermission Ability { get; init; } + public required byte Gender { get; init; } + public required byte OTGender { get; init; } + public required IndividualValueSet IVs { get; init; } + public ushort Species { get; } + public byte Level { get; } + public bool EvolveOnTrade { get; init; } + + public byte Form => 0; + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public byte LevelMin => Level; + public byte LevelMax => Level; + + public GemType TeraType => GemType.Default; + + public EncounterTrade9(ReadOnlySpan names, byte index, GameVersion game, ushort species, byte level) + { + Version = game; + Nicknames = EncounterUtil.GetNamesForLanguage(names, index); + TrainerNames = EncounterUtil.GetNamesForLanguage(names, (uint)(index + (names[1].Length >> 1))); + Species = species; + Level = level; + } + + #region Generating + + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + + public PK9 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + var pk = new PK9 + { + Species = Species, + CurrentLevel = Level, + Met_Location = Location, + Met_Level = Level, + MetDate = EncounterDate.GetDateSwitch(), + Gender = Gender, + Nature = (byte)Nature, + StatNature = (byte)Nature, + Ball = (byte)FixedBall, + + ID32 = ID32, + Version = (byte)version, + Language = lang, + OT_Gender = OTGender, + OT_Name = TrainerNames[lang], + + OT_Friendship = PersonalTable.SV[Species, Form].BaseFriendship, + + IsNicknamed = true, + Nickname = Nicknames[lang], + + HeightScalar = PokeSizeUtil.GetRandomScalar(), + WeightScalar = PokeSizeUtil.GetRandomScalar(), + Scale = PokeSizeUtil.GetRandomScalar(), + TeraTypeOriginal = GetOriginalTeraType(), + + HT_Name = tr.OT, + HT_Language = (byte)tr.Language, + CurrentHandler = 1, + HT_Friendship = PersonalTable.SV[Species, Form].BaseFriendship, + Obedience_Level = Level, + }; + + EncounterUtil1.SetEncounterMoves(pk, version, Level); + SetPINGA(pk, criteria); + + pk.ResetPartyStats(); + + return pk; + } + + private void SetPINGA(PK9 pk, EncounterCriteria criteria) + { + pk.PID = Util.Rand32(); + pk.EncryptionConstant = Util.Rand32(); + pk.StatNature = pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, PersonalTable.SV.GetFormEntry(Species, Form)); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + pk.SetRandomIVsTemplate(IVs, 0); + } + + private MoveType GetOriginalTeraType() + { + if (TeraType is GemType.Default) + return (MoveType)PersonalTable.SV.GetFormEntry(Species, Form).Type1; + if (TeraType.IsSpecified(out var type)) + return (MoveType)type; + return (MoveType)Util.Rand.Next(0, 18); + } + + #endregion + + #region Matching + + public bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language) => (uint)language < TrainerNames.Length && trainer.SequenceEqual(TrainerNames[language]); + public bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language) => (uint)language < Nicknames.Length && nickname.SequenceEqual(Nicknames[language]); + public string GetNickname(int language) => (uint)language < Nicknames.Length ? Nicknames[language] : Nicknames[0]; + + private bool IsMatchNatureGenderShiny(PKM pk) + { + if (!Shiny.IsValid(pk)) + return false; + if (pk.Gender != Gender) + return false; + if (pk.Nature != (int)Nature) + return false; + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match; + + #endregion + + public bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.Met_Level != Level) + return false; + if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!Legal.GetIsFixedIVSequenceValidNoRand(IVs, pk)) + return false; + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (pk.ID32 != ID32) + return false; + if (evo.Form != Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context)) + return false; + if (pk.OT_Gender != OTGender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + if (EvolveOnTrade && pk.Species == Species) + return false; + return true; + } + + private bool IsMatchEggLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchEggLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchEggLocationRemapped(pk); + // Either + return IsMatchEggLocationExact(pk) || IsMatchEggLocationRemapped(pk); + } + + private static bool IsMatchEggLocationRemapped(PKM pk) => pk.Egg_Location == 0; + private bool IsMatchEggLocationExact(PKM pk) => pk.Egg_Location == EggLocation; + + private bool IsMatchLocation(PKM pk) + { + var metState = LocationsHOME.GetRemapState(Context, pk.Context); + if (metState == LocationRemapState.Original) + return IsMatchLocationExact(pk); + if (metState == LocationRemapState.Remapped) + return IsMatchLocationRemapped(pk); + return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk); + } + + private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location; + + private bool IsMatchLocationRemapped(PKM pk) + { + var met = (ushort)pk.Met_Location; + var version = pk.Version; + if (pk.Context == EntityContext.Gen8) + return LocationsHOME.IsValidMetSV(met, version); + return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; + } +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/IGemType.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/IGemType.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/IGemType.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen9/IGemType.cs diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/ITeraRaid9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/ITeraRaid9.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/ITeraRaid9.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen9/ITeraRaid9.cs diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/SizeType9.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9/SizeType9.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/EncounterStatic/SizeType9.cs rename to PKHeX.Core/Legality/Encounters/Templates/Gen9/SizeType9.cs diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterArea.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterArea.cs new file mode 100644 index 000000000..e634ad72b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterArea.cs @@ -0,0 +1,21 @@ +namespace PKHeX.Core; + +/// +/// Contains a collection of for the area. +/// +/// Encounter Slot type. +public interface IEncounterArea where T : IEncounterTemplate, IVersion +{ + /// + /// Slots in the area. + /// + T[] Slots { get; } +} + +/// +/// Area contains matching location logic. +/// +public interface IAreaLocation +{ + bool IsMatchLocation(int location); +} diff --git a/PKHeX.Core/Legality/Encounters/IEncounterConvertible.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterConvertible.cs similarity index 52% rename from PKHeX.Core/Legality/Encounters/IEncounterConvertible.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterConvertible.cs index 314c43fe9..5c0296e41 100644 --- a/PKHeX.Core/Legality/Encounters/IEncounterConvertible.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterConvertible.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// /// Exposes conversion methods to create a from the object's data. @@ -17,3 +17,18 @@ public interface IEncounterConvertible /// PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria); } + +public interface IEncounterConvertible where T : PKM +{ + /// + /// Creates a from the template, using the input as the trainer data. + /// + /// This method calls with a fixed criteria containing no restrictions on the generated data. + T ConvertToPKM(ITrainerInfo tr); + + /// + /// Creates a from the template, using the input as the trainer data. + ///
The generation routine will try to yield a result that matches the specifications in the .
+ ///
+ T ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria); +} diff --git a/PKHeX.Core/Legality/Encounters/IEncounterInfo.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterInfo.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/IEncounterInfo.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterInfo.cs diff --git a/PKHeX.Core/Legality/Encounters/IEncounterMatch.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterMatch.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/IEncounterMatch.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterMatch.cs diff --git a/PKHeX.Core/Legality/Encounters/IEncounterTemplate.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterTemplate.cs similarity index 51% rename from PKHeX.Core/Legality/Encounters/IEncounterTemplate.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterTemplate.cs index 1f578cf83..336e451b3 100644 --- a/PKHeX.Core/Legality/Encounters/IEncounterTemplate.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterTemplate.cs @@ -3,7 +3,7 @@ namespace PKHeX.Core; /// /// Represents all details that an entity may be encountered with. /// -public interface IEncounterTemplate : ISpeciesForm, IVersion, IGeneration, IShiny +public interface IEncounterTemplate : ISpeciesForm, IVersion, IGeneration, IShiny, ILevelRange { /// /// Original Context @@ -14,33 +14,19 @@ public interface IEncounterTemplate : ISpeciesForm, IVersion, IGeneration, IShin /// Indicates if the encounter originated as an egg. /// bool EggEncounter { get; } - - /// - /// Minimum level for the encounter. - /// - byte LevelMin { get; } - - /// - /// Maximum level for the encounter. - /// - byte LevelMax { get; } } public static partial class Extensions { - private static bool IsWithinEncounterRange(this IEncounterTemplate encounter, int lvl) - { - return encounter.LevelMin <= lvl && lvl <= encounter.LevelMax; - } - public static bool IsWithinEncounterRange(this IEncounterTemplate encounter, PKM pk) { + var level = pk.CurrentLevel; if (!pk.HasOriginalMetLocation) - return encounter.IsWithinEncounterRange(pk.CurrentLevel); + return encounter.IsLevelWithinRange(level); if (encounter.EggEncounter) - return pk.CurrentLevel == encounter.LevelMin; + return level == encounter.LevelMin; if (encounter is MysteryGift g) - return pk.CurrentLevel == g.Level; - return pk.CurrentLevel == pk.Met_Level; + return level == g.Level; + return level == pk.Met_Level; } } diff --git a/PKHeX.Core/Legality/Encounters/IEncounterable.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterable.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/IEncounterable.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/IEncounterable.cs diff --git a/PKHeX.Core/Legality/Structures/ILocation.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/ILocation.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/ILocation.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/ILocation.cs diff --git a/PKHeX.Core/Legality/Encounters/IEncounterFormRandom.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IEncounterFormRandom.cs similarity index 70% rename from PKHeX.Core/Legality/Encounters/IEncounterFormRandom.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IEncounterFormRandom.cs index f7095800d..cdfce32d9 100644 --- a/PKHeX.Core/Legality/Encounters/IEncounterFormRandom.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IEncounterFormRandom.cs @@ -5,5 +5,8 @@ namespace PKHeX.Core; ///
public interface IEncounterFormRandom { + /// + /// Indicates if the form is random and unspecified. + /// bool IsRandomUnspecificForm { get; } } diff --git a/PKHeX.Core/Legality/Structures/IFixedAbilityNumber.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedAbilityNumber.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/IFixedAbilityNumber.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedAbilityNumber.cs diff --git a/PKHeX.Core/Legality/Structures/IFixedBall.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedBall.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/IFixedBall.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedBall.cs diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedGender.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedGender.cs new file mode 100644 index 000000000..d99aaaada --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedGender.cs @@ -0,0 +1,17 @@ +namespace PKHeX.Core; + +/// +/// Contains information about the initial gender of the encounter. +/// +public interface IFixedGender +{ + /// + /// Magic gender index for the encounter. + /// + sbyte Gender { get; } + + /// + /// Indicates if the gender is a single value (not random). + /// + bool IsFixedGender => Gender != -1; +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedNickname.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedNickname.cs new file mode 100644 index 000000000..1b66ad936 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedNickname.cs @@ -0,0 +1,30 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Contains information about initial Nickname Details +/// +public interface IFixedNickname +{ + /// + /// Indicates if the Nickname is specified by the encounter template. + /// + bool IsFixedNickname { get; } + + /// + /// Checks if the specified nickname matches the encounter template. + /// + /// Entity to check. + /// Trainer name to check. + /// Language ID to check with. + /// True if matches. + bool IsNicknameMatch(PKM pk, ReadOnlySpan nickname, int language); + + /// + /// Gets the nickname for the specified language. + /// + /// Language ID to check with. + /// Localized nickname. + string GetNickname(int language); +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedTrainer.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedTrainer.cs new file mode 100644 index 000000000..7f8f94aec --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFixedTrainer.cs @@ -0,0 +1,23 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Contains information about initial Trainer Details +/// +public interface IFixedTrainer +{ + /// + /// Indicates if the Trainer Name / details are specified by the encounter template. + /// + bool IsFixedTrainer { get; } + + /// + /// Checks if the Trainer Name / details match the encounter template. + /// + /// Entity to check. + /// Trainer name to check. + /// Language ID to check with. + /// True if matches. + bool IsTrainerMatch(PKM pk, ReadOnlySpan trainer, int language); +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFlawlessIVCount.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFlawlessIVCount.cs new file mode 100644 index 000000000..a375206aa --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFlawlessIVCount.cs @@ -0,0 +1,12 @@ +namespace PKHeX.Core; + +/// +/// Contains information about the number of perfect IVs the object has. +/// +public interface IFlawlessIVCount +{ + /// + /// Number of perfect IVs the object has. + /// + byte FlawlessIVCount { get; } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFlawlessIVCountConditional.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFlawlessIVCountConditional.cs new file mode 100644 index 000000000..efcb16932 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IFlawlessIVCountConditional.cs @@ -0,0 +1,13 @@ +namespace PKHeX.Core; + +/// +/// Contains information about the number of perfect IVs the object originates with. +/// +public interface IFlawlessIVCountConditional +{ + /// + /// Number of perfect IVs the object originates with. + /// + /// Entity to check + (byte Min, byte Max) GetFlawlessIVCount(PKM pk); +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IHatchCycle.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IHatchCycle.cs new file mode 100644 index 000000000..932375ee1 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IHatchCycle.cs @@ -0,0 +1,15 @@ +namespace PKHeX.Core; + +/// +/// Contains information about initial Hatch Cycles +/// +public interface IHatchCycle +{ + /// + /// Number of Hatch Cycles required to hatch an egg. + /// + /// + /// When non-zero, this value is used instead of the default value from Personal data. + /// + byte EggCycles { get; } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/ILevelRange.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/ILevelRange.cs new file mode 100644 index 000000000..f72e8ad63 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/ILevelRange.cs @@ -0,0 +1,61 @@ +namespace PKHeX.Core; + +/// +/// Contains information about the level range the object originates with. +/// +public interface ILevelRange +{ + /// + /// Minimum level. + /// + byte LevelMin { get; } + + /// + /// Maximum level. + /// + byte LevelMax { get; } +} + +public static class ILevelRangeExtensions +{ + public static bool IsFixedLevel(this ILevelRange r) => r.LevelMin == r.LevelMax; + public static bool IsRandomLevel(this ILevelRange r) => r.LevelMin != r.LevelMax; + + /// + /// Gets if the specified level inputs are within range of the and + /// + /// Range reference + /// Single level + /// True if within slot's range, false if impossible. + public static bool IsLevelWithinRange(this ILevelRange r, int lvl) => r.LevelMin <= lvl && lvl <= r.LevelMax; + + /// + /// Gets if the specified level inputs are within range of the and + /// + /// Range reference + /// Highest value the low end of levels can be + /// Lowest value the high end of levels can be + /// True if within slot's range, false if impossible. + public static bool IsLevelWithinRange(this ILevelRange r, byte min, byte max) => r.LevelMin <= max && min <= r.LevelMax; + + /// + /// Gets if the specified level inputs are within range of the and + /// + /// Range reference + /// Single level + /// Highest value the low end of levels can be + /// Lowest value the high end of levels can be + /// True if within slot's range, false if impossible. + public static bool IsLevelWithinRange(this ILevelRange r, int lvl, int minDecrease, int maxIncrease) => r.LevelMin - minDecrease <= lvl && lvl <= r.LevelMax + maxIncrease; + + /// + /// Gets if the specified level inputs are within range of the and + /// + /// Range reference + /// Lowest level allowed + /// Highest level allowed + /// Highest value the low end of levels can be + /// Lowest value the high end of levels can be + /// True if within slot's range, false if impossible. + public static bool IsLevelWithinRange(this ILevelRange r, byte min, byte max, int minDecrease, int maxIncrease) => r.LevelMin - minDecrease <= max && min <= r.LevelMax + maxIncrease; +} diff --git a/PKHeX.Core/Legality/Structures/IMoveset.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IMoveset.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/IMoveset.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IMoveset.cs diff --git a/PKHeX.Core/Legality/Structures/IRelearn.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IRelearn.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/IRelearn.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IRelearn.cs diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IRestrictVersion.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IRestrictVersion.cs new file mode 100644 index 000000000..427ac19fb --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IRestrictVersion.cs @@ -0,0 +1,12 @@ +namespace PKHeX.Core; + +public interface IRestrictVersion +{ + bool CanBeReceivedByVersion(int version); +} + +public interface IRandomCorrelation +{ + bool IsCompatible(PIDType val, PKM pk); + PIDType GetSuggestedCorrelation(); +} diff --git a/PKHeX.Core/Legality/Structures/IShinyPotential.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IShinyPotential.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/IShinyPotential.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IShinyPotential.cs diff --git a/PKHeX.Core/Legality/Structures/IVersion.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IVersion.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/IVersion.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/IVersion.cs diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/IMagnetStatic.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/IMagnetStatic.cs similarity index 93% rename from PKHeX.Core/Legality/Encounters/EncounterSlot/IMagnetStatic.cs rename to PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/IMagnetStatic.cs index c214219e1..5dac4b752 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/IMagnetStatic.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/IMagnetStatic.cs @@ -5,7 +5,7 @@ namespace PKHeX.Core; ///
/// /// When encountering a Wild Pokémon with a lead that has an ability of or , -/// the game picks encounters from the that match the type. +/// the game picks encounters from the that match the type. /// The values in this interface change the to allow different values for slot yielding. /// public interface IMagnetStatic diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/INumberedSlot.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/INumberedSlot.cs new file mode 100644 index 000000000..98b89a0fe --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/INumberedSlot.cs @@ -0,0 +1,15 @@ +namespace PKHeX.Core; + +/// +/// that contains information about what Index it is within the area's type-group of slots. +/// +/// +/// Useful for checking legality (if the RNG can yield this slot). +/// +public interface INumberedSlot +{ + /// + /// Number Index of the . + /// + byte SlotNumber { get; } +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/ISlotRNGType.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/ISlotRNGType.cs new file mode 100644 index 000000000..87109efd8 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/ISlotRNGType.cs @@ -0,0 +1,12 @@ +namespace PKHeX.Core; + +/// +/// Contains information about the slot types the object represents. +/// +public interface ISlotRNGType +{ + /// + /// Encounter Slot Type + /// + SlotType Type { get; } +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs b/PKHeX.Core/Legality/Encounters/Templates/Shared/EncounterEgg.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs rename to PKHeX.Core/Legality/Encounters/Templates/Shared/EncounterEgg.cs diff --git a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs b/PKHeX.Core/Legality/Encounters/Templates/Shared/EncounterInvalid.cs similarity index 100% rename from PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs rename to PKHeX.Core/Legality/Encounters/Templates/Shared/EncounterInvalid.cs diff --git a/PKHeX.Core/Legality/Structures/IndividualValueSet.cs b/PKHeX.Core/Legality/Encounters/Templates/Shared/IndividualValueSet.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/IndividualValueSet.cs rename to PKHeX.Core/Legality/Encounters/Templates/Shared/IndividualValueSet.cs diff --git a/PKHeX.Core/Legality/Structures/Moveset.cs b/PKHeX.Core/Legality/Encounters/Templates/Shared/Moveset.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/Moveset.cs rename to PKHeX.Core/Legality/Encounters/Templates/Shared/Moveset.cs diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs b/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs index e641c6bb0..7e02cbcb4 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs @@ -22,11 +22,14 @@ public static class EncounterVerifier private static CheckResult VerifyEncounter(PKM pk, IEncounterTemplate enc) => enc switch { EncounterEgg e => VerifyEncounterEgg(pk, e.Generation), - EncounterTrade t => VerifyEncounterTrade(pk, t), - EncounterSlot w => VerifyEncounterWild(w), - EncounterStatic s => VerifyEncounterStatic(pk, s), + { EggEncounter: true } => VerifyEncounterEgg(pk, enc.Generation), + EncounterShadow3Colo { EReader: true } when pk.Language != (int)LanguageID.Japanese => GetInvalid(LG3EReader), + EncounterStatic3 { Species: (int)Species.Mew, Location: 201 } when pk.Language != (int)LanguageID.Japanese => GetInvalid(LEncUnreleasedEMewJP), + EncounterStatic3 { Species: (int)Species.Deoxys, Location: 200 } when pk.Language == (int)LanguageID.Japanese => GetInvalid(LEncUnreleased), + EncounterStatic4 { Roaming: true } when pk is G4PKM { Met_Location: 193, GroundTile: GroundTileType.Water } => GetInvalid(LG4InvalidTileR45Surf), MysteryGift g => VerifyEncounterEvent(pk, g), - _ => GetInvalid(LEncInvalid), + EncounterInvalid => GetInvalid(LEncInvalid), + _ => GetValid(string.Empty), // todo: refactor }; private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc) @@ -38,45 +41,31 @@ public static class EncounterVerifier { EncounterSlot1 => GetValid(LEncCondition), EncounterSlot2 s2 => VerifyWildEncounterGen2(pk, s2), - EncounterStatic s => VerifyEncounterStatic(pk, s), - EncounterTrade t => VerifyEncounterTrade(pk, t), - _ => GetInvalid(LEncInvalid), + EncounterTrade1 t => VerifyEncounterTrade(pk, t), + EncounterTrade2 => GetValid(LEncTradeMatch), + _ => GetValid(string.Empty), // todo: refactor }; } // Gen2 Wild Encounters - private static CheckResult VerifyWildEncounterGen2(PKM pk, EncounterSlot2 encounter) + private static CheckResult VerifyWildEncounterGen2(ITrainerID16 pk, EncounterSlot2 enc) => enc.SlotType switch { - switch (encounter.SlotType) - { - case SlotType.Headbutt: - return VerifyWildEncounterCrystalHeadbutt(pk, encounter); - - case SlotType.Old_Rod or SlotType.Good_Rod or SlotType.Super_Rod: - switch (encounter.Location) - { - case 19: // National Park - return GetInvalid(LG2InvalidTilePark); - case 76: // Route 14 - return GetInvalid(LG2InvalidTileR14); - } - break; - } - - return GetValid(LEncCondition); - } - - private static CheckResult VerifyWildEncounterCrystalHeadbutt(ITrainerID32 tr, EncounterSlot2 s2) - { - return s2.IsTreeAvailable(tr.TID16) + SlotType.Headbutt => enc.IsTreeAvailable(pk.TID16) ? GetValid(LG2TreeID) - : GetInvalid(LG2InvalidTileTreeNotFound); - } + : GetInvalid(LG2InvalidTileTreeNotFound), + SlotType.Old_Rod or SlotType.Good_Rod or SlotType.Super_Rod => enc.Location switch + { + 19 => GetInvalid(LG2InvalidTilePark), // National Park + 76 => GetInvalid(LG2InvalidTileR14), // Route 14 + _ => GetValid(LEncCondition), + }, + _ => GetValid(LEncCondition), + }; // Eggs private static CheckResult VerifyEncounterEgg(PKM pk, int gen) => gen switch { - 2 => new CheckResult(CheckIdentifier.Encounter), // valid -- no met location info + 2 => pk.IsEgg ? VerifyUnhatchedEgg2(pk) : VerifyEncounterEgg2(pk), 3 => pk.IsEgg ? VerifyUnhatchedEgg3(pk) : VerifyEncounterEgg3(pk), 4 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade4) : VerifyEncounterEgg4(pk), 5 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade5) : VerifyEncounterEgg5(pk), @@ -88,6 +77,32 @@ public static class EncounterVerifier _ => GetInvalid(LEggLocationInvalid), }; + private static CheckResult VerifyEncounterEgg2(PKM pk) + { + if (pk is not ICaughtData2 { CaughtData: not 0 } c2) + return GetValid(LEggLocation); + + if (c2.Met_Level != 1) + return GetInvalid(string.Format(LEggFMetLevel_0, 1)); + + if (pk.Met_Location > 95) + return GetInvalid(LEggMetLocationFail); + // Any met location is fine. + return GetValid(LEggLocation); + } + + private static CheckResult VerifyUnhatchedEgg2(PKM pk) + { + if (pk is not ICaughtData2 { CaughtData: not 0 } c2) + return new CheckResult(CheckIdentifier.Encounter); + + if (c2.Met_Level != 1) + return GetInvalid(string.Format(LEggFMetLevel_0, 1)); + if (c2.Met_Location != 0) + return GetInvalid(LEggLocationInvalid); + return GetValid(LEggLocation); + } + private static CheckResult VerifyUnhatchedEgg3(PKM pk) { if (pk.Met_Level != 0) @@ -163,7 +178,7 @@ public static class EncounterVerifier } // Native - const byte level = 0; + const byte level = EggStateLegality.EggMetLevel34; if (pk.Met_Level != level) return GetInvalid(string.Format(LEggFMetLevel_0, level)); @@ -180,7 +195,7 @@ public static class EncounterVerifier private static CheckResult VerifyEncounterEgg5(PKM pk) { - const byte level = 1; + const byte level = EggStateLegality.EggMetLevel; if (pk.Met_Level != level) return GetInvalid(string.Format(LEggFMetLevel_0, level)); @@ -194,7 +209,7 @@ public static class EncounterVerifier private static CheckResult VerifyEncounterEgg6(PKM pk) { - const byte level = 1; + const byte level = EggStateLegality.EggMetLevel; if (pk.Met_Level != level) return GetInvalid(string.Format(LEggFMetLevel_0, level)); @@ -210,7 +225,7 @@ public static class EncounterVerifier private static CheckResult VerifyEncounterEgg7(PKM pk) { - const byte level = 1; + const byte level = EggStateLegality.EggMetLevel; if (pk.Met_Level != level) return GetInvalid(string.Format(LEggFMetLevel_0, level)); @@ -226,7 +241,7 @@ public static class EncounterVerifier private static CheckResult VerifyEncounterEgg8(PKM pk) { - const byte level = 1; + const byte level = EggStateLegality.EggMetLevel; if (pk.Met_Level != level) return GetInvalid(string.Format(LEggFMetLevel_0, level)); @@ -250,7 +265,7 @@ public static class EncounterVerifier if (pk is PK8) return VerifyEncounterEgg8(pk); - const byte level = 1; + const byte level = EggStateLegality.EggMetLevel; if (pk.Met_Level != level) return GetInvalid(string.Format(LEggFMetLevel_0, level)); @@ -269,7 +284,7 @@ public static class EncounterVerifier if (pk is PK8) return VerifyEncounterEgg8(pk); - const byte level = 1; + const byte level = EggStateLegality.EggMetLevel; if (pk.Met_Level != level) return GetInvalid(string.Format(LEggFMetLevel_0, level)); @@ -285,7 +300,7 @@ public static class EncounterVerifier private static CheckResult VerifyUnhatchedEgg(PKM pk, int tradeLoc, int noneLoc = 0) { - var eggLevel = pk.Format < 5 ? 0 : 1; + var eggLevel = pk.Format is 3 or 4 ? EggStateLegality.EggMetLevel34 : EggStateLegality.EggMetLevel; if (pk.Met_Level != eggLevel) return GetInvalid(string.Format(LEggFMetLevel_0, eggLevel)); if (pk.Egg_Location == tradeLoc) @@ -299,51 +314,7 @@ public static class EncounterVerifier : GetInvalid(LEggLocationNone); } - // Other - private static CheckResult VerifyEncounterWild(EncounterSlot slot) - { - var summary = slot.GetConditionString(out bool valid); - return valid ? GetValid(summary) : GetInvalid(summary); - } - - private static CheckResult VerifyEncounterStatic(PKM pk, EncounterStatic s) - { - // Check for Unreleased Encounters / Collisions - switch (s.Generation) - { - case 3: - if (s is EncounterStaticShadow {EReader: true} && pk.Language != (int)LanguageID.Japanese) // Non-JP E-reader Pokemon - return GetInvalid(LG3EReader); - - switch (s.Species) - { - case (int)Species.Mew when s.Location == 201 && pk.Language != (int)LanguageID.Japanese: // Non-JP Mew (Old Sea Map) - return GetInvalid(LEncUnreleasedEMewJP); - case (int)Species.Deoxys when s.Location == 200 && pk.Language == (int)LanguageID.Japanese: // JP Deoxys (Birth Island) - return GetInvalid(LEncUnreleased); - } - - break; - case 4: - if (s is EncounterStatic4 {Roaming: true} && pk.Met_Location == 193 && pk is IGroundTile {GroundTile:GroundTileType.Water}) // Roaming pokemon surfing in Johto Route 45 - return GetInvalid(LG4InvalidTileR45Surf); - break; - case 7: - if (s.EggLocation == Locations.Daycare5 && pk.RelearnMove1 != 0) // Eevee gift egg - return GetInvalid(LEncStaticRelearn, CheckIdentifier.RelearnMove); // not gift egg - break; - } - if (s.EggEncounter && !pk.IsEgg) // hatched - { - var hatchCheck = VerifyEncounterEgg(pk, s.Generation); - if (!hatchCheck.Valid) - return hatchCheck; - } - - return GetValid(LEncStaticMatch); - } - - private static CheckResult VerifyEncounterTrade(ISpeciesForm pk, EncounterTrade trade) + private static CheckResult VerifyEncounterTrade(ISpeciesForm pk, EncounterTrade1 trade) { var species = pk.Species; if (trade.EvolveOnTrade && trade.Species == species) diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup1.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup1.cs index 9790a2798..4f15194f3 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup1.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup1.cs @@ -40,7 +40,7 @@ public sealed class EvolutionGroup1 : IEvolutionGroup, IEvolutionEnvironment return present; } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -70,7 +70,7 @@ public sealed class EvolutionGroup1 : IEvolutionGroup, IEvolutionEnvironment return present; } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup2.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup2.cs index 5b5d7c943..79d36a80d 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup2.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup2.cs @@ -37,7 +37,7 @@ public sealed class EvolutionGroup2 : IEvolutionGroup return present; } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -67,7 +67,7 @@ public sealed class EvolutionGroup2 : IEvolutionGroup return present; } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup3.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup3.cs index b13807b96..58c6887df 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup3.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup3.cs @@ -37,7 +37,7 @@ public sealed class EvolutionGroup3 : IEvolutionGroup return present; } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -67,7 +67,7 @@ public sealed class EvolutionGroup3 : IEvolutionGroup return present; } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup4.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup4.cs index 765ca4fc3..1ac8b22eb 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup4.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup4.cs @@ -37,7 +37,7 @@ public sealed class EvolutionGroup4 : IEvolutionGroup return present; } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -69,7 +69,7 @@ public sealed class EvolutionGroup4 : IEvolutionGroup return present; } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup5.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup5.cs index c09b74906..fa8f121c7 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup5.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup5.cs @@ -30,7 +30,7 @@ public sealed class EvolutionGroup5 : IEvolutionGroup return present; } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -60,7 +60,7 @@ public sealed class EvolutionGroup5 : IEvolutionGroup return present; } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup6.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup6.cs index fea06ff16..b1b513ace 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup6.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup6.cs @@ -37,7 +37,7 @@ public sealed class EvolutionGroup6 : IEvolutionGroup evo = evo with { Form = 0 }; // Normal } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -64,7 +64,7 @@ public sealed class EvolutionGroup6 : IEvolutionGroup return present; } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup7.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup7.cs index c66a1e8ee..4f63b5f3f 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup7.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup7.cs @@ -73,12 +73,12 @@ public sealed class EvolutionGroup7 : IEvolutionGroup return present; } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { var b = Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); return b && !IsEvolutionBanned(pk, result); diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup7b.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup7b.cs index 4ed405645..19305b746 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup7b.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup7b.cs @@ -30,7 +30,7 @@ public sealed class EvolutionGroup7b : IEvolutionGroup return present; } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -57,7 +57,7 @@ public sealed class EvolutionGroup7b : IEvolutionGroup return present; } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroupHOME.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroupHOME.cs index a07145f88..efe44304e 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroupHOME.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroupHOME.cs @@ -216,12 +216,12 @@ public sealed class EvolutionEnvironment8 : IEvolutionEnvironment { private static readonly EvolutionTree Tree = EvolutionTree.Evolves8; - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { var b = Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); return b && !IsEvolutionBanned(pk, head); @@ -241,10 +241,10 @@ public sealed class EvolutionEnvironment8a : IEvolutionEnvironment { private static readonly EvolutionTree Tree = EvolutionTree.Evolves8a; - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm => Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm => Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -252,10 +252,10 @@ public sealed class EvolutionEnvironment8b : IEvolutionEnvironment { private static readonly EvolutionTree Tree = EvolutionTree.Evolves8b; - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm => Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm => Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); } @@ -263,10 +263,10 @@ public sealed class EvolutionEnvironment9 : IEvolutionEnvironment { private static readonly EvolutionTree Tree = EvolutionTree.Evolves9; - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm => Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result); - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { var b = Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result); return b && !IsEvolutionBanned(head); diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/IEvolutionGroup.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/IEvolutionGroup.cs index 2c72ef6d6..7dd3a33b1 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/IEvolutionGroup.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/IEvolutionGroup.cs @@ -51,11 +51,11 @@ public interface IEvolutionEnvironment /// Attempts to devolve the provided to the previous evolution. ///
/// True if the de-evolution was successful and should be used. - bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result); + bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm; /// /// Attempts to evolve the provided to the next evolution. /// /// True if the evolution was successful and should be used. - bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result); + bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm; } diff --git a/PKHeX.Core/Legality/Evolutions/Forward/EvolutionForwardPersonal.cs b/PKHeX.Core/Legality/Evolutions/Forward/EvolutionForwardPersonal.cs index 5fdd95aef..7a029895e 100644 --- a/PKHeX.Core/Legality/Evolutions/Forward/EvolutionForwardPersonal.cs +++ b/PKHeX.Core/Legality/Evolutions/Forward/EvolutionForwardPersonal.cs @@ -45,7 +45,7 @@ public sealed class EvolutionForwardPersonal : IEvolutionForward } } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { var methods = GetForward(head.Species, head.Form); foreach (var method in methods.Span) diff --git a/PKHeX.Core/Legality/Evolutions/Forward/EvolutionForwardSpecies.cs b/PKHeX.Core/Legality/Evolutions/Forward/EvolutionForwardSpecies.cs index 25bb36053..fa76c6027 100644 --- a/PKHeX.Core/Legality/Evolutions/Forward/EvolutionForwardSpecies.cs +++ b/PKHeX.Core/Legality/Evolutions/Forward/EvolutionForwardSpecies.cs @@ -42,7 +42,7 @@ public sealed class EvolutionForwardSpecies : IEvolutionForward } } - public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { var methods = GetForward(head.Species, head.Form); foreach (var method in methods.Span) diff --git a/PKHeX.Core/Legality/Evolutions/Forward/IEvolutionForward.cs b/PKHeX.Core/Legality/Evolutions/Forward/IEvolutionForward.cs index 16ec1ddeb..03657edb0 100644 --- a/PKHeX.Core/Legality/Evolutions/Forward/IEvolutionForward.cs +++ b/PKHeX.Core/Legality/Evolutions/Forward/IEvolutionForward.cs @@ -32,5 +32,5 @@ public interface IEvolutionForward /// Skip evolution exclusion checks /// Resulting evolution criteria /// True if the evolution is possible and is valid. - bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result); + bool TryEvolve(T head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm; } diff --git a/PKHeX.Core/Legality/Evolutions/Methods/EvoCriteria.cs b/PKHeX.Core/Legality/Evolutions/Methods/EvoCriteria.cs index 902452b0d..5dc113143 100644 --- a/PKHeX.Core/Legality/Evolutions/Methods/EvoCriteria.cs +++ b/PKHeX.Core/Legality/Evolutions/Methods/EvoCriteria.cs @@ -3,7 +3,7 @@ namespace PKHeX.Core; /// /// Evolution Info tracking how an evolution was performed, and the end result species and form. /// -public readonly record struct EvoCriteria : ISpeciesForm +public readonly record struct EvoCriteria : ISpeciesForm, ILevelRange { public required ushort Species { get; init; } public byte Form { get; init; } diff --git a/PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReversePersonal.cs b/PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReversePersonal.cs index c6a0f67e2..c4a796df1 100644 --- a/PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReversePersonal.cs +++ b/PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReversePersonal.cs @@ -48,7 +48,7 @@ public sealed class EvolutionReversePersonal : IEvolutionReverse yield return (s.Species, s.Form); } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { ref readonly var node = ref Lineage[head.Species, head.Form]; return node.TryDevolve(pk, currentMaxLevel, levelMin, skipChecks, out result); diff --git a/PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReverseSpecies.cs b/PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReverseSpecies.cs index f1442e9d7..d63916814 100644 --- a/PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReverseSpecies.cs +++ b/PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReverseSpecies.cs @@ -48,7 +48,7 @@ public sealed class EvolutionReverseSpecies : IEvolutionReverse yield return (s.Species, s.Form); } - public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) + public bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm { ref readonly var node = ref Lineage[head.Species, head.Form]; return node.TryDevolve(pk, currentMaxLevel, levelMin, skipChecks, out result); diff --git a/PKHeX.Core/Legality/Evolutions/Reversal/IEvolutionReverse.cs b/PKHeX.Core/Legality/Evolutions/Reversal/IEvolutionReverse.cs index db5b1d599..be7725250 100644 --- a/PKHeX.Core/Legality/Evolutions/Reversal/IEvolutionReverse.cs +++ b/PKHeX.Core/Legality/Evolutions/Reversal/IEvolutionReverse.cs @@ -30,5 +30,5 @@ public interface IEvolutionReverse /// Skip evolution exclusion checks /// Resulting evolution criteria /// True if the de-evolution is possible and is valid. - bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result); + bool TryDevolve(T head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result) where T : ISpeciesForm; } diff --git a/PKHeX.Core/Legality/LearnSource/Group/LearnGroupHOME.cs b/PKHeX.Core/Legality/LearnSource/Group/LearnGroupHOME.cs index 195431381..ecb1fd9be 100644 --- a/PKHeX.Core/Legality/LearnSource/Group/LearnGroupHOME.cs +++ b/PKHeX.Core/Legality/LearnSource/Group/LearnGroupHOME.cs @@ -204,7 +204,6 @@ public sealed class LearnGroupHOME : ILearnGroup } } - private static bool TryAddExclusiveMoves(Span result, ReadOnlySpan current, PKM pk) { if (pk.Species is (int)Species.Hoopa) diff --git a/PKHeX.Core/Legality/LearnSource/LearnMethod.cs b/PKHeX.Core/Legality/LearnSource/LearnMethod.cs index 7b10a94ea..85eec365b 100644 --- a/PKHeX.Core/Legality/LearnSource/LearnMethod.cs +++ b/PKHeX.Core/Legality/LearnSource/LearnMethod.cs @@ -47,7 +47,7 @@ public static class LearnMethodExtensions public static bool IsValid(this LearnMethod method) => method >= Empty; /// - /// CHecks if the is expecting another move instead. + /// Checks if the is expecting another move instead. /// /// Method to check /// True if the method is valid, false otherwise. diff --git a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1RB.cs b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1RB.cs index 1b6201e2d..c423c52ee 100644 --- a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1RB.cs +++ b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1RB.cs @@ -55,7 +55,7 @@ public sealed class LearnSource1RB : ILearnSource private static bool GetIsTutor(ushort species, byte move) { // No special tutors besides Stadium, which is GB-era only. - if (!ParseSettings.AllowGBCartEra) + if (!ParseSettings.AllowGBStadium2) return false; // Surf Pikachu via Stadium diff --git a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1YW.cs b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1YW.cs index b7f78cc5d..18d53d44c 100644 --- a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1YW.cs +++ b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1YW.cs @@ -55,7 +55,7 @@ public sealed class LearnSource1YW : ILearnSource private static bool GetIsTutor(ushort species, byte move) { // No special tutors besides Stadium, which is GB-era only. - if (!ParseSettings.AllowGBCartEra) + if (!ParseSettings.AllowGBStadium2) return false; // Surf Pikachu via Stadium diff --git a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2GS.cs b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2GS.cs index f27e52d02..e50de885e 100644 --- a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2GS.cs +++ b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2GS.cs @@ -46,6 +46,32 @@ public sealed class LearnSource2GS : ILearnSource, IEggSource return EggMoves[species].Moves; } + // Present and not in Crystal: + // 001 (Bulbasaur) += Charm + // 016 (Pidgey) += SteelWing + // 043 (Oddish) += Charm + // 046 (Paras) += SweetScent + // 083 (Farfetchd) += SteelWing + // 120 (Staryu) += AuroraBeam, Barrier, Supersonic + // 142 (Aerodactyl) += SteelWing + // 143 (Snorlax) += Charm + // 238 (Smoochum) += LovelyKiss + + // Added via Crystal, not in GS: + // 023 (Ekans) += Crunch + // 027 (Sandshrew) += MetalClaw + // 054 (Psyduck) += CrossChop + // 104 (Cubone) += SwordsDance + // 152 (Chikorita) += SwordsDance + // 155 (Cyndaquil) += Submission + // 163 (Hoothoot) += SkyAttack + // 198 (Murkrow) += SkyAttack + // 216 (Teddiursa) += MetalClaw + // 227 (Skarmory) += SkyAttack + // 231 (Phanpy) += WaterGun + // 239 (Elekid) += CrossChop + // 240 (Magby) += CrossChop + public MoveLearnInfo GetCanLearn(PKM pk, PersonalInfo2 pi, EvoCriteria evo, ushort move, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current) { if (move > Legal.MaxMoveID_2) // byte diff --git a/PKHeX.Core/Legality/LegalityAnalysis.cs b/PKHeX.Core/Legality/LegalityAnalysis.cs index 285d9e34e..1558facf5 100644 --- a/PKHeX.Core/Legality/LegalityAnalysis.cs +++ b/PKHeX.Core/Legality/LegalityAnalysis.cs @@ -1,4 +1,4 @@ -#define SUPPRESS +//#define SUPPRESS using System; using System.Collections.Generic; @@ -223,7 +223,6 @@ public sealed class LegalityAnalysis private void ParsePK5() { UpdateChecks(); - NHarmonia.Verify(this); if (Entity.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } diff --git a/PKHeX.Core/Legality/LegalityAnalyzers.cs b/PKHeX.Core/Legality/LegalityAnalyzers.cs index 2769cb441..d52c574ea 100644 --- a/PKHeX.Core/Legality/LegalityAnalyzers.cs +++ b/PKHeX.Core/Legality/LegalityAnalyzers.cs @@ -20,7 +20,6 @@ internal static class LegalityAnalyzers public static readonly HyperTrainingVerifier HyperTraining = new(); public static readonly GenderVerifier GenderValues = new(); public static readonly PIDVerifier PIDEC = new(); - public static readonly NHarmoniaVerifier NHarmonia = new(); public static readonly CXDVerifier CXD = new(); public static readonly MemoryVerifier Memory = new(); public static readonly HistoryVerifier History = new(); diff --git a/PKHeX.Core/Legality/Moves/GameData.cs b/PKHeX.Core/Legality/Moves/GameData.cs index b4081d2c8..6ad01563f 100644 --- a/PKHeX.Core/Legality/Moves/GameData.cs +++ b/PKHeX.Core/Legality/Moves/GameData.cs @@ -3,10 +3,23 @@ using static PKHeX.Core.GameVersion; namespace PKHeX.Core; +/// +/// Provides access to game-specific data for Personal and LearnSource. +/// public static class GameData { + /// + /// Gets the Personal table for the specified game version. + /// + /// The game version to retrieve data for. + /// The Personal table for the specified game version. public static IPersonalTable GetPersonal(GameVersion game) => Personal(game); + /// + /// Gets the LearnSource for the specified game version. + /// + /// The game version to retrieve data for. + /// The LearnSource for the specified game version. public static ILearnSource GetLearnSource(GameVersion game) => game switch { RD or GN or BU or RB => LearnSource1RB.Instance, @@ -56,6 +69,11 @@ public static class GameData _ => throw new ArgumentOutOfRangeException(nameof(game), $"{game} is not a valid entry in the expression."), }; + /// + /// Retrieves the personal table for the specified game version. + /// + /// The game version to retrieve data for. + /// The Personal table of the specified game version. private static IPersonalTable Personal(GameVersion game) => game switch { RD or GN or BU or RB => PersonalTable.RB, diff --git a/PKHeX.Core/Legality/RNG/Frame/Frame.cs b/PKHeX.Core/Legality/RNG/Frame/Frame.cs index 55e5ffdc0..d4d9f6bc1 100644 --- a/PKHeX.Core/Legality/RNG/Frame/Frame.cs +++ b/PKHeX.Core/Legality/RNG/Frame/Frame.cs @@ -5,17 +5,12 @@ namespace PKHeX.Core; /// /// Represents an RNG seed and the conditions of which it occurs. /// +/// Ending seed value for the frame (prior to nature call). +/// +/// [DebuggerDisplay($"{{{nameof(FrameType)},nq}}[{{{nameof(Lead)},nq}}]")] -public sealed class Frame +public record Frame(uint Seed, FrameType FrameType, LeadRequired Lead) { - /// - /// Ending seed value for the frame (prior to nature call). - /// - public readonly uint Seed; - public readonly LeadRequired Lead; - - private readonly FrameType FrameType; - /// /// Starting seed for the frame (to generate the frame). /// @@ -24,35 +19,28 @@ public sealed class Frame /// /// RNG Call Value for the Level Calc /// - public uint RandLevel { get; set; } + public ushort RandLevel { get; set; } /// /// RNG Call Value for the Encounter Slot Calc /// - public uint RandESV { get; set; } + public ushort RandESV { get; set; } public bool LevelSlotModified => Lead.IsLevelOrSlotModified() || (Lead & LeadRequired.UsesLevelCall) != 0; - public Frame(uint seed, FrameType type, LeadRequired lead) - { - Seed = seed; - Lead = lead; - FrameType = type; - } - /// /// Checks the Encounter Slot for RNG calls before the Nature loop. /// /// Slot Data /// Ancillary pk data for determining how to check level. /// Slot number for this frame & lead value. - public bool IsSlotCompatibile(T slot, PKM pk) where T : EncounterSlot, IMagnetStatic, INumberedSlot, ISlotRNGType + public bool IsSlotCompatibile(T slot, PKM pk) where T : IMagnetStatic, INumberedSlot, ISlotRNGType, ILevelRange { // The only level rand type slots are Honey Tree and National Park BCC // Gen3 always does level rand, but the level ranges are same min,max. if (FrameType != FrameType.MethodH) { - bool hasLevelCall = slot.IsRandomLevel; + bool hasLevelCall = slot.IsRandomLevel(); if (Lead.NeedsLevelCall() != hasLevelCall) return false; } @@ -101,8 +89,3 @@ public sealed class Frame /// public int GetSlot(SlotType t) => SlotRange.GetSlot(t, RandESV, FrameType); } - -public interface ISlotRNGType -{ - SlotType Type { get; } -} diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs b/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs index 9073087cf..dc4ecade7 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs @@ -20,6 +20,7 @@ public static class FrameFinder if (pk.Version == (int)GameVersion.CXD) return Array.Empty(); + // Don't trust pk.Nature, just get the correct original via EncryptionConstant var info = new FrameGenerator(pk) {Nature = pk.EncryptionConstant % 25}; var seeds = GetSeeds(pidiv, info, pk); var frames = pidiv.Type == PIDType.CuteCharm @@ -67,9 +68,8 @@ public static class FrameFinder continue; var prev = LCRNG.Prev(f.Seed); // ESV - var rand = prev >> 16; - f.RandESV = rand; - f.RandLevel = f.Seed >> 16; + f.RandESV = (ushort)(prev >> 16); + f.RandLevel = (ushort)(f.Seed >> 16); f.OriginSeed = LCRNG.Prev(prev); if (f.Lead != LeadRequired.CuteCharm) // needs proc checking yield return f; @@ -111,11 +111,11 @@ public static class FrameFinder if (f.Lead == LeadRequired.CuteCharm) // 100% required for frame base { if (cc) - yield return info.GetFrame(prev2, LeadRequired.CuteCharm, p2, p1, prev3); + yield return info.GetFrame(prev2, LeadRequired.CuteCharm, (ushort)p2, (ushort)p1, prev3); yield break; } lead = cc ? LeadRequired.CuteCharm : LeadRequired.CuteCharmFail; - yield return info.GetFrame(prev2, lead, p2, p1, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p2, (ushort)p1, prev3); } if (f.Lead == LeadRequired.CuteCharm) yield break; @@ -127,7 +127,7 @@ public static class FrameFinder // 1 Nature bool max = p0 % 2 == 1; lead = max ? LeadRequired.PressureHustleSpirit : LeadRequired.PressureHustleSpiritFail; - yield return info.GetFrame(prev2, lead, p2, p1, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p2, (ushort)p1, prev3); // Keen Eye, Intimidate (Not compatible with Sweet Scent) // -2 ESV @@ -138,7 +138,7 @@ public static class FrameFinder if (max) // same result as above, no need to recalculate { lead = LeadRequired.IntimidateKeenEye; - yield return info.GetFrame(prev2, lead, p2, p1, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p2, (ushort)p1, prev3); } // Static or Magnet Pull @@ -151,7 +151,7 @@ public static class FrameFinder { // Since a failed proc is indistinguishable from the default frame calls, only generate if it succeeds. lead = LeadRequired.StaticMagnet; - yield return info.GetFrame(prev2, lead, p1, p0, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p1, (ushort)p0, prev3); } } @@ -160,7 +160,7 @@ public static class FrameFinder foreach (var f in frames) { // Current Seed of the frame is the ESV. - var rand = f.Seed >> 16; + var rand = (ushort)(f.Seed >> 16); f.RandESV = rand; f.RandLevel = rand; // unused f.OriginSeed = LCRNG.Prev(f.Seed); @@ -176,7 +176,7 @@ public static class FrameFinder // Create a copy for level; shift ESV and origin back var esv = f.OriginSeed >> 16; var origin = LCRNG.Prev(f.OriginSeed); - var withLevel = info.GetFrame(f.Seed, f.Lead | LeadRequired.UsesLevelCall, esv, f.RandLevel, origin); + var withLevel = info.GetFrame(f.Seed, f.Lead | LeadRequired.UsesLevelCall, (ushort)esv, f.RandLevel, origin); yield return withLevel; if (f.Lead == LeadRequired.None) @@ -212,13 +212,13 @@ public static class FrameFinder if (f.Lead == LeadRequired.CuteCharm) // 100% required for frame base { if (!cc) yield break; - yield return info.GetFrame(prev2, LeadRequired.CuteCharm, p1, p1, prev2); - yield return info.GetFrame(prev2, LeadRequired.CuteCharm | LeadRequired.UsesLevelCall, p2, p1, prev3); + yield return info.GetFrame(prev2, LeadRequired.CuteCharm, (ushort)p1, (ushort)p1, prev2); + yield return info.GetFrame(prev2, LeadRequired.CuteCharm | LeadRequired.UsesLevelCall, (ushort)p2, (ushort)p1, prev3); yield break; } lead = cc ? LeadRequired.CuteCharm : LeadRequired.CuteCharmFail; - yield return info.GetFrame(prev2, lead, p1, p1, prev2); - yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p1, (ushort)p1, prev2); + yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, (ushort)p2, (ushort)p1, prev3); } if (f.Lead == LeadRequired.CuteCharm) yield break; @@ -230,8 +230,8 @@ public static class FrameFinder // 1 Nature bool max = (info.DPPt ? p0 >> 15 : p0 & 1) == 1; lead = max ? LeadRequired.PressureHustleSpirit : LeadRequired.PressureHustleSpiritFail; - yield return info.GetFrame(prev2, lead, p1, p1, prev2); - yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p1, (ushort)p1, prev2); + yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, (ushort)p2, (ushort)p1, prev3); // Keen Eye, Intimidate (Not compatible with Sweet Scent) // -2 ESV @@ -242,8 +242,8 @@ public static class FrameFinder if (max) // same result as above, no need to recalculate { lead = LeadRequired.IntimidateKeenEye; - yield return info.GetFrame(prev2, lead, p1, p1, prev2); - yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p1, (ushort)p1, prev2); + yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, (ushort)p2, (ushort)p1, prev3); } // Static or Magnet Pull @@ -253,11 +253,11 @@ public static class FrameFinder // 1 Nature var force1 = (info.DPPt ? p1 >> 15 : p1 & 1) == 1; lead = force1 ? LeadRequired.StaticMagnet : LeadRequired.StaticMagnetFail; - yield return info.GetFrame(prev2, lead, p0, p0, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p0, (ushort)p0, prev3); var force2 = (info.DPPt ? p2 >> 15 : p2 & 1) == 1; lead = (force2 ? LeadRequired.StaticMagnet : LeadRequired.StaticMagnetFail) | LeadRequired.UsesLevelCall; - yield return info.GetFrame(prev2, lead, p1, p0, prev3); + yield return info.GetFrame(prev2, lead, (ushort)p1, (ushort)p0, prev3); } /// diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs b/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs index d64e3a744..1baa9a65e 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs @@ -18,9 +18,9 @@ public sealed class FrameGenerator public readonly bool Safari3; public Frame GetFrame(uint seed, LeadRequired lead) => new(seed, FrameType, lead); - public Frame GetFrame(uint seed, LeadRequired lead, uint esv, uint origin) => GetFrame(seed, lead, esv, esv, origin); + public Frame GetFrame(uint seed, LeadRequired lead, ushort esv, uint origin) => GetFrame(seed, lead, esv, esv, origin); - public Frame GetFrame(uint seed, LeadRequired lead, uint esv, uint lvl, uint origin) => new(seed, FrameType, lead) + public Frame GetFrame(uint seed, LeadRequired lead, ushort esv, ushort lvl, uint origin) => new(seed, FrameType, lead) { RandESV = esv, RandLevel = lvl, diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameType.cs b/PKHeX.Core/Legality/RNG/Frame/FrameType.cs index af865b755..1a3917b4d 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameType.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameType.cs @@ -1,9 +1,9 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// /// Type of PID-Nature roll algorithm the frame is obtained from. /// -public enum FrameType +public enum FrameType : byte { None, diff --git a/PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs b/PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs index 005ec3b49..79782fcbc 100644 --- a/PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs +++ b/PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace PKHeX.Core; @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// Indicates the requirement of the player's lead Pokémon, first sent out when starting a battle. /// [Flags] -public enum LeadRequired +public enum LeadRequired : byte { /// No Lead ability effect is present, or is not checked for this type of frame. None = 0, diff --git a/PKHeX.Core/Legality/RNG/Frame/SlotRange.cs b/PKHeX.Core/Legality/RNG/Frame/SlotRange.cs index b3d6d4f0c..4926d7bcd 100644 --- a/PKHeX.Core/Legality/RNG/Frame/SlotRange.cs +++ b/PKHeX.Core/Legality/RNG/Frame/SlotRange.cs @@ -72,11 +72,11 @@ public static class SlotRange }; } - public static int GetLevel(EncounterSlot slot, LeadRequired lead, uint lvlrand) + public static int GetLevel(T slot, LeadRequired lead, uint lvlrand) where T : ILevelRange { if ((lead & LeadRequired.PressureHustleSpiritFail) == LeadRequired.PressureHustleSpirit) return slot.LevelMax; - if (slot.IsFixedLevel) + if (slot.IsFixedLevel()) return slot.LevelMin; int delta = slot.LevelMax - slot.LevelMin + 1; var adjust = (int)(lvlrand % delta); diff --git a/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs b/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs index ea80408f9..dff8edf7b 100644 --- a/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs +++ b/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; namespace PKHeX.Core; @@ -14,16 +14,16 @@ public static class LockFinder /// RNG result PID and IV seed state /// Entity to check /// True if all valid. - public static bool IsAllShadowLockValid(EncounterStaticShadow s, PIDIV pv, PKM pk) + public static bool IsAllShadowLockValid(IShadow3 s, PIDIV pv, PKM pk) { if (s.Version == GameVersion.XD && pk.IsShiny) return false; // no xd shiny shadow mons - var teams = s.Locks; + var teams = s.PartyPrior; if (teams.Length == 0) return true; var tsv = s.Version == GameVersion.XD ? (uint)(pk.TID16 ^ pk.SID16) >> 3 : uint.MaxValue; // no xd shiny shadow mons - return IsAllShadowLockValid(pv, teams, tsv); + return IsAllShadowLockValid(pv, teams.Span, tsv); } /// @@ -32,7 +32,7 @@ public static class LockFinder /// RNG result PID and IV seed state /// Possible team data setups the NPC trainer has that need to generate before the shadow. /// Trainer shiny value that is disallowed in XD - public static bool IsAllShadowLockValid(PIDIV pv, IEnumerable teams, uint tsv = uint.MaxValue) + public static bool IsAllShadowLockValid(PIDIV pv, ReadOnlySpan teams, uint tsv = uint.MaxValue) { foreach (var t in teams) { diff --git a/PKHeX.Core/Legality/RNG/Locks/NPCLock.cs b/PKHeX.Core/Legality/RNG/Locks/NPCLock.cs index eb1e4b827..f22d4a300 100644 --- a/PKHeX.Core/Legality/RNG/Locks/NPCLock.cs +++ b/PKHeX.Core/Legality/RNG/Locks/NPCLock.cs @@ -1,7 +1,7 @@ namespace PKHeX.Core; /// -/// Locks associated to a given NPC PKM that appears before a . +/// Locks associated to a given NPC PKM that appears before a . /// public readonly record struct NPCLock { diff --git a/PKHeX.Core/Legality/RNG/MethodFinder.cs b/PKHeX.Core/Legality/RNG/MethodFinder.cs index 9f0dea552..fa6744cb8 100644 --- a/PKHeX.Core/Legality/RNG/MethodFinder.cs +++ b/PKHeX.Core/Legality/RNG/MethodFinder.cs @@ -369,7 +369,7 @@ public static class MethodFinder return GetNonMatch(out pidiv); } - private static bool GetCuteCharmMatch(PKM pk, uint pid, out PIDIV pidiv) + internal static bool GetCuteCharmMatch(PKM pk, uint pid, out PIDIV pidiv) { if (pid > 0xFF) return GetNonMatch(out pidiv); @@ -547,7 +547,7 @@ public static class MethodFinder return false; const byte AzurillGenderRatio = 0xBF; - var gender = EntityGender.GetFromPIDAndRatio(pk.PID, AzurillGenderRatio); + var gender = EntityGender.GetFromPIDAndRatio(pk.EncryptionConstant, AzurillGenderRatio); if (gender != 1) return false; @@ -744,105 +744,15 @@ public static class MethodFinder _ => false, }; - public static bool IsCompatible3(this PIDType val, IEncounterTemplate encounter, PKM pk) => encounter switch + internal static bool IsCuteCharm4Valid(ISpeciesForm enc, PKM pk) { - WC3 g => IsCompatible3Mystery(val, pk, g), - EncounterStatic3 s => IsCompatible3Static(val, pk, s), - EncounterStaticShadow => val is (CXD or CXDAnti), - EncounterSlot3PokeSpot => val is PokeSpot, - EncounterSlot3 w => w.Species != (int)Species.Unown - ? val is (Method_1 or Method_2 or Method_3 or Method_4) - : val is (Method_1_Unown or Method_2_Unown or Method_3_Unown or Method_4_Unown), - _ => val is None, - }; - - private static bool IsCompatible3Static(PIDType val, PKM pk, EncounterStatic3 s) => pk.Version switch - { - (int)GameVersion.CXD => val is (CXD or CXD_ColoStarter or CXDAnti), - (int)GameVersion.E => val is Method_1, // no roamer glitch - (int)GameVersion.FR or (int) GameVersion.LG => s.Roaming ? val.IsRoamerPIDIV(pk) : val is Method_1, // roamer glitch - _ => s.Roaming ? val.IsRoamerPIDIV(pk) : val is (Method_1 or Method_4), // RS, roamer glitch && RSBox s/w emulation => method 4 available - }; - - private static bool IsCompatible3Mystery(PIDType val, PKM pk, WC3 g) => val == g.Method || val switch - { - // forced shiny eggs, when hatched, can lose their detectable correlation. - None => (g.Method is (BACD_R_S or BACD_U_S)) && g.IsEgg && !pk.IsEgg, - CXDAnti => g.Method is CXD && g.Shiny == Shiny.Never, - _ => false, - }; - - private static bool IsRoamerPIDIV(this PIDType val, PKM pk) - { - // Roamer PIDIV is always Method 1. - // M1 is checked before M1R. A M1R PIDIV can also be a M1 PIDIV, so check that collision. - if (Method_1_Roamer == val) - return true; - if (Method_1 != val) + if (pk.Gender is not (0 or 1)) return false; - - // only 8 bits are stored instead of 32 -- 5 bits HP, 3 bits for ATK. - // return pk.IV32 <= 0xFF; - return pk is { IV_DEF: 0, IV_SPE: 0, IV_SPA: 0, IV_SPD: 0, IV_ATK: <= 7 }; - } - - public static bool IsCompatible4(this PIDType val, IEncounterTemplate encounter, PKM pk) => encounter switch - { - // Pokewalker can sometimes be confused with CuteCharm due to the PID creation routine. Double check if it is okay. - EncounterStatic4Pokewalker when val is CuteCharm => GetCuteCharmMatch(pk, pk.EncryptionConstant, out _) && IsCuteCharm4Valid(encounter, pk), - EncounterStatic4Pokewalker => val is Pokewalker, - - EncounterStatic4 {Species: (int)Species.Pichu} => val is Pokewalker, - EncounterStatic4 {Shiny: Shiny.Always} => val is ChainShiny, - EncounterStatic4 when val is CuteCharm => IsCuteCharm4Valid(encounter, pk), - EncounterStatic4 => val is Method_1, - - EncounterSlot4 w => val switch - { - // Chain shiny with Poké Radar is only possible in DPPt, in grass. Safari Zone does not allow using the Poké Radar - ChainShiny => pk is { IsShiny: true, HGSS: false } && (w.GroundTile & GroundTileAllowed.Grass) != 0 && !Locations.IsSafariZoneLocation4(w.Location), - CuteCharm => IsCuteCharm4Valid(encounter, pk), - _ => val is Method_1, - }, - - PGT => IsG4ManaphyPIDValid(val, pk), // Manaphy is the only PGT in the database - PCD d when d.Gift.PK.PID != 1 => true, // Already matches PCD's fixed PID requirement - _ => val is None, - }; - - private static bool IsG4ManaphyPIDValid(PIDType val, PKM pk) - { - if (pk.IsEgg) - { - if (pk.IsShiny) - return false; - if (val == Method_1) - return true; - return val == G4MGAntiShiny && IsAntiShinyARNG(); - } - - if (val == Method_1) - return pk.WasTradedEgg || !pk.IsShiny; // can't be shiny on received game - return val == G4MGAntiShiny && (pk.WasTradedEgg || IsAntiShinyARNG()); - - bool IsAntiShinyARNG() - { - var shinyPID = ARNG.Prev(pk.PID); - var tmp = pk.ID32 ^ shinyPID; - var xor = (ushort)(tmp ^ (tmp >> 16)); - return xor < 8; // shiny proc - } - } - - private static bool IsCuteCharm4Valid(ISpeciesForm encounter, PKM pk) - { - if (pk.Species is (int)Species.Marill or (int)Species.Azumarill) - { - return !IsCuteCharmAzurillMale(pk.PID) // recognized as not Azurill - || encounter.Species == (int)Species.Azurill; // encounter must be male Azurill - } - - return true; + if (pk.Species is not ((int)Species.Marill or (int)Species.Azumarill)) + return true; + if (!IsCuteCharmAzurillMale(pk.PID)) // recognized as not Azurill + return true; + return enc.Species == (int)Species.Azurill; // encounter must be male Azurill } private static bool IsCuteCharmAzurillMale(uint pid) => pid is >= 0xC8 and <= 0xE0; diff --git a/PKHeX.Core/Legality/RNG/PIDGenerator.cs b/PKHeX.Core/Legality/RNG/PIDGenerator.cs index 3f69582d5..a1b47ce25 100644 --- a/PKHeX.Core/Legality/RNG/PIDGenerator.cs +++ b/PKHeX.Core/Legality/RNG/PIDGenerator.cs @@ -327,11 +327,10 @@ public static class PIDGenerator SetRandomIVs(pk); } - private static void SetRandomWildPID4(PKM pk, int nature, int ability, int gender, PIDType specific = PIDType.None) + public static void SetRandomWildPID4(PKM pk, int nature, int ability, int gender, PIDType type) { pk.RefreshAbility(ability); pk.Gender = gender; - var type = GetPIDType(pk, specific); var method = GetGeneratorMethod(type); var rnd = Util.Rand; @@ -352,32 +351,13 @@ public static class PIDGenerator if (pk.Nature != nature) return false; - if ((pk.PID & 1) != ability) + if ((pk.EncryptionConstant & 1) != ability) return false; return true; } - private static PIDType GetPIDType(PKM pk, PIDType specific) - { - if (specific != PIDType.None) - return specific; - if (pk.Version == 15) - return PIDType.CXD; - if (pk is { Species: (int)Species.Unown, Gen3: true }) - { - return Util.Rand.Next(3) switch - { - 1 => PIDType.Method_2_Unown, - 2 => PIDType.Method_4_Unown, - _ => PIDType.Method_1_Unown, - }; - } - - return PIDType.Method_1; - } - - private static void SetRandomWildPID5(PKM pk, int nature, int ability, int gender, PIDType specific = PIDType.None) + public static void SetRandomWildPID5(PKM pk, int nature, int ability, int gender, PIDType specific = PIDType.None) { var tidbit = (pk.TID16 ^ pk.SID16) & 1; pk.RefreshAbility(ability); diff --git a/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs b/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs index 0bfd07e87..ad285c6ae 100644 --- a/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs +++ b/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs @@ -124,7 +124,7 @@ internal static class GBRestrictions /// /// Data to check /// true if can inhabit, false if not. - private static bool CanInhabitGen1(this PKM pk) + internal static bool CanInhabitGen1(this PKM pk) { // Korean Gen2 games can't trade-back because there are no Gen1 Korean games released if (pk.Korean || pk.IsEgg) @@ -134,7 +134,7 @@ internal static class GBRestrictions // If you put a Pokemon in the N64 box, the met info is retained, even if you switch over to a Gen1 game to teach it TMs // You can use rare candies from within the lab, so level-up moves from RBY context can be learned this way as well // Stadium 2 is GB Cart Era only (not 3DS Virtual Console). - if (pk is ICaughtData2 {CaughtData: not 0} && !ParseSettings.AllowGBCartEra) + if (pk is ICaughtData2 {CaughtData: not 0} && !ParseSettings.AllowGBStadium2) return false; // Sanity check species, if it could have existed as a pre-evolution. @@ -234,7 +234,7 @@ internal static class GBRestrictions private static bool IsCatchRateMatchEncounter(IEncounterTemplate enc, PK1 pk1) => enc switch { - EncounterStatic1 s when s.GetMatchRating(pk1) != EncounterMatchRating.PartialMatch => true, + EncounterStatic1 s when s.GetMatchRating(pk1) < EncounterMatchRating.PartialMatch => true, EncounterTrade1 => true, _ => RateMatchesEncounter(enc.Species, enc.Version, pk1.Catch_Rate), }; diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6.cs index 465f304c0..db3948a3d 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6.cs @@ -13,6 +13,26 @@ public sealed partial class MemoryContext6 : MemoryContext public override EntityContext Context => EntityContext.Gen6; + public static bool GetCanBeCaptured(ushort species, GameVersion version) => version switch + { + GameVersion.Any => GetCanBeCaptured(species, CaptureFlagsX) || GetCanBeCaptured(species, CaptureFlagsY) + || GetCanBeCaptured(species, CaptureFlagsAS) || GetCanBeCaptured(species, CaptureFlagsOR), + GameVersion.X => GetCanBeCaptured(species, CaptureFlagsX), + GameVersion.Y => GetCanBeCaptured(species, CaptureFlagsY), + GameVersion.AS => GetCanBeCaptured(species, CaptureFlagsAS), + GameVersion.OR => GetCanBeCaptured(species, CaptureFlagsOR), + _ => false, + }; + + private static bool GetCanBeCaptured(ushort species, ReadOnlySpan flags) + { + int offset = species >> 3; + if (offset >= flags.Length) + return false; + int bitIndex = species & 7; + return (flags[offset] & (1 << bitIndex)) != 0; + } + private static ReadOnlySpan GetPokeCenterLocations(GameVersion game) { return GameVersion.XY.Contains(game) ? LocationsWithPokeCenter_XY : LocationsWithPokeCenter_AO; diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6Data.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6Data.cs index 34f847091..e0b5b6a1c 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6Data.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6Data.cs @@ -170,4 +170,44 @@ public partial class MemoryContext6 { 0001, 0033, 0050, 0051, 0053, }; + + public static ReadOnlySpan CaptureFlagsX => new byte[] + { + 0xB6, 0x75, 0xE1, 0x7B, 0xCB, 0x5A, 0x4A, 0xF5, 0x6C, 0xAD, 0x9C, 0xAA, 0x65, 0x93, 0xFF, 0xFF, + 0x3F, 0xD4, 0x5F, 0x00, 0x3A, 0x8D, 0x8C, 0xCF, 0x4E, 0xEC, 0xEA, 0xEF, 0x9B, 0x58, 0x82, 0x00, + 0xC0, 0x98, 0x54, 0x53, 0x64, 0xDE, 0xCB, 0xFF, 0xF9, 0xF1, 0x7F, 0x0A, 0xB7, 0xCA, 0x9E, 0x00, + 0x00, 0xB0, 0x51, 0x95, 0x8A, 0x06, 0xA6, 0x9E, 0xFB, 0x1C, 0x00, 0x80, 0x00, 0x00, 0x00, 0xC6, + 0x2A, 0xB9, 0x2C, 0xAD, 0xD1, 0xB0, 0x52, 0x9B, 0x76, 0xDC, 0x34, 0x81, 0xED, 0xED, 0xA9, 0x1D, + 0x00, 0x6C, 0x7B, 0x7F, 0xB7, 0x54, 0x73, 0xE5, 0x7B, 0x55, + }; + + public static ReadOnlySpan CaptureFlagsY => new byte[] + { + 0xB6, 0x7D, 0xE1, 0x7B, 0xCB, 0x5A, 0x4A, 0xF5, 0x6C, 0xAD, 0x9C, 0xAE, 0x65, 0x93, 0xFF, 0xFC, + 0x3F, 0xD4, 0x5F, 0x00, 0x3A, 0x8D, 0x8C, 0xCF, 0x4E, 0xEC, 0xEA, 0xEF, 0x8B, 0x58, 0xC2, 0x00, + 0xC0, 0x98, 0x54, 0x53, 0x64, 0xDE, 0xE8, 0xFF, 0xF9, 0xF1, 0x7F, 0x0A, 0xB7, 0x4A, 0x9F, 0x00, + 0x00, 0xB0, 0x51, 0x95, 0x8A, 0x06, 0xA6, 0x9E, 0xFB, 0x1C, 0x00, 0x80, 0x00, 0x00, 0x00, 0xC6, + 0x2A, 0xB9, 0x2C, 0xAD, 0xD1, 0xB0, 0x52, 0x9B, 0x76, 0xDC, 0x34, 0x81, 0xED, 0xED, 0xA9, 0x1D, + 0x00, 0x6C, 0x7B, 0x7F, 0xB7, 0x54, 0x4F, 0xE5, 0x7B, 0x65, + }; + + public static ReadOnlySpan CaptureFlagsAS => new byte[] + { + 0x00, 0x40, 0x1A, 0x0A, 0xA8, 0x5E, 0x66, 0x85, 0x04, 0xAF, 0xD2, 0x81, 0x36, 0xA0, 0xF4, 0x81, + 0x36, 0x56, 0x00, 0x49, 0x00, 0x8D, 0x84, 0xC1, 0x40, 0x2F, 0x40, 0xC4, 0x0F, 0x95, 0x39, 0x92, + 0xA4, 0xD7, 0xD0, 0xB9, 0x64, 0x5B, 0xBB, 0xDF, 0xBD, 0x29, 0xFB, 0xAB, 0xEB, 0x5B, 0x4E, 0x7E, + 0x4D, 0x02, 0x14, 0x05, 0x60, 0x8A, 0x11, 0x0F, 0x08, 0x05, 0x00, 0x00, 0xEF, 0x81, 0x24, 0x04, + 0xA0, 0x38, 0x9C, 0x18, 0x94, 0xF8, 0x58, 0x55, 0x00, 0x02, 0x8D, 0x24, 0x24, 0x20, 0x3C, 0xD2, + 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x05, 0x18, + }; + + public static ReadOnlySpan CaptureFlagsOR => new byte[] + { + 0x00, 0x40, 0x1A, 0x0A, 0xA8, 0x5E, 0x66, 0x85, 0x04, 0xAF, 0xD2, 0x81, 0x36, 0xA0, 0xF4, 0x81, + 0x36, 0x56, 0x00, 0x49, 0x00, 0x8D, 0x84, 0xC1, 0x40, 0x2F, 0x40, 0xC4, 0x0F, 0x95, 0x39, 0x94, + 0xA4, 0x17, 0xD6, 0xB9, 0x64, 0x9B, 0xBB, 0xDF, 0xBD, 0xA9, 0xFC, 0xAB, 0xEB, 0x5B, 0x4E, 0xBE, + 0x4D, 0x02, 0x14, 0x05, 0x60, 0x8A, 0x11, 0x0F, 0x08, 0x05, 0x00, 0x00, 0xF7, 0x81, 0x24, 0x04, + 0xA0, 0x38, 0x9C, 0x14, 0x94, 0xF8, 0x58, 0x55, 0x00, 0x02, 0x8D, 0x24, 0x24, 0x20, 0x3C, 0xD2, + 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x18, + }; } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs index 997cb9a3e..b6d807ae0 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs @@ -12,6 +12,23 @@ public sealed partial class MemoryContext8 : MemoryContext public override EntityContext Context => EntityContext.Gen8; + public static bool GetCanBeCaptured(ushort species, GameVersion version) => version switch + { + GameVersion.Any => GetCanBeCaptured(species, CaptureFlagsSW) || GetCanBeCaptured(species, CaptureFlagsSH), + GameVersion.SW => GetCanBeCaptured(species, CaptureFlagsSW), + GameVersion.SH => GetCanBeCaptured(species, CaptureFlagsSH), + _ => false, + }; + + private static bool GetCanBeCaptured(ushort species, ReadOnlySpan flags) + { + int offset = species >> 3; + if (offset >= flags.Length) + return false; + int bitIndex = species & 7; + return (flags[offset] & (1 << bitIndex)) != 0; + } + public override IEnumerable GetMemoryItemParams() { var hashSet = new HashSet(Legal.HeldItems_SWSH); @@ -110,7 +127,7 @@ public sealed partial class MemoryContext8 : MemoryContext private static bool IsWildEncounter(PKM pk, IEncounterTemplate enc) { - if (enc is not (EncounterSlot8 or EncounterStatic { Gift: false } or EncounterStatic8N or EncounterStatic8ND or EncounterStatic8NC or EncounterStatic8U)) + if (enc is not (EncounterSlot8 or EncounterStatic8 { Gift: false } or EncounterStatic8N or EncounterStatic8ND or EncounterStatic8NC or EncounterStatic8U)) return false; if (pk is IRibbonSetMark8 { RibbonMarkCurry: true }) return false; diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8Data.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8Data.cs index 232762818..8f2316489 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8Data.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8Data.cs @@ -318,4 +318,28 @@ public partial class MemoryContext8 0x90CC7E, 0x2FBF7F, 0x2FBF7F, 0xB797FF, 0x3FB7FF, 0xBFFFFF, 0xCC8BFF, 0xF69F7F, 0x37FDFF, 0x2B277F, 0x8FFBFA, 0x8CDFFA, 0xFCE9EF, 0x8F6F7B, 0x826AB0, 0x866AB0, 0x8C69FE, 0x776AB0, 0x8CFB7A, 0x0CFEBA, }; + + public static ReadOnlySpan CaptureFlagsSW => new byte[] + { + 0xFE, 0x1F, 0x00, 0xFE, 0xFF, 0x3F, 0xFC, 0xFC, 0x1F, 0xE3, 0x0F, 0xFC, 0xCC, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7F, 0x00, 0x18, 0xFE, 0xC7, 0x07, 0xBC, 0x44, 0xF9, 0xF0, 0x4F, 0xF0, 0xFF, 0x67, + 0x9B, 0xC1, 0xCF, 0x07, 0xFC, 0xC4, 0x67, 0xC8, 0x13, 0x67, 0xFE, 0x7F, 0x98, 0x3F, 0xFA, 0xFF, + 0x01, 0x00, 0xF8, 0x80, 0xF1, 0x1E, 0xFC, 0xFD, 0x7F, 0xFC, 0xDF, 0xEA, 0xFF, 0x01, 0x00, 0x7C, + 0xE0, 0xF3, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xF9, 0xFB, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xF8, 0x00, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x24, 0x01, 0xC7, 0xFF, 0xFF, 0xFF, + 0x1F, 0xF7, 0xBF, 0xFF, 0x69, 0x24, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCD, + 0x07, + }; + + public static ReadOnlySpan CaptureFlagsSH => new byte[] + { + 0xFE, 0x1F, 0x00, 0xFE, 0xFF, 0x3F, 0xFC, 0xFC, 0x1F, 0xE3, 0x0F, 0xFC, 0xCC, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7F, 0x00, 0x18, 0xFE, 0xC7, 0x07, 0xBC, 0x44, 0xF9, 0xF0, 0x4F, 0xF0, 0xFF, 0x67, + 0x9B, 0xC1, 0xCF, 0x07, 0xFC, 0xC4, 0x67, 0xC8, 0x13, 0x67, 0xFE, 0x7F, 0x98, 0x3F, 0xFA, 0xFF, + 0x01, 0x00, 0xF8, 0x80, 0xF1, 0x1E, 0xFC, 0xFD, 0x7F, 0xFC, 0xDF, 0xEA, 0xFF, 0x01, 0x00, 0x7C, + 0xE0, 0xF3, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xF9, 0xFB, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xF8, 0x00, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x24, 0x01, 0xC7, 0xFF, 0xFF, 0xFF, + 0x1F, 0xF7, 0xBF, 0xFF, 0x69, 0x24, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, + 0x07, + }; } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs index 78b0858e1..876fd1474 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using static PKHeX.Core.Encounters6XY; -using static PKHeX.Core.Encounters6AO; -using static PKHeX.Core.Encounters8; using static PKHeX.Core.Move; using static PKHeX.Core.Species; @@ -170,43 +167,11 @@ public static class MemoryPermissions public static bool GetCanBeCaptured(ushort species, EntityContext gen, GameVersion version) => gen switch { - EntityContext.Gen6 => version switch - { - GameVersion.Any => GetCanBeCaptured(species, SlotsX, StaticX) || GetCanBeCaptured(species, SlotsY, StaticY) - || GetCanBeCaptured(species, SlotsA, StaticA) || GetCanBeCaptured(species, SlotsO, StaticO), - - GameVersion.X => GetCanBeCaptured(species, SlotsX, StaticX), - GameVersion.Y => GetCanBeCaptured(species, SlotsY, StaticY), - - GameVersion.AS => GetCanBeCaptured(species, SlotsA, StaticA), - GameVersion.OR => GetCanBeCaptured(species, SlotsO, StaticO), - _ => false, - }, - EntityContext.Gen8 => version switch - { - GameVersion.Any => GetCanBeCaptured(species, SlotsSW, StaticSW) || GetCanBeCaptured(species, SlotsSH, StaticSH), - GameVersion.SW => GetCanBeCaptured(species, SlotsSW, StaticSW), - GameVersion.SH => GetCanBeCaptured(species, SlotsSH, StaticSH), - _ => false, - }, + EntityContext.Gen6 => MemoryContext6.GetCanBeCaptured(species, version), + EntityContext.Gen8 => MemoryContext8.GetCanBeCaptured(species, version), _ => false, }; - private static bool GetCanBeCaptured(ushort species, TArea[] areas, TStatic[] statics) where TArea : IMemorySpeciesArea where TStatic : IEncounterTemplate - { - foreach (var area in areas) - { - if (area.HasSpecies(species)) - return true; - } - foreach (var s in statics) - { - if (s.Species == species) - return true; - } - return false; - } - public static bool GetCanDynamaxTrainer(ushort species, int gen, GameVersion version) { if (gen != 8) diff --git a/PKHeX.Core/Legality/Enums/CheckIdentifier.cs b/PKHeX.Core/Legality/Structures/CheckIdentifier.cs similarity index 100% rename from PKHeX.Core/Legality/Enums/CheckIdentifier.cs rename to PKHeX.Core/Legality/Structures/CheckIdentifier.cs diff --git a/PKHeX.Core/Legality/Structures/LegalInfo.cs b/PKHeX.Core/Legality/Structures/LegalInfo.cs index a1fd884e3..c299c6c96 100644 --- a/PKHeX.Core/Legality/Structures/LegalInfo.cs +++ b/PKHeX.Core/Legality/Structures/LegalInfo.cs @@ -67,7 +67,7 @@ public sealed class LegalInfo : IGeneration public bool PIDIVMatches { get; internal set; } = true; /// Indicates whether or not the can originate from the with explicit RNG matching. - /// This boolean is true until all valid entries are tested for all possible matches, after which it is false. + /// This boolean is true until all valid entries are tested for all possible matches, after which it is false. public bool FrameMatches { get; internal set; } = true; public LegalInfo(PKM pk, List parse) diff --git a/PKHeX.Core/Legality/Enums/Severity.cs b/PKHeX.Core/Legality/Structures/Severity.cs similarity index 100% rename from PKHeX.Core/Legality/Enums/Severity.cs rename to PKHeX.Core/Legality/Structures/Severity.cs diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs index b7670f461..a2ae5b3fd 100644 --- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs @@ -413,7 +413,7 @@ public sealed class AbilityVerifier : Verifier { // Must not have the Ability bit flag set. // Shadow encounters set a random ability index; don't bother checking if it's a re-battle for ability bit flipping. - if (abit && enc is not EncounterStaticShadow) + if (abit && enc is not IShadow3) return GetInvalid(LAbilityMismatchFlag, CheckIdentifier.PID); } else @@ -421,7 +421,7 @@ public sealed class AbilityVerifier : Verifier // Gen3 mainline origin sets the Ability index based on the PID, but only if it has two abilities. // Version value check isn't factually correct, but there are no C/XD gifts with (Version!=15) that have two abilities. // Pikachu, Celebi, Ho-Oh - if (pk.Version != (int)GameVersion.CXD && abit != ((pk.PID & 1) == 1)) + if (pk.Version != (int)GameVersion.CXD && abit != ((pk.EncryptionConstant & 1) == 1)) return GetInvalid(LAbilityMismatchPID, CheckIdentifier.PID); } } diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs index 7d5a0c638..fc39366e1 100644 --- a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs @@ -37,21 +37,6 @@ public sealed class BallVerifier : Verifier if (ball != 0) return VerifyBallEquals(data, ball); - // Fixed ball cases -- can be only one ball ever - switch (enc) - { - case MysteryGift g: - return VerifyBallMysteryGift(data, g); - case EncounterTrade t: - return VerifyBallEquals(data, t.Ball); - case EncounterStatic {Gift: true} s: - return VerifyBallEquals(data, s.Ball); - case EncounterSlot8GO: // Already a strict match - return GetResult(true); - case EncounterSlot8b {IsMarsh: true}: - return VerifyBallEquals(data, (int)Safari); - } - // Capture / Inherit cases -- can be one of many balls var pk = data.Entity; if (pk.Species == (int)Species.Shedinja && enc.Species != (int)Species.Shedinja) // Shedinja. For gen3, copy the ball from Nincada @@ -65,43 +50,28 @@ public sealed class BallVerifier : Verifier return VerifyBallEquals(data, (int)Poke); // Poké ball Only } + // Fixed ball cases -- can be only one ball ever + switch (enc) + { + case IFixedBall { FixedBall: not None } s: + return VerifyBallEquals(data, (byte)s.FixedBall); + case EncounterSlot8GO: // Already a strict match + return GetResult(true); + } + // Capturing with Heavy Ball is impossible in Sun/Moon for specific species. if (pk is { Ball: (int)Heavy, SM: true } && enc is not EncounterEgg && BallBreedLegality.AlolanCaptureNoHeavyBall.Contains(enc.Species)) return GetInvalid(LBallHeavy); // Heavy Ball, can inherit if from egg (US/UM fixed catch rate calc) return enc switch { - EncounterStatic e => VerifyBallStatic(data, e), - EncounterSlot w => VerifyBallWild(data, w), + EncounterStatic5Entree => VerifyBallEquals(data, BallUseLegality.DreamWorldBalls), EncounterEgg => VerifyBallEgg(data), EncounterInvalid => VerifyBallEquals(data, pk.Ball), // ignore ball, pass whatever - _ => VerifyBallEquals(data, (int)Poke), + _ => VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, enc.Version)), }; } - private CheckResult VerifyBallMysteryGift(LegalityAnalysis data, MysteryGift gift) - { - if (gift is { Generation: 4, Species: (int)Species.Manaphy, Ball: 0 }) // there is no ball data in Manaphy PGT Mystery Gift from Gen4 - return VerifyBallEquals(data, (int)Poke); // Pokeball - return VerifyBallEquals(data, gift.Ball); - } - - private CheckResult VerifyBallStatic(LegalityAnalysis data, EncounterStatic s) - { - if (s is EncounterStatic5 { EntreeForestDreamWorld: true }) - return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls); - return VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, s.Version)); - } - - private CheckResult VerifyBallWild(LegalityAnalysis data, EncounterSlot w) - { - var req = w.FixedBall; - if (req != None) - return VerifyBallEquals(data, (int) req); - - return VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, w.Version)); - } - private CheckResult VerifyBallEgg(LegalityAnalysis data) { var pk = data.Entity; diff --git a/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs b/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs index bb29badfd..78a03983b 100644 --- a/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs @@ -12,44 +12,44 @@ public sealed class CXDVerifier : Verifier public override void Verify(LegalityAnalysis data) { var pk = data.Entity; - if (data.EncounterMatch is EncounterStatic3 s3) - VerifyCXDStarterCorrelation(data, s3); + if (data.EncounterMatch is EncounterStatic3Colo { IsColoStarter: true }) + VerifyStarterColo(data); + else if (data.EncounterMatch is EncounterStatic3XD { Species: (ushort)Species.Eevee }) + VerifyStarterXD(data); + if (pk.OT_Gender == 1) data.AddLine(GetInvalid(LG3OTGender, CheckIdentifier.Trainer)); } - private static void VerifyCXDStarterCorrelation(LegalityAnalysis data, EncounterStatic3 enc) + private static void VerifyStarterColo(LegalityAnalysis data) + { + var type = data.Info.PIDIV.Type; + if (type is not (PIDType.CXD or PIDType.CXDAnti or PIDType.CXD_ColoStarter)) + return; // already flagged as invalid + if (type != PIDType.CXD_ColoStarter) + data.AddLine(GetInvalid(LEncConditionBadRNGFrame, CheckIdentifier.PID)); + } + + private static void VerifyStarterXD(LegalityAnalysis data) { var (type, seed) = data.Info.PIDIV; if (type is not (PIDType.CXD or PIDType.CXDAnti or PIDType.CXD_ColoStarter)) return; // already flagged as invalid bool valid; - if (enc.Species is (int)Species.Espeon or (int)Species.Umbreon) + var pk = data.Entity; + if (type == PIDType.CXD_ColoStarter && pk.Species == (int)Species.Umbreon) { - valid = type == PIDType.CXD_ColoStarter; - } - else if (enc.Species == (int)Species.Eevee) - { - var pk = data.Entity; - if (type == PIDType.CXD_ColoStarter && pk.Species == (int)Species.Umbreon) - { - // reset pidiv type to be CXD -- ColoStarter is same correlation as Eevee->Umbreon - data.Info.PIDIV = new PIDIV(PIDType.CXD, seed); - valid = true; - } - else - { - valid = LockFinder.IsXDStarterValid(seed, pk.TID16, pk.SID16); - if (valid) // unroll seed to origin that generated TID16/SID16->pkm - data.Info.PIDIV = new PIDIV(PIDType.CXD, XDRNG.Prev4(seed)); - } + // reset pidiv type to be CXD -- ColoStarter is same correlation as Eevee->Umbreon + data.Info.PIDIV = new PIDIV(PIDType.CXD, seed); + valid = true; } else { - return; + valid = LockFinder.IsXDStarterValid(seed, pk.TID16, pk.SID16); + if (valid) // unroll seed to origin that generated TID16/SID16->pkm + data.Info.PIDIV = new PIDIV(PIDType.CXD, XDRNG.Prev4(seed)); } - if (!valid) data.AddLine(GetInvalid(LEncConditionBadRNGFrame, CheckIdentifier.PID)); } diff --git a/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs index 27ef93511..48e5bd876 100644 --- a/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs @@ -10,6 +10,9 @@ public sealed class EffortValueVerifier : Verifier { protected override CheckIdentifier Identifier => CheckIdentifier.EVs; + private const int totalMax = 510; // Total Max + private const int vitaMax = 100; // Vitamin Max for consideration in Gen3 & Gen4. + public override void Verify(LegalityAnalysis data) { var pk = data.Entity; @@ -27,30 +30,15 @@ public sealed class EffortValueVerifier : Verifier return; int sum = pk.EVTotal; - if (sum > 510) // format >= 3 + if (sum > totalMax) // format >= 3 data.AddLine(GetInvalid(LEffortAbove510)); + Span evs = stackalloc int[6]; pk.GetEVs(evs); if (format >= 6 && evs.IndexOfAny(253, 254, 255) != -1) data.AddLine(GetInvalid(LEffortAbove252)); - - const int vitaMax = 100; // Vitamin Max - if (format < 5) // 3/4 - { - if (enc.LevelMin == 100) // only true for Gen4 and Format=4 - { - // Cannot EV train at level 100 -- Certain events are distributed at level 100. - if (evs.Find(static ev => ev > vitaMax) != default) // 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[enc.Species].EXPGrowth; - var baseEXP = Experience.GetEXP(enc.LevelMin, growth); - if (baseEXP == pk.EXP && evs.Find(static ev => ev > vitaMax) != default) - data.AddLine(GetInvalid(string.Format(LEffortUntrainedCap, vitaMax))); - } - } + else if (format < 5) // 3/4 + VerifyGainedEVs34(data, enc, evs, pk); // Only one of the following can be true: 0, 508, and x%6!=0 if (sum == 0 && !enc.IsWithinEncounterRange(pk)) @@ -60,4 +48,21 @@ public sealed class EffortValueVerifier : Verifier else if (evs[0] != 0 && evs.IndexOfAnyExcept(evs[0]) == -1) data.AddLine(Get(LEffortAllEqual, Severity.Fishy)); } + + private void VerifyGainedEVs34(LegalityAnalysis data, IEncounterTemplate enc, Span evs, PKM pk) + { + if (enc.LevelMin == 100) // only true for Gen4 and Format=4 + { + // Cannot EV train at level 100 -- Certain events are distributed at level 100. + if (evs.Find(static ev => ev > vitaMax) != default) // 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[enc.Species].EXPGrowth; + var baseEXP = Experience.GetEXP(enc.LevelMin, growth); + if (baseEXP == pk.EXP && evs.Find(static ev => ev > vitaMax) != default) + data.AddLine(GetInvalid(string.Format(LEffortUntrainedCap, vitaMax))); + } + } } diff --git a/PKHeX.Core/Legality/Verifiers/Egg/EggStateLegality.cs b/PKHeX.Core/Legality/Verifiers/Egg/EggStateLegality.cs index d336a3399..4ab14b4c3 100644 --- a/PKHeX.Core/Legality/Verifiers/Egg/EggStateLegality.cs +++ b/PKHeX.Core/Legality/Verifiers/Egg/EggStateLegality.cs @@ -54,7 +54,7 @@ public static class EggStateLegality /// Maximum value the Hatch Counter can be. public static int GetMaximumEggHatchCycles(PKM pk, IEncounterTemplate enc) { - if (enc is EncounterStatic { EggCycles: not 0 } s) + if (enc is IHatchCycle { EggCycles: not 0 } s) return s.EggCycles; return pk.PersonalInfo.HatchCycles; } @@ -65,6 +65,9 @@ public static class EggStateLegality /// Generation the egg is given in public static byte GetEggLevel(int generation) => generation >= 4 ? (byte)1 : (byte)5; + public const byte EggMetLevel34 = 0; + public const byte EggMetLevel = 1; + /// /// Met Level which eggs are given to the player. May change if transferred to future games. /// @@ -72,9 +75,9 @@ public static class EggStateLegality /// Generation the egg is given in public static int GetEggLevelMet(GameVersion version, int generation) => generation switch { - 2 => version is C ? 1 : 0, // GS do not store met data - 3 or 4 => 0, - _ => 1, + 2 => version is C ? EggMetLevel : 0, // GS do not store met data + 3 or 4 => EggMetLevel34, + _ => EggMetLevel, }; /// diff --git a/PKHeX.Core/Legality/Verifiers/GenderVerifier.cs b/PKHeX.Core/Legality/Verifiers/GenderVerifier.cs index a0d001131..c69c0d252 100644 --- a/PKHeX.Core/Legality/Verifiers/GenderVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/GenderVerifier.cs @@ -43,12 +43,14 @@ public sealed class GenderVerifier : Verifier private static void VerifyNaturePID(LegalityAnalysis data) { var pk = data.Entity; - var result = pk.EncryptionConstant % 25 == pk.Nature + var result = GetExpectedNature(pk) == pk.Nature ? GetValid(LPIDNatureMatch, CheckIdentifier.Nature) : GetInvalid(LPIDNatureMismatch, CheckIdentifier.Nature); data.AddLine(result); } + private static uint GetExpectedNature(PKM pk) => pk.EncryptionConstant % 25; + private static bool IsValidGenderPID(LegalityAnalysis data) { var pk = data.Entity; diff --git a/PKHeX.Core/Legality/Verifiers/GroundTileVerifier.cs b/PKHeX.Core/Legality/Verifiers/GroundTileVerifier.cs index ce1373c4f..4221efdff 100644 --- a/PKHeX.Core/Legality/Verifiers/GroundTileVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/GroundTileVerifier.cs @@ -1,4 +1,4 @@ -using static PKHeX.Core.LegalityCheckStrings; +using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core; @@ -13,8 +13,21 @@ public sealed class GroundTileVerifier : Verifier { if (data.Entity is not IGroundTile e) return; - var type = data.EncounterMatch is IGroundTypeTile t ? t.GroundTile : GroundTileAllowed.None; - var result = !type.Contains(e.GroundTile) ? GetInvalid(LEncTypeMismatch) : GetValid(LEncTypeMatch); + var enc = data.EncounterMatch; + bool valid = IsGroundTileValid(enc, e); + var result = !valid ? GetInvalid(LEncTypeMismatch) : GetValid(LEncTypeMatch); data.AddLine(result); } + + /// + /// Indicates if the is valid for the . + /// + /// Encounter Template + /// Entity with a stored value. + /// True if stored ground tile value is permitted. + public static bool IsGroundTileValid(IEncounterTemplate enc, IGroundTile e) + { + var type = enc is IGroundTypeTile t ? t.GroundTile : GroundTileAllowed.None; + return type.Contains(e.GroundTile); + } } diff --git a/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs b/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs index 094f3413d..4e657124b 100644 --- a/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs @@ -216,7 +216,7 @@ public sealed class HistoryVerifier : Verifier return enc switch { - EncounterTrade => false, + IFixedTrainer { IsFixedTrainer: true } => false, EncounterSlot8GO => false, WC6 { OT_Name.Length: > 0 } => false, WC7 { OT_Name.Length: > 0, TID16: not 18075 } => false, // Ash Pikachu QR Gift doesn't set Current Handler diff --git a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs index b47550459..2ef84fdf2 100644 --- a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs @@ -14,23 +14,24 @@ public sealed class IndividualValueVerifier : Verifier { switch (data.EncounterMatch) { - case EncounterStatic s: - VerifyIVsStatic(data, s); + case EncounterSlot7GO: + case EncounterSlot8GO: + VerifyIVsGoTransfer(data); break; - case EncounterSlot w: - VerifyIVsSlot(data, w); + case IFlawlessIVCount s: + VerifyIVsFlawless(data, s); + break; + case EncounterSlot7: + VerifyIVsGen7(data); break; case MysteryGift g: VerifyIVsMystery(data, g); break; } - var pk = data.Entity; - { - var hpiv = pk.IV_HP; - if (hpiv < 30 && AllIVsEqual(pk, hpiv)) - data.AddLine(Get(string.Format(LIVAllEqual_0, hpiv), Severity.Fishy)); - } + var hpiv = pk.IV_HP; + if (hpiv < 30 && AllIVsEqual(pk, hpiv)) + data.AddLine(Get(string.Format(LIVAllEqual_0, hpiv), Severity.Fishy)); } private static bool AllIVsEqual(PKM pk, int hpiv) @@ -59,24 +60,10 @@ public sealed class IndividualValueVerifier : Verifier } } - private void VerifyIVsSlot(LegalityAnalysis data, EncounterSlot w) - { - switch (w.Generation) - { - case 6: VerifyIVsGen6(data, w); break; - case 7: VerifyIVsGen7(data); break; - case 8: VerifyIVsGen8(data); break; - } - } - private void VerifyIVsGen7(LegalityAnalysis data) { var pk = data.Entity; - if (pk.GO) - { - VerifyIVsGoTransfer(data); - } - else if (pk.AbilityNumber == 4) + if (pk.AbilityNumber == 4) { var abilities = (IPersonalAbility12H)pk.PersonalInfo; if (!AbilityVerifier.CanAbilityPatch(pk.Format, abilities, pk.Species)) @@ -84,41 +71,21 @@ public sealed class IndividualValueVerifier : Verifier } } - private void VerifyIVsGen8(LegalityAnalysis data) + private void VerifyIVsFlawless(LegalityAnalysis data, IFlawlessIVCount s) { - var pk = data.Entity; - if (pk.GO) - VerifyIVsGoTransfer(data); - else if (data.EncounterMatch is EncounterSlot8a s) + if (s.FlawlessIVCount != 0) VerifyIVsFlawless(data, s.FlawlessIVCount); } - private void VerifyIVsGen6(LegalityAnalysis data, EncounterSlot w) - { - if (w is EncounterSlot6XY xy) - { - if (PersonalTable.XY[xy.Species].IsEggGroup(15)) // Undiscovered - VerifyIVsFlawless(data, 3); - if (xy.IsFriendSafari) - VerifyIVsFlawless(data, 2); - } - } - private void VerifyIVsFlawless(LegalityAnalysis data, int count) { if (data.Entity.FlawlessIVCount < count) data.AddLine(GetInvalid(string.Format(LIVF_COUNT0_31, count))); } - private void VerifyIVsStatic(LegalityAnalysis data, EncounterStatic s) - { - if (s.FlawlessIVCount != 0) - VerifyIVsFlawless(data, s.FlawlessIVCount); - } - private void VerifyIVsGoTransfer(LegalityAnalysis data) { - if (data.EncounterMatch is EncounterSlotGO g && !g.GetIVsValid(data.Entity)) + if (data.EncounterMatch is IPogoSlot g && !g.GetIVsValid(data.Entity)) data.AddLine(GetInvalid(LIVNotCorrect)); } } diff --git a/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs b/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs index 28ed1e79a..125939d66 100644 --- a/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs @@ -54,7 +54,7 @@ public sealed class LanguageVerifier : Verifier if (currentLanguage > maxLanguageID) return false; // Language not available (yet) - if (currentLanguage <= (int)LanguageID.Hacked && !(enc is EncounterTrade5PID && EncounterTrade5PID.IsValidMissingLanguage(pk))) + if (currentLanguage <= (int)LanguageID.Hacked && !(enc is EncounterTrade5BW && EncounterTrade5BW.IsValidMissingLanguage(pk))) return false; // Missing Language value is not obtainable return true; // Language is possible diff --git a/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs b/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs index 58166d5b6..ae83966db 100644 --- a/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs @@ -167,14 +167,13 @@ public sealed class LegendsArceusVerifier : Verifier return ctr; } - private static int GetMoveCount(PKM pa) + private static int GetMoveCount(PA8 pa) { var count = 0; - for (int i = 0; i < 4; i++) - { - if (pa.GetMove(i) is not 0) - count++; - } + if (pa.Move1 != 0) count++; + if (pa.Move2 != 0) count++; + if (pa.Move3 != 0) count++; + if (pa.Move4 != 0) count++; return count; } diff --git a/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs b/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs index 2ac7e6d52..899ae6a02 100644 --- a/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs @@ -36,7 +36,7 @@ public sealed class LevelVerifier : Verifier return; } - var reqEXP = enc is EncounterStatic2Odd + var reqEXP = enc is EncounterStatic2 { DizzyPunchEgg: true } ? 125 // Gen2 Dizzy Punch gifts always have 125 EXP, even if it's more than the Lv5 exp required. : Experience.GetEXP(elvl, pk.PersonalInfo.EXPGrowth); if (reqEXP != pk.EXP) @@ -124,7 +124,7 @@ public sealed class LevelVerifier : Verifier // Context check is only applicable to gen1/2; transferring to Gen2 is a trade. // Stadium 2 can transfer across game/generation boundaries without initiating a trade. // Ignore this check if the environment's loaded trainer is not from Gen1/2 or is from GB Era. - if (ParseSettings.ActiveTrainer.Generation >= 3 || ParseSettings.AllowGBCartEra) + if (ParseSettings.ActiveTrainer.Generation >= 3 || ParseSettings.AllowGBStadium2) return false; var moves = data.Info.Moves; diff --git a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs index 9ddf3ecaf..b2741502a 100644 --- a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs @@ -49,6 +49,9 @@ public sealed class MiscVerifier : Verifier switch (pk) { + case PK5 pk5: + VerifyGen5Stats(data, pk5); + break; case PK7 {ResortEventStatus: >= ResortEventState.MAX}: data.AddLine(GetInvalid(LTransferBad)); break; @@ -137,6 +140,21 @@ public sealed class MiscVerifier : Verifier data.AddLine(GetInvalid(LStatIncorrectHeightValue)); } + private static void VerifyGen5Stats(LegalityAnalysis data, PK5 pk5) + { + var enc = data.EncounterMatch; + if (enc is EncounterStatic5N) + { + if (!pk5.NSparkle) + data.AddLine(GetInvalid(LG5SparkleRequired, Fateful)); + } + else + { + if (pk5.NSparkle) + data.AddLine(GetInvalid(LG5SparkleInvalid, Fateful)); + } + } + private static bool IsHeightScaleMatchRequired(PKM pk) { if (pk is IHomeTrack { HasTracker: false }) @@ -159,7 +177,7 @@ public sealed class MiscVerifier : Verifier var enc = data.EncounterOriginal; if (pk9 is { HeightScalar: 0, WeightScalar: 0 }) { - if (enc.Context.Generation() < 9 && enc is not EncounterSlotGO && !data.Info.EvoChainsAllGens.HasVisitedPLA) // <=Gen8 rerolls height/weight, never zero. + if (enc.Context.Generation() < 9 && !data.Info.EvoChainsAllGens.HasVisitedPLA && enc is not IPogoSlot) // <=Gen8 rerolls height/weight, never zero. data.AddLine(Get(LStatInvalidHeightWeight, Severity.Invalid, Encounter)); else if (CheckHeightWeightOdds(enc) && ParseSettings.ZeroHeightWeight != Severity.Valid) data.AddLine(Get(LStatInvalidHeightWeight, ParseSettings.ZeroHeightWeight, Encounter)); @@ -285,7 +303,7 @@ public sealed class MiscVerifier : Verifier { var pi = PersonalTable.RB[species]; var (match1, match2) = pi.IsMatchType(pk1); - if (!match2 && ParseSettings.AllowGBCartEra) + if (!match2 && ParseSettings.AllowGBStadium2) match2 = (species is (int)Species.Magnemite or (int)Species.Magneton) && pk1.Type2 == 9; // Steel Magnemite via Stadium2 var first = match1 ? GetValid(LG1TypeMatch1) : GetInvalid(LG1Type1Fail); @@ -319,7 +337,7 @@ public sealed class MiscVerifier : Verifier if (MoveInfo.IsAnyFromGeneration(2, data.Info.Moves)) return GetInvalid(LG1CatchRateItem); var e = data.EncounterMatch; - if (e is EncounterStatic1E {Version: GameVersion.Stadium} or EncounterTrade1) + if (e is EncounterGift1 {Version: GameVersion.Stadium} or EncounterTrade1) return GetValid(LG1CatchRateMatchPrevious); // Encounters detected by the catch rate, cant be invalid if match this encounters ushort species = pk1.Species; @@ -610,10 +628,10 @@ public sealed class MiscVerifier : Verifier data.AddLine(GetInvalid(LStatIncorrectWeight, Encounter)); } - private static bool IsStarterLGPE(ISpeciesForm pk) => pk.Species switch + private static bool IsStarterLGPE(ISpeciesForm pk) => pk switch { - (int)Species.Pikachu when pk.Form == 8 => true, - (int)Species.Eevee when pk.Form == 1 => true, + { Species: (int)Species.Pikachu, Form: 8 } => true, + { Species: (int)Species.Eevee, Form: 1 } => true, _ => false, }; diff --git a/PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs b/PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs deleted file mode 100644 index f3b8c38d6..000000000 --- a/PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs +++ /dev/null @@ -1,54 +0,0 @@ -using static PKHeX.Core.LegalityCheckStrings; - -namespace PKHeX.Core; - -/// -/// Verifies the data. -/// -public sealed class NHarmoniaVerifier : Verifier -{ - protected override CheckIdentifier Identifier => CheckIdentifier.Trainer; - - public override void Verify(LegalityAnalysis data) - { - var pk = data.Entity; - bool checksRequired = data.EncounterMatch is EncounterStatic5N; - if (pk is PK5 pk5) - { - bool has = pk5.NSparkle; - if (checksRequired && !has) - data.AddLine(GetInvalid(LG5SparkleRequired, CheckIdentifier.Fateful)); - if (!checksRequired && has) - data.AddLine(GetInvalid(LG5SparkleInvalid, CheckIdentifier.Fateful)); - } - - if (!checksRequired) - return; - - if (pk.OT_Gender != 0) - data.AddLine(GetInvalid(LG5OTGenderN, CheckIdentifier.Trainer)); - if (!VerifyNsPKMIVsValid(pk)) - data.AddLine(GetInvalid(LG5IVAll30, CheckIdentifier.IVs)); - if (!VerifyNsPKMOTValid(pk)) - data.AddLine(GetInvalid(LG5ID_N, CheckIdentifier.Trainer)); - if (pk.IsShiny) - data.AddLine(GetInvalid(LG5PIDShinyN, CheckIdentifier.Shiny)); - } - - private static bool VerifyNsPKMIVsValid(PKM pk) - { - // All are 30. - return pk is { IV_HP: 30, IV_ATK: 30, IV_DEF: 30, IV_SPA: 30, IV_SPD: 30, IV_SPE: 30 }; - } - - private static bool VerifyNsPKMOTValid(PKM pk) - { - if (pk.TID16 != 00002 || pk.SID16 != 00000) - return false; - var ot = pk.OT_Name; - if (ot.Length != 1) - return false; - var c = EncounterStatic5N.GetOT(pk.Language); - return c == ot; - } -} diff --git a/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs b/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs index 4bde7510b..abefd2b74 100644 --- a/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using static PKHeX.Core.LegalityCheckStrings; using static PKHeX.Core.LanguageID; @@ -46,10 +45,10 @@ public sealed class NicknameVerifier : Verifier data.AddLine(Get(LEncGiftNicknamed, ParseSettings.NicknamedMysteryGift)); } - if (enc is EncounterTrade t) + if (enc is IFixedTrainer t) { - VerifyNicknameTrade(data, t); - if (t.HasNickname) + VerifyNicknameTrade(data, enc); + if (t is IFixedNickname { IsFixedNickname: true }) return; } @@ -289,17 +288,14 @@ public sealed class NicknameVerifier : Verifier data.AddLine(GetValid(LNickMatchLanguageEgg, CheckIdentifier.Egg)); } - private static void VerifyNicknameTrade(LegalityAnalysis data, EncounterTrade t) + private static void VerifyNicknameTrade(LegalityAnalysis data, IEncounterTemplate t) { - switch (data.Info.Generation) + switch (t) { - case 8 when t is EncounterTrade8b b: VerifyTrade8b(data, b); return; - - case 1: VerifyTrade12(data, t); return; - case 2: return; // already checked all relevant properties when fetching with getValidEncounterTradeVC2 - case 3: VerifyTrade3(data, t); return; - case 4: VerifyTrade4(data, t); return; - case 5: VerifyTrade5(data, t); return; + case EncounterTrade8b b: VerifyTrade8b(data, b); return; + case EncounterTrade4PID t4: VerifyTrade4(data, t4); return; + case EncounterTrade5BW t5: + VerifyEncounterTrade5(data, t5); return; default: VerifyTrade(data, t, data.Entity.Language); return; } @@ -329,68 +325,12 @@ public sealed class NicknameVerifier : Verifier } } - private static void VerifyTrade12(LegalityAnalysis data, EncounterTrade t) - { - var t1 = (EncounterTrade1)t; - if (!t1.IsNicknameValid(data.Entity)) - data.AddLine(GetInvalid(LEncTradeChangedNickname, CheckIdentifier.Nickname)); - if (!t1.IsTrainerNameValid(data.Entity)) - data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer)); - } - - private static void VerifyTrade3(LegalityAnalysis data, EncounterTrade t) + private static void VerifyTrade4(LegalityAnalysis data, EncounterTrade4PID t) { var pk = data.Entity; - int lang = pk.Language; - if (t.Species == (int)Species.Jynx) // FRLG Jynx - lang = DetectTradeLanguageG3DANTAEJynx(pk, lang); - VerifyTrade(data, t, lang); - } - - private static void VerifyTrade4(LegalityAnalysis data, EncounterTrade t) - { - var pk = data.Entity; - if (pk.TID16 == 1000) - { - VerifyTradeOTOnly(data, t); - return; - } - int lang = pk.Language; - switch (t.Species) - { - case (int)Species.Pikachu: // HGSS Pikachu - lang = DetectTradeLanguageG4SurgePikachu(pk, t, lang); - // flag korean on gen4 saves since the pk.Language is German - FlagKoreanIncompatibleSameGenTrade(data, pk, lang); - break; - case (int)Species.Magikarp: // DPPt Magikarp - lang = DetectTradeLanguageG4MeisterMagikarp(pk, t, lang); - // flag korean on gen4 saves since the pk.Language is German - FlagKoreanIncompatibleSameGenTrade(data, pk, lang); - break; - - default: - if (t is EncounterTrade4PID && pk.Version is ((int)GameVersion.D or (int)GameVersion.P)) // mainline DP - { - // DP English origin are Japanese lang. Can't have LanguageID 2 - if (lang == 2) - { - data.AddLine(GetInvalid(string.Format(LOTLanguage, Japanese, English), CheckIdentifier.Language)); - break; - } - - // Since two locales (JPN/ENG) can have the same LanguageID, check which we should be validating with. - if (lang == 1) - { - ReadOnlySpan ot = pk.OT_Name; - var expect = t.GetOT(1); - var match = ot.SequenceEqual(expect); - if (!match) - lang = 2; // verify strings with English locale instead. - } - } - break; - } + if (t.IsIncorrectEnglish(pk)) + data.AddLine(GetInvalid(string.Format(LOTLanguage, Japanese, English), CheckIdentifier.Language)); + var lang = t.DetectOriginalLanguage(pk); VerifyTrade(data, t, lang); } @@ -400,22 +340,20 @@ public sealed class NicknameVerifier : Verifier int lang = pk.Language; if (t.Species == (int)Species.Magikarp) { - // Japanese - if (pk is { Language: (int)Japanese, OT_Name: "Diamond." or "Pearl." }) + if (t.IsMagikarpJapaneseTradedBDSP(pk)) { - // Traded between players, the original OT is replaced with the above OT (version dependent) as the original OT is >6 chars in length. - VerifyTradeNickname(data, t, t.Nicknames[(int)German], pk); + // Traded replaces the OT Name. Verify only the Nickname now. + VerifyNickname(data, t, (int)German); return; } - lang = DetectTradeLanguageG8MeisterMagikarp(pk, t, lang); - if (lang == 0) // err + lang = t.DetectMeisterMagikarpLanguage(pk.Nickname, pk.OT_Name, lang); + if (lang == -1) // err data.AddLine(GetInvalid(string.Format(LOTLanguage, $"{Japanese}/{German}", $"{(LanguageID)pk.Language}"), CheckIdentifier.Language)); } - if (t.Species == (int)Species.Chatot && pk is { Language: (int)French, IsNicknamed: false }) + if (t.IsPijako(pk)) { - // NgWord disallows the French nickname (Pijouk) and resets it back to default (Pijako). // Let it be anything (Nicknamed or not) and just verify the OT. VerifyTradeOTOnly(data, t); return; @@ -423,168 +361,55 @@ public sealed class NicknameVerifier : Verifier VerifyTrade(data, t, lang); } - private static int DetectTradeLanguageG8MeisterMagikarp(PKM pk, EncounterTrade8b t, int currentLanguageID) - { - // Receiving the trade on a German game -> Japanese LanguageID. - // Receiving the trade on any other language -> German LanguageID. - if (currentLanguageID is not ((int)Japanese or (int)German)) - return 0; - - var nick = pk.Nickname; - var ot = pk.OT_Name; - for (int i = 1; i < (int)ChineseT; i++) - { - if (t.Nicknames[i] != nick) - continue; - if (t.TrainerNames[i] != ot) - continue; - - // Language gets flipped to another language ID; can't be equal. - var shouldNotBe = currentLanguageID == (int)German ? German : Japanese; - return i != (int)shouldNotBe ? i : 0; - } - return 0; - } - - private static void FlagKoreanIncompatibleSameGenTrade(LegalityAnalysis data, PKM pk, int lang) - { - if (pk.Format != 4 || lang != (int)Korean) - return; // transferred or not appropriate - if (ParseSettings.ActiveTrainer.Language != (int)Korean && ParseSettings.ActiveTrainer.Language >= 0) - data.AddLine(GetInvalid(string.Format(LTransferOriginFInvalid0_1, L_XKorean, L_XKoreanNon), CheckIdentifier.Language)); - } - - private static int DetectTradeLanguage(ReadOnlySpan OT, EncounterTrade t, int currentLanguageID) - { - var names = t.TrainerNames; - for (int lang = 1; lang < names.Count; lang++) - { - var expect = names[lang]; - var match = OT.SequenceEqual(expect); - if (match) - return lang; - } - return currentLanguageID; - } - - private static int DetectTradeLanguageG3DANTAEJynx(PKM pk, int currentLanguageID) - { - if (currentLanguageID != (int)Italian) - return currentLanguageID; - - if (pk.Version == (int)GameVersion.LG) - currentLanguageID = (int)English; // translation error; OT was not localized => same as English - return currentLanguageID; - } - - private static int DetectTradeLanguageG4MeisterMagikarp(PKM pk, EncounterTrade t, int currentLanguageID) - { - if (currentLanguageID == (int)English) - return (int)German; - - // All have German, regardless of origin version. - var lang = DetectTradeLanguage(pk.OT_Name, t, currentLanguageID); - if (lang == (int)English) // possible collision with FR/ES/DE. Check nickname - return pk.Nickname == t.Nicknames[(int)French] ? (int)French : (int)Spanish; // Spanish is same as English - - return lang; - } - - private static int DetectTradeLanguageG4SurgePikachu(PKM pk, EncounterTrade t, int currentLanguageID) - { - if (currentLanguageID == (int)French) - return (int)English; - - // All have English, regardless of origin version. - var lang = DetectTradeLanguage(pk.OT_Name, t, currentLanguageID); - if (lang == 2) // possible collision with ES/IT. Check nickname - return pk.Nickname == t.Nicknames[(int)Italian] ? (int)Italian : (int)Spanish; - - return lang; - } - - private static void VerifyTrade5(LegalityAnalysis data, EncounterTrade t) + private static void VerifyEncounterTrade5(LegalityAnalysis data, EncounterTrade5BW t) { var pk = data.Entity; - int lang = pk.Language; - // Trades for JPN games have language ID of 0, not 1. - if (pk.BW) - { - if (pk.Format == 5 && lang == (int)Japanese) - data.AddLine(GetInvalid(string.Format(LOTLanguage, 0, Japanese), CheckIdentifier.Language)); + var lang = pk.Language; + if (pk.Format == 5 && lang == (int)Japanese) + data.AddLine(GetInvalid(string.Format(LOTLanguage, 0, Japanese), CheckIdentifier.Language)); - lang = Math.Max(lang, 1); - VerifyTrade(data, t, lang); - } - else // B2W2 - { - if (t.TID16 is Encounters5B2W2.YancyTID or Encounters5B2W2.CurtisTID) - VerifyTradeOTOnly(data, t); - else - VerifyTrade(data, t, lang); - } + lang = Math.Max(lang, 1); + VerifyTrade(data, t, lang); } - private static void VerifyTradeOTOnly(LegalityAnalysis data, EncounterTrade t) + private static void VerifyTradeOTOnly(LegalityAnalysis data, IFixedTrainer t) { - var result = CheckTradeOTOnly(data, t.TrainerNames); + var result = CheckTradeOTOnly(data, t); data.AddLine(result); } - private static CheckResult CheckTradeOTOnly(LegalityAnalysis data, IReadOnlyList validOT) + private static CheckResult CheckTradeOTOnly(LegalityAnalysis data, IFixedTrainer t) { var pk = data.Entity; if (pk.IsNicknamed && (pk.Format < 8 || pk.FatefulEncounter)) return GetInvalid(LEncTradeChangedNickname, CheckIdentifier.Nickname); int lang = pk.Language; - if (validOT.Count <= lang) + if (!t.IsTrainerMatch(pk, pk.OT_Name, lang)) return GetInvalid(LEncTradeIndexBad, CheckIdentifier.Trainer); - if (validOT[lang] != pk.OT_Name) - return GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer); return GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname); } - private static void VerifyTrade(LegalityAnalysis data, EncounterTrade t, int language) + private static void VerifyTrade(LegalityAnalysis data, IEncounterTemplate t, int language) { - var ot = t.GetOT(language); - var nick = t.GetNickname(language); - if (string.IsNullOrEmpty(nick)) - VerifyTradeOTOnly(data, t); - else - VerifyTradeOTNick(data, t, nick, ot); + if (t is IFixedTrainer { IsFixedTrainer: true } ft) + VerifyTrainerName(data, ft, language); + if (t is IFixedNickname { IsFixedNickname: true } fn) + VerifyNickname(data, fn, language); } - private static void VerifyTradeOTNick(LegalityAnalysis data, EncounterTrade t, ReadOnlySpan nick, ReadOnlySpan encounterOT) + private static void VerifyNickname(LegalityAnalysis data, IFixedNickname fn, int language) { var pk = data.Entity; - // trades that are not nicknamed (but are present in a table with others being named) - VerifyTradeNickname(data, t, nick, pk); - - var currentOT = pk.OT_Name; - var match = encounterOT.SequenceEqual(currentOT); - if (!match) - data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer)); - } - - private static void VerifyTradeNickname(LegalityAnalysis data, EncounterTrade t, ReadOnlySpan expectedNickname, PKM pk) - { - var result = IsNicknameMatch(expectedNickname, pk, t) + var result = fn.IsNicknameMatch(pk, pk.Nickname, language) ? GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname) : Get(LEncTradeChangedNickname, ParseSettings.NicknamedTrade, CheckIdentifier.Nickname); data.AddLine(result); } - private static bool IsNicknameMatch(ReadOnlySpan nick, ILangNick pk, EncounterTrade enc) + private static void VerifyTrainerName(LegalityAnalysis data, IFixedTrainer ft, int language) { - if (nick == "Quacklin’" && pk.Nickname == "Quacklin'") - return true; - if (enc.IsNicknamed != pk.IsNicknamed) - return false; - - var currentNick = pk.Nickname; - var match = nick.SequenceEqual(currentNick); - if (!match) // if not match, must not be a nicknamed trade && not currently named - return !enc.IsNicknamed && !pk.IsNicknamed; - return true; + var pk = data.Entity; + if (!ft.IsTrainerMatch(pk, pk.OT_Name, language)) + data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer)); } } diff --git a/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs b/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs index b61c78268..24f3992d0 100644 --- a/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs @@ -33,45 +33,22 @@ public sealed class PIDVerifier : Verifier private void VerifyShiny(LegalityAnalysis data) { var pk = data.Entity; + var enc = data.EncounterMatch; - switch (data.EncounterMatch) + if (!enc.Shiny.IsValid(pk)) + data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny)); + + switch (enc) { - case EncounterStatic s: - if (!s.Shiny.IsValid(pk)) - data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny)); - - // Underground Raids are originally anti-shiny on encounter. - // When selecting a prize at the end, the game rolls and force-shiny is applied to be XOR=1. - if (s is EncounterStatic8U {Shiny: Shiny.Random}) - { - if (pk.ShinyXor is <= 15 and not 1) - data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny)); - break; - } - - if (s.Generation != 5) - break; - - // Generation 5 has a correlation for wild captures. - // Certain static encounter types are just generated straightforwardly. - if (s.Location == 75) // Entree Forest - break; - - // Not wild / forced ability - if (s.Gift || s.Ability == AbilityPermission.OnlyHidden) - break; - - // Forced PID or generated without an encounter - // Crustle has 0x80 for its StartWildBattle flag; dunno what it does, but sometimes it doesn't align with the expected PID xor. - if (s is EncounterStatic5 { IsWildCorrelationPID: true }) - VerifyG5PID_IDCorrelation(data); + // Forced PID or generated without an encounter + // Crustle has 0x80 for its StartWildBattle flag; dunno what it does, but sometimes it doesn't align with the expected PID xor. + case EncounterStatic5 { IsWildCorrelationPID: true }: + VerifyG5PID_IDCorrelation(data); break; - - case EncounterSlot5 {IsHiddenGrotto: true}: - if (pk.IsShiny) - data.AddLine(GetInvalid(LG5PIDShinyGrotto, CheckIdentifier.Shiny)); + case EncounterSlot5 {IsHiddenGrotto: true} when pk.IsShiny: + data.AddLine(GetInvalid(LG5PIDShinyGrotto, CheckIdentifier.Shiny)); break; - case EncounterSlot5: + case EncounterSlot5 {IsHiddenGrotto: false}: VerifyG5PID_IDCorrelation(data); break; @@ -83,6 +60,11 @@ public sealed class PIDVerifier : Verifier case WC7 wc7 when wc7.IsAshGreninjaWC7(pk) && pk.IsShiny: data.AddLine(GetInvalid(LEncGiftShinyMismatch, CheckIdentifier.Shiny)); break; + // Underground Raids are originally anti-shiny on encounter. + // When selecting a prize at the end, the game rolls and force-shiny is applied to be XOR=1. + case EncounterStatic8U u when !u.IsShinyXorValid(pk.ShinyXor): + data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny)); + break; } } diff --git a/PKHeX.Core/Legality/Verifiers/ParseSettings.cs b/PKHeX.Core/Legality/Verifiers/ParseSettings.cs index 32bc4197a..275a3b520 100644 --- a/PKHeX.Core/Legality/Verifiers/ParseSettings.cs +++ b/PKHeX.Core/Legality/Verifiers/ParseSettings.cs @@ -19,7 +19,7 @@ public static class ParseSettings /// Setting to specify if an analysis should permit data sourced from the physical cartridge era of GameBoy games. /// /// If false, indicates to use Virtual Console rules (which are transferable to Gen7+) - public static bool AllowGBCartEra { get; set; } + public static bool AllowGBCartEra { private get; set; } /// /// Setting to specify if an analysis should permit trading a Generation 1 origin file to Generation 2, then back. Useful for checking RBY Metagame rules. @@ -68,7 +68,13 @@ public static class ParseSettings /// Pokemon Stadium 2 was never released in Korea. /// Data being checked /// True if Crystal data is allowed - public static bool AllowGen2MoveReminder(PKM pk) => !pk.Korean && AllowGBCartEra; + public static bool AllowGen2MoveReminder(PKM pk) => !pk.Korean && AllowGBStadium2; + + public static bool AllowGen2OddEgg(PKM pk) => !pk.Japanese || AllowGBCartEra; + + public static bool AllowGBVirtualConsole3DS => !AllowGBCartEra; + public static bool AllowGBEraEvents => AllowGBCartEra; + public static bool AllowGBStadium2 => AllowGBCartEra; internal static bool IsFromActiveTrainer(PKM pk) => ActiveTrainer.IsFromTrainer(pk); diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/MarkRules.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/MarkRules.cs index 2b760f070..9fd8326f9 100644 --- a/PKHeX.Core/Legality/Verifiers/Ribbons/MarkRules.cs +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/MarkRules.cs @@ -76,7 +76,7 @@ public static class MarkRules private static bool IsSlotWeatherPermittedSWSH(AreaWeather8 permit, EncounterSlot8 s) { - var location = s.Location; + var location = s.Parent.Location; // If it's not in the main table, it can only have Normal weather. if (!EncounterArea8.WeatherbyArea.TryGetValue(location, out var weather)) weather = AreaWeather8.Normal; @@ -88,7 +88,7 @@ public static class MarkRules return false; // Check bleed conditions otherwise. - return EncounterArea8.IsWeatherBleedPossible(s.SlotType, permit, location); + return EncounterArea8.IsWeatherBleedPossible(s.Type, permit, location); } /// diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonRules.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonRules.cs index e8fc4d9f6..62f34c1e0 100644 --- a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonRules.cs +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonRules.cs @@ -180,7 +180,7 @@ public static class RibbonRules if (enc.LevelMin <= 50) return true; - return enc is not (EncounterStaticShadow or WC3); + return enc is not (IShadow3 or WC3); } /// @@ -294,7 +294,7 @@ public static class RibbonRules if (enc.Generation != 3) return false; - if (enc is not EncounterStaticShadow) + if (enc is not IShadow3) return false; // Ribbon is awarded when the Pokémon is purified in the game of origin. diff --git a/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs b/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs index 07dfb15c7..2c788e3ac 100644 --- a/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs @@ -59,7 +59,7 @@ public sealed class TrainerNameVerifier : Verifier /// internal static bool IsPlayerOriginalTrainer(IEncounterable enc) => enc switch { - EncounterTrade { HasTrainerName: true } => false, + IFixedTrainer { IsFixedTrainer: true } => false, MysteryGift { IsEgg: false } => false, EncounterStatic5N => false, _ => true, @@ -78,7 +78,7 @@ public sealed class TrainerNameVerifier : Verifier return ot.Length <= len; } - if (e is EncounterTrade { HasTrainerName: true }) + if (e is IFixedTrainer { IsFixedTrainer: true }) return true; // already verified if (e is MysteryGift mg && mg.OT_Name.Length == ot.Length) @@ -108,7 +108,7 @@ public sealed class TrainerNameVerifier : Verifier if (pk.OT_Gender == 1) { - if (pk is ICaughtData2 {CaughtData:0} or { Format: > 2, VC1: true } || data is {EncounterOriginal: {Generation:1} or EncounterStatic2E {IsGift:true}}) + if (pk is ICaughtData2 {CaughtData:0} or { Format: > 2, VC1: true } || data is {EncounterOriginal: {Generation:1} or EncounterGift2 {IsGift:true}}) data.AddLine(GetInvalid(LG1OTGender)); } } @@ -117,7 +117,7 @@ public sealed class TrainerNameVerifier : Verifier { if (StringConverter12.GetIsG1English(str)) { - if (str.Length > 7 && data.EncounterOriginal is not EncounterTradeGB) // OT already verified; GER shuckle has 8 chars + if (str.Length > 7 && data.EncounterOriginal is not IFixedTrainer { IsFixedTrainer: true }) // OT already verified; GER shuckle has 8 chars data.AddLine(GetInvalid(LOTLong)); } else if (StringConverter12.GetIsG1Japanese(str)) diff --git a/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs b/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs index f64e1bf68..ff1ddea00 100644 --- a/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs @@ -210,7 +210,7 @@ public sealed class TransferVerifier : Verifier } } - public void VerifyVCEncounter(PKM pk, IEncounterTemplate original, ILocation transfer, LegalityAnalysis data) + public void VerifyVCEncounter(PKM pk, IEncounterTemplate original, EncounterTransfer7 transfer, LegalityAnalysis data) { if (pk.Met_Location != transfer.Location) data.AddLine(GetInvalid(LTransferMetLocation)); @@ -220,7 +220,7 @@ public sealed class TransferVerifier : Verifier data.AddLine(GetInvalid(LEggLocationNone)); // Flag Moves that cannot be transferred - if (original is EncounterStatic2Odd) // Dizzy Punch Gifts + if (original is EncounterStatic2 { DizzyPunchEgg: true}) // Dizzy Punch Gifts FlagIncompatibleTransferMove(pk, data.Info.Moves, 146, 2); // can't have Dizzy Punch at all bool checkShiny = pk.VC2 || (pk.VC1 && GBRestrictions.IsTimeCapsuleTransferred(pk, data.Info.Moves, original).WasTimeCapsuleTransferred()); diff --git a/PKHeX.Core/MysteryGifts/MysteryGift.cs b/PKHeX.Core/MysteryGifts/MysteryGift.cs index d312e1cbe..662e98e23 100644 --- a/PKHeX.Core/MysteryGifts/MysteryGift.cs +++ b/PKHeX.Core/MysteryGifts/MysteryGift.cs @@ -7,7 +7,7 @@ namespace PKHeX.Core; /// /// Mystery Gift Template File /// -public abstract class MysteryGift : IEncounterable, IMoveset, IRelearn, ITrainerID32, IFatefulEncounterReadOnly +public abstract class MysteryGift : IEncounterable, IMoveset, IRelearn, ITrainerID32, IFatefulEncounterReadOnly, IEncounterMatch { /// /// Determines whether or not the given length of bytes is valid for a mystery gift. diff --git a/PKHeX.Core/MysteryGifts/PCD.cs b/PKHeX.Core/MysteryGifts/PCD.cs index f8e7dad9b..933f9c8e0 100644 --- a/PKHeX.Core/MysteryGifts/PCD.cs +++ b/PKHeX.Core/MysteryGifts/PCD.cs @@ -12,7 +12,7 @@ namespace PKHeX.Core; /// https://projectpokemon.org/home/forums/topic/5870-pok%C3%A9mon-mystery-gift-editor-v143-now-with-bw-support/ /// See also: http://tccphreak.shiny-clique.net/debugger/pcdfiles.htm /// -public sealed class PCD : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 +public sealed class PCD : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, IRestrictVersion { public const int Size = 0x358; // 856 public override int Generation => 4; @@ -127,7 +127,7 @@ public sealed class PCD : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 return Gift.ConvertToPKM(tr, criteria); } - public bool CanBeReceivedByVersion(int pkmVersion) => ((CardCompatibility >> pkmVersion) & 1) == 1; + public bool CanBeReceivedByVersion(int pkmVersion) => (byte)Version == pkmVersion; public override bool IsMatchExact(PKM pk, EvoCriteria evo) { @@ -194,7 +194,7 @@ public sealed class PCD : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 } protected override bool IsMatchPartial(PKM pk) => !CanBeReceivedByVersion(pk.Version); - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; public bool RibbonEarth { get => Gift.RibbonEarth; set => Gift.RibbonEarth = value; } public bool RibbonNational { get => Gift.RibbonNational; set => Gift.RibbonNational = value; } diff --git a/PKHeX.Core/MysteryGifts/PGF.cs b/PKHeX.Core/MysteryGifts/PGF.cs index 125bf0130..9108146cf 100644 --- a/PKHeX.Core/MysteryGifts/PGF.cs +++ b/PKHeX.Core/MysteryGifts/PGF.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// /// Generation 5 Mystery Gift Template File /// -public sealed class PGF : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, INature +public sealed class PGF : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, INature, IRestrictVersion { public const int Size = 0xCC; public const int SizeFull = 0x2D0; @@ -251,8 +251,10 @@ public sealed class PGF : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, I source.SetEncounterMoves(Species, Form, Level, moves); pk.SetMoves(moves); } - - pk.SetMaximumPPCurrent(); + else + { + pk.SetMaximumPPCurrent(); + } if (IsEgg) // User's { @@ -414,7 +416,7 @@ public sealed class PGF : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, I return true; } - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; protected override bool IsMatchPartial(PKM pk) => !CanBeReceivedByVersion(pk.Version); public bool CanBeReceivedByVersion(int game) => OriginGame == 0 || OriginGame == game; diff --git a/PKHeX.Core/MysteryGifts/PGT.cs b/PKHeX.Core/MysteryGifts/PGT.cs index 9de3330d9..e8a78fa46 100644 --- a/PKHeX.Core/MysteryGifts/PGT.cs +++ b/PKHeX.Core/MysteryGifts/PGT.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// /// Generation 4 Mystery Gift Template File (Inner Gift Data, no card data) /// -public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 +public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, IRandomCorrelation { public const int Size = 0x104; // 260 public override int Generation => 4; @@ -22,7 +22,7 @@ public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 public override int Ball { - get => IsEntity ? PK.Ball : 0; + get => IsManaphyEgg ? 4 : IsEntity ? PK.Ball : 0; set { if (IsEntity) PK.Ball = value; } } @@ -166,7 +166,7 @@ public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 { pk4.Met_Location = pk4.Egg_Location + 3000; pk4.Egg_Location = 0; - pk4.MetDate = DateOnly.FromDateTime(DateTime.Now); + pk4.MetDate = EncounterDate.GetDateNDS(); pk4.IsEgg = false; } else @@ -240,7 +240,7 @@ public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 { pk4.IsEgg = false; // Met Location & Date is modified when transferred to pk5; don't worry about it. - pk4.EggMetDate = DateOnly.FromDateTime(DateTime.Now); + pk4.EggMetDate = EncounterDate.GetDateNDS(); } private void SetUnhatchedEggDetails(PK4 pk4) @@ -248,7 +248,7 @@ public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 pk4.IsEgg = true; pk4.IsNicknamed = false; pk4.Nickname = SpeciesName.GetEggName(pk4.Language, Generation); - pk4.EggMetDate = DateOnly.FromDateTime(DateTime.Now); + pk4.EggMetDate = EncounterDate.GetDateNDS(); } private static uint GeneratePID(uint seed, PK4 pk4) @@ -301,4 +301,42 @@ public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 public bool RibbonWorld { get => PK.RibbonWorld; set => PK.RibbonWorld = value; } public bool RibbonChampionWorld { get => PK.RibbonChampionWorld; set => PK.RibbonChampionWorld = value; } public bool RibbonSouvenir { get => PK.RibbonSouvenir; set => PK.RibbonSouvenir = value; } + + public bool IsCompatible(PIDType val, PKM pk) + { + if (IsManaphyEgg) + return IsG4ManaphyPIDValid(val, pk); + return val == PIDType.None; + } + + public PIDType GetSuggestedCorrelation() + { + if (IsManaphyEgg) + return PIDType.Method_1; + return PIDType.None; + } + + private static bool IsG4ManaphyPIDValid(PIDType val, PKM pk) + { + if (pk.IsEgg) + { + if (pk.IsShiny) + return false; + if (val == PIDType.Method_1) + return true; + return val == PIDType.G4MGAntiShiny && IsAntiShinyARNG(pk); + } + + if (val == PIDType.Method_1) + return pk.WasTradedEgg || !pk.IsShiny; // can't be shiny on received game + return val == PIDType.G4MGAntiShiny && (pk.WasTradedEgg || IsAntiShinyARNG(pk)); + + static bool IsAntiShinyARNG(PKM pk) + { + var shinyPID = ARNG.Prev(pk.PID); + var tmp = pk.ID32 ^ shinyPID; + var xor = (ushort)(tmp ^ (tmp >> 16)); + return xor < 8; // shiny proc + } + } } diff --git a/PKHeX.Core/MysteryGifts/PL6.cs b/PKHeX.Core/MysteryGifts/PL6.cs index 88f1d0ddb..cd7514e77 100644 --- a/PKHeX.Core/MysteryGifts/PL6.cs +++ b/PKHeX.Core/MysteryGifts/PL6.cs @@ -77,7 +77,7 @@ public sealed class PL6 /// This Template object is very similar to the structure and similar objects, in that the structure offsets are ordered the same. /// This template object is only present in Generation 6 save files. /// -public sealed class PL6_PKM : IRibbonSetEvent3, IRibbonSetEvent4, IEncounterInfo +public sealed class PL6_PKM : IRibbonSetEvent3, IRibbonSetEvent4, IEncounterInfo, IMoveset, IRelearn, IContestStats, IMemoryOT, ITrainerID32 { internal const int Size = 0xA0; @@ -86,8 +86,11 @@ public sealed class PL6_PKM : IRibbonSetEvent3, IRibbonSetEvent4, IEncounterInfo public PL6_PKM() : this(new byte[Size]) { } public PL6_PKM(byte[] data) => Data = data; - public int TID16 { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } - public int SID16 { get => ReadUInt16LittleEndian(Data.AsSpan(0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(0x02), (ushort)value); } + public TrainerIDFormat TrainerIDDisplayFormat => TrainerIDFormat.SixteenBit; + + public uint ID32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } + public ushort TID16 { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), value); } + public ushort SID16 { get => ReadUInt16LittleEndian(Data.AsSpan(0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(0x02), value); } public int OriginGame { get => Data[0x04]; set => Data[0x04] = (byte)value; } public uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(0x08)); set => WriteUInt32LittleEndian(Data.AsSpan(0x08), value); } public int Ball { get => Data[0xE]; set => Data[0xE] = (byte)value; } @@ -114,12 +117,12 @@ public sealed class PL6_PKM : IRibbonSetEvent3, IRibbonSetEvent4, IEncounterInfo public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x3E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3E), (ushort)value); } public byte MetLevel { get => Data[0x40]; set => Data[0x40] = value; } - public int CNT_Cool { get => Data[0x41]; set => Data[0x41] = (byte)value; } - public int CNT_Beauty { get => Data[0x42]; set => Data[0x42] = (byte)value; } - public int CNT_Cute { get => Data[0x43]; set => Data[0x43] = (byte)value; } - public int CNT_Smart { get => Data[0x44]; set => Data[0x44] = (byte)value; } - public int CNT_Tough { get => Data[0x45]; set => Data[0x45] = (byte)value; } - public int CNT_Sheen { get => Data[0x46]; set => Data[0x46] = (byte)value; } + public byte CNT_Cool { get => Data[0x41]; set => Data[0x41] = value; } + public byte CNT_Beauty { get => Data[0x42]; set => Data[0x42] = value; } + public byte CNT_Cute { get => Data[0x43]; set => Data[0x43] = value; } + public byte CNT_Smart { get => Data[0x44]; set => Data[0x44] = value; } + public byte CNT_Tough { get => Data[0x45]; set => Data[0x45] = value; } + public byte CNT_Sheen { get => Data[0x46]; set => Data[0x46] = value; } public int IV_HP { get => Data[0x47]; set => Data[0x47] = (byte)value; } public int IV_ATK { get => Data[0x48]; set => Data[0x48] = (byte)value; } @@ -183,7 +186,7 @@ public sealed class PL6_PKM : IRibbonSetEvent3, IRibbonSetEvent4, IEncounterInfo } } - public Moveset RelearnMoves + public Moveset Relearn { get => new(RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4); set diff --git a/PKHeX.Core/MysteryGifts/WA8.cs b/PKHeX.Core/MysteryGifts/WA8.cs index 67f56d913..d9a47a1ef 100644 --- a/PKHeX.Core/MysteryGifts/WA8.cs +++ b/PKHeX.Core/MysteryGifts/WA8.cs @@ -491,7 +491,7 @@ public sealed class WA8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDyn pk.SID16 = tr.SID16; } - pk.MetDate = IsDateRestricted && EncounterServerDate.WA8Gifts.TryGetValue(CardID, out var dt) ? dt.Start : DateOnly.FromDateTime(DateTime.Now); + pk.MetDate = IsDateRestricted && EncounterServerDate.WA8Gifts.TryGetValue(CardID, out var dt) ? dt.Start : EncounterDate.GetDateSwitch(); // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). if (CardID is 9018 or 9019 or 9020) @@ -529,7 +529,7 @@ public sealed class WA8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDyn private void SetEggMetData(PKM pk) { pk.IsEgg = true; - pk.EggMetDate = DateOnly.FromDateTime(DateTime.Now); + pk.EggMetDate = EncounterDate.GetDateSwitch(); pk.Nickname = SpeciesName.GetEggName(pk.Language, Generation); pk.IsNicknamed = true; } @@ -706,7 +706,7 @@ public sealed class WA8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDyn return pk.PID == GetPID(pk, type); } - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; protected override bool IsMatchPartial(PKM pk) => false; // no version compatibility checks yet. #region Lazy Ribbon Implementation diff --git a/PKHeX.Core/MysteryGifts/WB7.cs b/PKHeX.Core/MysteryGifts/WB7.cs index 5c0771c9d..6509eb0d7 100644 --- a/PKHeX.Core/MysteryGifts/WB7.cs +++ b/PKHeX.Core/MysteryGifts/WB7.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// /// Generation 7 Mystery Gift Template File (LGP/E) /// -public sealed class WB7 : DataMysteryGift, ILangNick, IAwakened, INature, ILangNicknamedTemplate +public sealed class WB7 : DataMysteryGift, ILangNick, IAwakened, INature, ILangNicknamedTemplate, IRestrictVersion { public const int Size = 0x108; public const int SizeFull = 0x310; @@ -423,7 +423,7 @@ public sealed class WB7 : DataMysteryGift, ILangNick, IAwakened, INature, ILangN pk.SID16 = tr.SID16; } - pk.MetDate = Date ?? DateOnly.FromDateTime(DateTime.Now); + pk.MetDate = Date ?? EncounterDate.GetDateSwitch(); pk.IsNicknamed = GetIsNicknamed(redeemLanguage); pk.Nickname = pk.IsNicknamed ? GetNickname(redeemLanguage) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); @@ -605,6 +605,6 @@ public sealed class WB7 : DataMysteryGift, ILangNick, IAwakened, INature, ILangN return true; } - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; protected override bool IsMatchPartial(PKM pk) => false; } diff --git a/PKHeX.Core/MysteryGifts/WB8.cs b/PKHeX.Core/MysteryGifts/WB8.cs index 9f1965477..0989aa036 100644 --- a/PKHeX.Core/MysteryGifts/WB8.cs +++ b/PKHeX.Core/MysteryGifts/WB8.cs @@ -488,7 +488,7 @@ public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, ICo pk.SID16 = tr.SID16; } - pk.MetDate = IsDateRestricted && EncounterServerDate.WB8Gifts.TryGetValue(CardID, out var dt) ? dt.Start : DateOnly.FromDateTime(DateTime.Now); + pk.MetDate = IsDateRestricted && EncounterServerDate.WB8Gifts.TryGetValue(CardID, out var dt) ? dt.Start : EncounterDate.GetDateSwitch(); // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). if (CardID is 9015 or 9016 or 9017) pk.Met_Day = 20; @@ -522,7 +522,7 @@ public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, ICo private void SetEggMetData(PKM pk) { pk.IsEgg = true; - pk.EggMetDate = DateOnly.FromDateTime(DateTime.Now); + pk.EggMetDate = EncounterDate.GetDateSwitch(); pk.Nickname = SpeciesName.GetEggName(pk.Language, Generation); pk.IsNicknamed = false; } @@ -721,7 +721,7 @@ public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, ICo return LocationsHOME.GetMetSWSH((ushort)Location, version) == met; } - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; protected override bool IsMatchPartial(PKM pk) => false; // no version compatibility checks yet. #region Lazy Ribbon Implementation diff --git a/PKHeX.Core/MysteryGifts/WC3.cs b/PKHeX.Core/MysteryGifts/WC3.cs index 20d03d172..e715ca557 100644 --- a/PKHeX.Core/MysteryGifts/WC3.cs +++ b/PKHeX.Core/MysteryGifts/WC3.cs @@ -9,7 +9,7 @@ namespace PKHeX.Core; /// This is fabricated data built to emulate the future generation Mystery Gift objects. /// Data here is not stored in any save file and cannot be naturally exported. /// -public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate +public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate, IRandomCorrelation { public override MysteryGift Clone() => (WC3)MemberwiseClone(); @@ -19,6 +19,17 @@ public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate /// Matched Type /// public PIDType Method { get; init; } + public PIDType GetSuggestedCorrelation() => Method; + public bool IsCompatible(PIDType type, PKM pk) + { + if (type == Method) + return true; + + // forced shiny eggs, when hatched, can lose their detectable correlation. + if (!IsEgg || pk.IsEgg) + return false; + return type is PIDType.BACD_R_S or PIDType.BACD_U_S; + } private const ushort UnspecifiedID = ushort.MaxValue; @@ -261,11 +272,7 @@ public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate if (Language != -1 && Language != pk.Language) return false; if (Ball != pk.Ball) return false; if (FatefulEncounter != pk.FatefulEncounter) - { - // XD Gifts only at level 20 get flagged after transfer - if (Version == GameVersion.XD != (pk is XK3)) - return false; - } + return false; if (pk.IsNative) { @@ -295,7 +302,7 @@ public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate return ot.Length == 7 && wc.StartsWith(ot, StringComparison.Ordinal); } - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; protected override bool IsMatchPartial(PKM pk) => false; public string GetNickname(int language) => Nickname ?? string.Empty; diff --git a/PKHeX.Core/MysteryGifts/WC6.cs b/PKHeX.Core/MysteryGifts/WC6.cs index 6291a4abe..bdfc77589 100644 --- a/PKHeX.Core/MysteryGifts/WC6.cs +++ b/PKHeX.Core/MysteryGifts/WC6.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// /// Generation 6 Mystery Gift Template File /// -public sealed class WC6 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, INature, IMemoryOT +public sealed class WC6 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, INature, IMemoryOT, IRestrictVersion { public const int Size = 0x108; public const uint EonTicketConst = 0x225D73C2; @@ -376,12 +376,12 @@ public sealed class WC6 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, I } else { - pk.SetDefaultRegionOrigins(); + pk.SetDefaultRegionOrigins(pk.Language); } pk.SetMaximumPPCurrent(); - pk.MetDate = Date ?? DateOnly.FromDateTime(DateTime.Now); + pk.MetDate = Date ?? EncounterDate.GetDate3DS(); if ((tr.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) { @@ -606,7 +606,12 @@ public sealed class WC6 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, I if (RestrictLanguage != 0 && RestrictLanguage != pk.Language) return true; if (!CanBeReceivedByVersion(pk.Version)) - return true; + { + if (!IsEgg || pk.IsEgg) + return true; + if (pk.Egg_Location != Locations.LinkTrade6) + return true; + } return false; } } diff --git a/PKHeX.Core/MysteryGifts/WC7.cs b/PKHeX.Core/MysteryGifts/WC7.cs index e912c19c7..630152cfe 100644 --- a/PKHeX.Core/MysteryGifts/WC7.cs +++ b/PKHeX.Core/MysteryGifts/WC7.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// /// Generation 7 Mystery Gift Template File /// -public sealed class WC7 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, INature, IMemoryOT +public sealed class WC7 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, INature, IMemoryOT, IRestrictVersion { public const int Size = 0x108; public override int Generation => 7; @@ -440,7 +440,7 @@ public sealed class WC7 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, I } else { - pk.SetDefaultRegionOrigins(); + pk.SetDefaultRegionOrigins(language); } pk.SetMaximumPPCurrent(); @@ -458,7 +458,7 @@ public sealed class WC7 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, I pk.SID16 = tr.SID16; } - pk.MetDate = Date ?? DateOnly.FromDateTime(DateTime.Now); + pk.MetDate = Date ?? EncounterDate.GetDate3DS(); pk.IsNicknamed = IsNicknamed; pk.Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); @@ -640,14 +640,19 @@ public sealed class WC7 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, I set { } } - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; protected override bool IsMatchPartial(PKM pk) { if (RestrictLanguage != 0 && RestrictLanguage != pk.Language) return true; if (!CanBeReceivedByVersion(pk.Version)) - return true; + { + if (!IsEgg || pk.IsEgg) + return true; + if (pk.Egg_Location != Locations.LinkTrade6) + return true; + } return false; } } diff --git a/PKHeX.Core/MysteryGifts/WC8.cs b/PKHeX.Core/MysteryGifts/WC8.cs index d5e07b467..0097dc9bd 100644 --- a/PKHeX.Core/MysteryGifts/WC8.cs +++ b/PKHeX.Core/MysteryGifts/WC8.cs @@ -7,7 +7,7 @@ namespace PKHeX.Core; /// /// Generation 8 Mystery Gift Template File /// -public sealed class WC8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDynamaxLevel, IRibbonIndex, IMemoryOT, ILangNicknamedTemplate, IEncounterServerDate, +public sealed class WC8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDynamaxLevel, IRibbonIndex, IMemoryOT, ILangNicknamedTemplate, IEncounterServerDate, IRestrictVersion, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8 { public const int Size = 0x2D0; @@ -537,18 +537,18 @@ public sealed class WC8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDyn private DateOnly GetSuggestedDate() { if (!IsDateRestricted) - return DateOnly.FromDateTime(DateTime.Now); + return EncounterDate.GetDateSwitch(); if (EncounterServerDate.WC8GiftsChk.TryGetValue(Checksum, out var range)) return range.Start; if (EncounterServerDate.WC8Gifts.TryGetValue(CardID, out range)) return range.Start; - return DateOnly.FromDateTime(DateTime.Now); + return EncounterDate.GetDateSwitch(); } private void SetEggMetData(PKM pk) { pk.IsEgg = true; - pk.EggMetDate = DateOnly.FromDateTime(DateTime.Now); + pk.EggMetDate = EncounterDate.GetDateSwitch(); pk.Nickname = SpeciesName.GetEggName(pk.Language, Generation); pk.IsNicknamed = true; } @@ -780,7 +780,7 @@ public sealed class WC8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDyn public bool IsDateRestricted => IsHOMEGift; - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; protected override bool IsMatchPartial(PKM pk) => false; // no version compatibility checks yet. #region Lazy Ribbon Implementation diff --git a/PKHeX.Core/MysteryGifts/WC9.cs b/PKHeX.Core/MysteryGifts/WC9.cs index 58229bfa0..c6209aa91 100644 --- a/PKHeX.Core/MysteryGifts/WC9.cs +++ b/PKHeX.Core/MysteryGifts/WC9.cs @@ -198,8 +198,13 @@ public sealed class WC9 : DataMysteryGift, ILangNick, INature, ITeraType, IRibbo public int MetLevel { get => Data[CardStart + 0x241]; set => Data[CardStart + 0x241] = (byte)value; } public MoveType TeraTypeOriginal { get => (MoveType)Data[CardStart + 0x242]; set => Data[CardStart + 0x242] = (byte)value; } - public MoveType TeraTypeOverride { get => (MoveType)Data[CardStart + 0x243]; set => Data[CardStart + 0x243] = (byte)value; } - public MoveType TeraType => TeraTypeUtil.GetTeraType((byte)TeraTypeOriginal, (byte)TeraTypeOverride); + public MoveType TeraTypeOverride + { + get => (MoveType)TeraTypeUtil.OverrideNone; + set { } + } + + public MoveType TeraType => TeraTypeOriginal; public short HeightValue { get => ReadInt16LittleEndian(Data.AsSpan(CardStart + 0x244)); set => WriteInt16LittleEndian(Data.AsSpan(CardStart + 0x244), value); } public short WeightValue { get => ReadInt16LittleEndian(Data.AsSpan(CardStart + 0x246)); set => WriteInt16LittleEndian(Data.AsSpan(CardStart + 0x246), value); } @@ -551,18 +556,18 @@ public sealed class WC9 : DataMysteryGift, ILangNick, INature, ITeraType, IRibbo private DateOnly GetSuggestedDate() { if (!IsDateRestricted) - return DateOnly.FromDateTime(DateTime.Now); + return EncounterDate.GetDateSwitch(); if (EncounterServerDate.WC9GiftsChk.TryGetValue(Checksum, out var range)) return range.Start; if (EncounterServerDate.WC9Gifts.TryGetValue(CardID, out range)) return range.Start; - return DateOnly.FromDateTime(DateTime.Now); + return EncounterDate.GetDateSwitch(); } private void SetEggMetData(PKM pk) { pk.IsEgg = true; - pk.EggMetDate = DateOnly.FromDateTime(DateTime.Now); + pk.EggMetDate = EncounterDate.GetDateSwitch(); pk.Nickname = SpeciesName.GetEggName(pk.Language, Generation); pk.IsNicknamed = true; } @@ -773,7 +778,7 @@ public sealed class WC9 : DataMysteryGift, ILangNick, INature, ITeraType, IRibbo public bool IsDateRestricted => true; - protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchDeferred(PKM pk) => false; protected override bool IsMatchPartial(PKM pk) { if (pk is ITeraType t && TeraType != t.TeraTypeOriginal) diff --git a/PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs b/PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs index 9628a58a9..e26c52f69 100644 --- a/PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs +++ b/PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs @@ -16,11 +16,20 @@ public interface IRegionOrigin public static partial class Extensions { - public static void SetDefaultRegionOrigins(this IRegionOrigin o) + public static void SetDefaultRegionOrigins(this IRegionOrigin o, int language) { - o.ConsoleRegion = 1; // North America - o.Region = 7; // California - o.Country = 49; // USA + if (language == 1) + { + o.ConsoleRegion = 0; // Japan + o.Country = 1; // Japan + o.Region = 0; + } + else + { + o.ConsoleRegion = 1; // North America + o.Country = 49; // USA + o.Region = 7; // California + } } public static void CopyRegionOrigin(this IRegionOrigin source, IRegionOrigin dest) diff --git a/PKHeX.Core/PKM/PK1.cs b/PKHeX.Core/PKM/PK1.cs index 7c9634234..74d68e4ab 100644 --- a/PKHeX.Core/PKM/PK1.cs +++ b/PKHeX.Core/PKM/PK1.cs @@ -175,7 +175,7 @@ public sealed class PK1 : GBPKML, IPersonalType Nature = Experience.GetNatureVC(EXP), PID = rnd.Rand32(), Ball = 4, - MetDate = DateOnly.FromDateTime(DateTime.Now), + MetDate = EncounterDate.GetDate3DS(), Version = (int)GameVersion.RD, // Default to red Move1 = Move1, Move2 = Move2, diff --git a/PKHeX.Core/PKM/PK2.cs b/PKHeX.Core/PKM/PK2.cs index 8acaa638f..189377e10 100644 --- a/PKHeX.Core/PKM/PK2.cs +++ b/PKHeX.Core/PKM/PK2.cs @@ -143,7 +143,7 @@ public sealed class PK2 : GBPKML, ICaughtData2 Nature = Experience.GetNatureVC(EXP), PID = rnd.Rand32(), Ball = 4, - MetDate = DateOnly.FromDateTime(DateTime.Now), + MetDate = EncounterDate.GetDate3DS(), Version = HasOriginalMetLocation ? (int)GameVersion.C : (int)GameVersion.SI, Move1 = Move1, Move2 = Move2, diff --git a/PKHeX.Core/PKM/PK3.cs b/PKHeX.Core/PKM/PK3.cs index ed4eee14f..e44bbf312 100644 --- a/PKHeX.Core/PKM/PK3.cs +++ b/PKHeX.Core/PKM/PK3.cs @@ -259,7 +259,7 @@ public sealed class PK3 : G3PKM, ISanityChecksum PKRS_Strain = PKRS_Strain, PKRS_Days = PKRS_Days, OT_Gender = OT_Gender, - MetDate = DateOnly.FromDateTime(DateTime.Now), + MetDate = EncounterDate.GetDateNDS(), Met_Level = CurrentLevel, Met_Location = Locations.Transfer3, // Pal Park diff --git a/PKHeX.Core/PKM/PK4.cs b/PKHeX.Core/PKM/PK4.cs index 17d31e92d..568c241fd 100644 --- a/PKHeX.Core/PKM/PK4.cs +++ b/PKHeX.Core/PKM/PK4.cs @@ -314,14 +314,12 @@ public sealed class PK4 : G4PKM if (Data[0x5F] < 0x10 && ReadUInt16LittleEndian(Data.AsSpan(0x80)) > 0x4000) return new PK5(Data); - var moment = DateOnly.FromDateTime(DateTime.Now); - PK5 pk5 = new(Data.AsSpan(0, PokeCrypto.SIZE_5PARTY).ToArray()) // Convert away! { JunkByte = 0, OT_Friendship = 70, // Apply new met date - MetDate = moment, + MetDate = EncounterDate.GetDateNDS(), }; pk5.HeldMail.Clear(); diff --git a/PKHeX.Core/PKM/PKM.cs b/PKHeX.Core/PKM/PKM.cs index b1cb09324..9e6d79fda 100644 --- a/PKHeX.Core/PKM/PKM.cs +++ b/PKHeX.Core/PKM/PKM.cs @@ -455,6 +455,7 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa Move2 = value.Move2; Move3 = value.Move3; Move4 = value.Move4; + this.SetMaximumPPCurrent(Moves); } public void SetMoves(ReadOnlySpan value) @@ -463,6 +464,7 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa Move2 = value.Length > 1 ? value[1] : default; Move3 = value.Length > 2 ? value[2] : default; Move4 = value.Length > 3 ? value[3] : default; + this.SetMaximumPPCurrent(Moves); } public ushort[] RelearnMoves @@ -896,16 +898,16 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa } /// - public void SetRandomIVs(int minFlawless = -1) => SetRandomIVs(stackalloc int[6], minFlawless); + public void SetRandomIVs(int minFlawless = 0) => SetRandomIVs(stackalloc int[6], minFlawless); /// /// Randomizes the IVs within game constraints. /// /// Temporary variable storage /// Count of flawless IVs to set. If none provided, a count will be detected. - public void SetRandomIVs(Span ivs, int minFlawless = -1) + public void SetRandomIVs(Span ivs, int minFlawless = 0) { - if (Version == (int)GameVersion.GO && minFlawless != 6) + if (Version == (int)GameVersion.GO) { SetRandomIVsGO(ivs); return; @@ -915,10 +917,9 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa for (int i = 0; i < 6; i++) ivs[i] = rnd.Next(MaxIV + 1); - int count = minFlawless == -1 ? GetFlawlessIVCount() : minFlawless; - if (count != 0) + if (minFlawless != 0) { - for (int i = 0; i < count; i++) + for (int i = 0; i < minFlawless; i++) ivs[i] = MaxIV; rnd.Shuffle(ivs); // Randomize IV order } @@ -944,53 +945,19 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa /// IV template to generate from /// Count of flawless IVs to set. If none provided, a count will be detected. /// Randomized IVs if desired. - public void SetRandomIVsTemplate(IndividualValueSet template, int minFlawless = -1) + public void SetRandomIVsTemplate(IndividualValueSet template, int minFlawless = 0) { - int count = minFlawless == -1 ? GetFlawlessIVCount() : minFlawless; Span ivs = stackalloc int[6]; var rnd = Util.Rand; do { for (int i = 0; i < 6; i++) ivs[i] = template[i] < 0 ? rnd.Next(MaxIV + 1) : template[i]; - } while (ivs.Count(MaxIV) < count); + } while (ivs.Count(MaxIV) < minFlawless); SetIVs(ivs); } - /// - /// Gets the amount of flawless IVs that the should have. - /// - /// Count of IVs that should be max. - public int GetFlawlessIVCount() - { - int gen = Generation; - if (gen >= 6) - { - var species = Species; - if (SpeciesCategory.IsMythical(species)) - return 3; - if (SpeciesCategory.IsLegendary(species)) - return 3; - if (SpeciesCategory.IsSubLegendary(species)) - return 3; - if (gen <= 7 && SpeciesCategory.IsUltraBeast(species)) - return 3; - } - if (XY) - { - if (PersonalInfo.EggGroup1 == 15) // Undiscovered - return 3; - if (Met_Location == 148 && Met_Level == 30) // Friend Safari - return 2; - } - if (VC) - return Species is (int)Core.Species.Mew or (int)Core.Species.Celebi ? 5 : 3; - if (this is IAlpha {IsAlpha: true}) - return 3; - return 0; - } - /// /// Applies all shared properties from the current to the . /// diff --git a/PKHeX.Core/PKM/Strings/StringConverter.cs b/PKHeX.Core/PKM/Strings/StringConverter.cs index 27dcc4297..d2b734af6 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter.cs @@ -127,6 +127,11 @@ public static class StringConverter _ => chr, }; + /// + /// Determines if a string contains full-width characters. + /// + /// The input string to check for full-width characters. + /// True if the input string contains full-width characters; otherwise, false. internal static bool GetIsFullWidthString(ReadOnlySpan str) { foreach (var c in str) @@ -140,6 +145,11 @@ public static class StringConverter return false; } + /// + /// Determines if a string contains East Asian script characters. + /// + /// The input string to check for East Asian script characters. + /// True if the input string contains East Asian script characters; otherwise, false. public static bool HasEastAsianScriptCharacters(ReadOnlySpan str) { foreach (var c in str) diff --git a/PKHeX.Core/Resources/legality/wild/Gen6/encounter_x.pkl b/PKHeX.Core/Resources/legality/wild/Gen6/encounter_x.pkl index 92e1b7b0f0d00b801dff3716a95250db0904c9b4..c1cb9fa52f86706d4cf0040b66ecf5a4b36d0841 100644 GIT binary patch delta 412 zcmWO2J5B;o6o>IMATW-T>j6ij7?p?wHK6ek!6!au0q()p*2Ip+g7PHRQ`y?F0Sgvz z7GgnZ=~sNo{rz*Q`#p(flgP61M^$826Ioak$rBM<67f_-gUItTR$Rv`IK!)W4X@)3 z+{BxB3vc5c+`_xKjrVW|ckwl*JitSIiLWpjIq5Yf zBPYGVWaOl`n2enC4wI3Sj_~M(frpdcGmw#!K43C((nm~2PWpt&$my5NxNsAIMATW-T>j6ij7?p?wHK6ek!6!au0q()p*2Ip+g7PHRQ`y?F0Sgvz z7GgnZ=~sNo{rz*Q`#p(flgP61M^$826Ioak$rBM<67f_-gUItTR$Rv`IK!)W4X@)3 z+{BxB3vc5c+`_xKjrVW|ckwl*JitSIiLWpjIq5Yf zBPYGVWaOl`n2enC4wI3Sj_~M(frpdcGmw#!K43C((nm~2PWpt&$my5NxNsA diff --git a/PKHeX.Core/Legality/Structures/SimpleTrainerInfo.cs b/PKHeX.Core/Saves/Abstractions/SimpleTrainerInfo.cs similarity index 100% rename from PKHeX.Core/Legality/Structures/SimpleTrainerInfo.cs rename to PKHeX.Core/Saves/Abstractions/SimpleTrainerInfo.cs diff --git a/PKHeX.Core/Saves/SAV1.cs b/PKHeX.Core/Saves/SAV1.cs index edb94860b..653ff0e32 100644 --- a/PKHeX.Core/Saves/SAV1.cs +++ b/PKHeX.Core/Saves/SAV1.cs @@ -17,6 +17,7 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray public string SaveRevisionString => (Japanese ? "J" : "U") + (IsVirtualConsole ? "VC" : "GB"); public bool Japanese { get; } public bool Korean => false; + public override int Language => Japanese ? 1 : -1; public override PersonalTable1 Personal { get; } diff --git a/PKHeX.Core/Saves/SAV2.cs b/PKHeX.Core/Saves/SAV2.cs index 8ed6ad68d..ad90f40d6 100644 --- a/PKHeX.Core/Saves/SAV2.cs +++ b/PKHeX.Core/Saves/SAV2.cs @@ -17,6 +17,7 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo public string SaveRevisionString => (Japanese ? "J" : !Korean ? "U" : "K") + (IsVirtualConsole ? "VC" : "GB"); public bool Japanese { get; } public bool Korean { get; } + public override int Language => Japanese ? 1 : Korean ? (int)LanguageID.Korean : -1; public override PersonalTable2 Personal { get; } public override ReadOnlySpan HeldItems => Legal.HeldItems_GSC; diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs index 0d442c06c..32073406d 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs @@ -8,7 +8,7 @@ namespace PKHeX.Core; /// /// Go Park Entity transferred from to . /// -public sealed class GP1 : IEncounterInfo, IFixedAbilityNumber, IScaledSizeReadOnly +public sealed class GP1 : IEncounterInfo, IFixedAbilityNumber, IScaledSizeReadOnly, IEncounterConvertible { public const int SIZE = 0x1B0; public readonly byte[] Data; @@ -20,8 +20,6 @@ public sealed class GP1 : IEncounterInfo, IFixedAbilityNumber, IScaledSizeReadOn public int Generation => 7; public EntityContext Context => EntityContext.Gen7b; public AbilityPermission Ability => AbilityPermission.Any12; - public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPB7(tr); - public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPB7(tr, criteria); public GP1(byte[] data) => Data = data; public GP1() : this(new byte[SIZE]) => InitializeBlank(Data); @@ -136,9 +134,11 @@ public sealed class GP1 : IEncounterInfo, IFixedAbilityNumber, IScaledSizeReadOn /// public const byte InitialAV = 2; - public PB7 ConvertToPB7(ITrainerInfo sav) => ConvertToPB7(sav, EncounterCriteria.Unrestricted); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr); + PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria); + public PB7 ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); - public PB7 ConvertToPB7(ITrainerInfo sav, EncounterCriteria criteria) + public PB7 ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) { var rnd = Util.Rand; var pk = new PB7 @@ -197,7 +197,6 @@ public sealed class GP1 : IEncounterInfo, IFixedAbilityNumber, IScaledSizeReadOn ILearnSource source = LearnSource7GG.Instance; source.SetEncounterMoves(Species, Form, Level, moves); pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; pk.HeightScalar = HeightScalar; diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan9.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan9.cs index ddf211bd0..081387743 100644 --- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan9.cs +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan9.cs @@ -185,7 +185,6 @@ public sealed class Zukan9 : ZukanBase } } - private static void SetIsFormSeen(PokeDexEntry9SV entry, IGenderDetail pi, byte form, bool seenForm) { entry.SetIsFormSeen(form, seenForm); diff --git a/PKHeX.Core/Saves/Util/SaveUtil.cs b/PKHeX.Core/Saves/Util/SaveUtil.cs index 90f93fc3d..ec400e6fd 100644 --- a/PKHeX.Core/Saves/Util/SaveUtil.cs +++ b/PKHeX.Core/Saves/Util/SaveUtil.cs @@ -773,7 +773,7 @@ public static class SaveUtil // Only set geolocation data for 3DS titles if (sav is IRegionOrigin o) - o.SetDefaultRegionOrigins(); + o.SetDefaultRegionOrigins((int)language); return sav; } diff --git a/PKHeX.Core/Util/ArrayUtil.cs b/PKHeX.Core/Util/ArrayUtil.cs index bfaeb7847..6b7d45904 100644 --- a/PKHeX.Core/Util/ArrayUtil.cs +++ b/PKHeX.Core/Util/ArrayUtil.cs @@ -128,7 +128,7 @@ public static class ArrayUtil arr3.CopyTo(result[ctr..]); return arr; } - + internal static T[] ConcatAll(ReadOnlySpan arr1, ReadOnlySpan arr2, ReadOnlySpan arr3, ReadOnlySpan arr4) { int len = arr1.Length + arr2.Length + arr3.Length + arr4.Length; diff --git a/PKHeX.Core/Util/FileUtil.cs b/PKHeX.Core/Util/FileUtil.cs index aef0f1440..daa360293 100644 --- a/PKHeX.Core/Util/FileUtil.cs +++ b/PKHeX.Core/Util/FileUtil.cs @@ -299,7 +299,7 @@ public static class FileUtil if (!fi.Exists) return null; if (fi.Length == GP1.SIZE && TryGetGP1(File.ReadAllBytes(file), out var gp1)) - return gp1.ConvertToPB7(sav); + return gp1.ConvertToPKM(sav); if (!EntityDetection.IsSizePlausible(fi.Length) && !MysteryGift.IsMysteryGift(fi.Length)) return null; var data = File.ReadAllBytes(file); diff --git a/PKHeX.Core/Util/ValueTypeTypeConverter.cs b/PKHeX.Core/Util/ValueTypeTypeConverter.cs index 5f6af3ba9..ef4b0aa48 100644 --- a/PKHeX.Core/Util/ValueTypeTypeConverter.cs +++ b/PKHeX.Core/Util/ValueTypeTypeConverter.cs @@ -52,9 +52,11 @@ public sealed class TypeConverterU32 : TypeConverter { if (value is not string input) return base.ConvertFrom(context, culture, value); - if (input.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) - input = input[2..]; - return uint.TryParse(input, System.Globalization.NumberStyles.HexNumber, culture, out var result) ? result : 0u; + + var span = input.AsSpan(); + if (span.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + span = span[2..]; + return uint.TryParse(span, System.Globalization.NumberStyles.HexNumber, culture, out var result) ? result : 0u; } } @@ -81,8 +83,9 @@ public sealed class TypeConverterU64 : TypeConverter { if (value is not string input) return base.ConvertFrom(context, culture, value); - if (input.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) - input = input[2..]; - return ulong.TryParse(input, System.Globalization.NumberStyles.HexNumber, culture, out var result) ? result : 0ul; + var span = input.AsSpan(); + if (span.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + span = span[2..]; + return ulong.TryParse(span, System.Globalization.NumberStyles.HexNumber, culture, out var result) ? result : 0ul; } } diff --git a/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs b/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs index f8913e9fa..208e770fa 100644 --- a/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs +++ b/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs @@ -271,9 +271,8 @@ public static class SpriteUtil public static int GetDisplayGender(IEncounterTemplate enc) => enc switch { - EncounterSlotGO g => (int)g.Gender & 1, - EncounterStatic s => Math.Max(0, (int)s.Gender), - EncounterTrade t => Math.Max(0, (int)t.Gender), + IFixedGender { IsFixedGender: true } s => Math.Max(0, (int)s.Gender), + IPogoSlot g => (int)g.Gender & 1, _ => 0, }; diff --git a/PKHeX.Drawing/ImageUtil.cs b/PKHeX.Drawing/ImageUtil.cs index 30cb926cc..924e12491 100644 --- a/PKHeX.Drawing/ImageUtil.cs +++ b/PKHeX.Drawing/ImageUtil.cs @@ -205,7 +205,7 @@ public static class ImageUtil public static void ChangeAllTo(Span data, Color c, int start, int end) { - var arr = MemoryMarshal.Cast(data).Slice(start / 4, (end - start) / 4); + var arr = MemoryMarshal.Cast(data[start..end]); var value = c.ToArgb(); arr.Fill(value); } diff --git a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs index 55ba75656..650f425bf 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs @@ -771,7 +771,6 @@ public sealed partial class PKMEditor : UserControl, IMainEditor var la = new LegalityAnalysis(Entity); tr.SetRecordFlags(moves, la.Info.EvoChainsAllGens.Get(Entity.Context)); } - Entity.HealPP(); FieldsLoaded = false; LoadMoves(Entity); ClickPP(this, EventArgs.Empty); @@ -839,6 +838,8 @@ public sealed partial class PKMEditor : UserControl, IMainEditor int minlvl = EncounterSuggestion.GetLowestLevel(Entity, encounter.LevelMin); if (minlvl == 0) minlvl = level; + if (Entity.Format < 3 && encounter.Encounter is { } x && !x.Version.Contains(GameVersion.C)) + location = 0; if (Entity.CurrentLevel >= minlvl && Entity.Met_Level == level && Entity.Met_Location == location) { @@ -850,7 +851,7 @@ public sealed partial class PKMEditor : UserControl, IMainEditor if (!silent) { - var suggestions = EntitySuggestionUtil.GetMetLocationSuggestionMessage(Entity, level, location, minlvl); + var suggestions = EntitySuggestionUtil.GetMetLocationSuggestionMessage(Entity, level, location, minlvl, encounter.Encounter); if (suggestions.Count <= 1) // no suggestion return false; @@ -871,6 +872,21 @@ public sealed partial class PKMEditor : UserControl, IMainEditor if (Entity is { Gen6: true, WasEgg: true } && ModifyPKM) Entity.SetHatchMemory6(); } + else + { + Entity.Met_Location = location; + TB_MetLevel.Text = encounter.GetSuggestedMetLevel(Entity).ToString(); + CB_MetLocation.SelectedValue = location; + var timeIndex = 0; + if (encounter.Encounter is { } enc && location is < 253 and not 0) + { + if (enc is EncounterSlot2 s2) + timeIndex = s2.GetRandomTime(); + else + timeIndex = Util.Rand.Next(1, 4); + } + CB_MetTimeOfDay.SelectedIndex = timeIndex; + } if (Entity.CurrentLevel < minlvl) TB_Level.Text = minlvl.ToString(); diff --git a/PKHeX.WinForms/Controls/PKM Editor/StatEditor.cs b/PKHeX.WinForms/Controls/PKM Editor/StatEditor.cs index 526a50ad4..60f82f425 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/StatEditor.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/StatEditor.cs @@ -427,7 +427,7 @@ public partial class StatEditor : UserControl else if (ModifierKeys == Keys.Alt) ivs.Clear(); else - Entity.SetRandomIVs(ivs); + Entity.SetRandomIVs(ivs, new LegalityAnalysis(Entity).EncounterMatch is IFlawlessIVCount fc ? fc.FlawlessIVCount : 0); LoadIVs(ivs); if (Entity is IGanbaru g) diff --git a/PKHeX.WinForms/Subforms/SAV_Encounters.cs b/PKHeX.WinForms/Subforms/SAV_Encounters.cs index 492561517..868bce3c1 100644 --- a/PKHeX.WinForms/Subforms/SAV_Encounters.cs +++ b/PKHeX.WinForms/Subforms/SAV_Encounters.cs @@ -261,18 +261,20 @@ public partial class SAV_Encounters : Form var comparer = new ReferenceComparer(); results = results.Distinct(comparer); // only distinct objects + static Func IsPresent(TTable pt) where TTable : IPersonalTable => z => + { + if (pt.IsPresentInGame(z.Species, z.Form)) + return true; + return z is IEncounterFormRandom { IsRandomUnspecificForm: true } && pt.IsSpeciesInGame(z.Species); + }; if (Main.Settings.EncounterDb.FilterUnavailableSpecies) { - static bool IsPresentInGameSV(ISpeciesForm pk) => PersonalTable.SV.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameSWSH(ISpeciesForm pk) => PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameBDSP(ISpeciesForm pk) => PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameLA(ISpeciesForm pk) => PersonalTable.LA.IsPresentInGame(pk.Species, pk.Form); results = SAV switch { - SAV9SV => results.Where(IsPresentInGameSV), - SAV8SWSH => results.Where(IsPresentInGameSWSH), - SAV8BS => results.Where(IsPresentInGameBDSP), - SAV8LA => results.Where(IsPresentInGameLA), + SAV9SV s9 => results.Where(IsPresent(s9.Personal)), + SAV8SWSH s8 => results.Where(IsPresent(s8.Personal)), + SAV8BS b8 => results.Where(IsPresent(b8.Personal)), + SAV8LA a8 => results.Where(IsPresent(a8.Personal)), _ => results.Where(z => z.Generation <= 7), }; } diff --git a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs index 5f6da354e..f9b150497 100644 --- a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs +++ b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs @@ -232,22 +232,20 @@ public partial class SAV_MysteryGiftDB : Form System.Media.SystemSounds.Asterisk.Play(); } + private static Func IsPresent(TTable pt) where TTable : IPersonalTable => z => pt.IsPresentInGame(z.Species, z.Form); + private void LoadDatabase() { var db = EncounterEvent.GetAllEvents(); if (Main.Settings.MysteryDb.FilterUnavailableSpecies) { - static bool IsPresentInGameSV(ISpeciesForm pk) => PersonalTable.SV.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameSWSH(ISpeciesForm pk) => PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameBDSP(ISpeciesForm pk) => PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameLA(ISpeciesForm pk) => PersonalTable.LA.IsPresentInGame(pk.Species, pk.Form); db = SAV switch { - SAV9SV => db.Where(IsPresentInGameSV), - SAV8SWSH => db.Where(IsPresentInGameSWSH), - SAV8BS => db.Where(IsPresentInGameBDSP), - SAV8LA => db.Where(IsPresentInGameLA), + SAV9SV s9 => db.Where(IsPresent(s9.Personal)), + SAV8SWSH s8 => db.Where(IsPresent(s8.Personal)), + SAV8BS b8 => db.Where(IsPresent(b8.Personal)), + SAV8LA a8 => db.Where(IsPresent(a8.Personal)), SAV7b => db.Where(z => z is WB7), SAV7 => db.Where(z => z.Generation < 7 || z is WC7), _ => db.Where(z => z.Generation <= SAV.Generation), diff --git a/Tests/PKHeX.Core.Tests/Legality/LegalityTests.cs b/Tests/PKHeX.Core.Tests/Legality/LegalityTests.cs index 4b103a159..45fc98978 100644 --- a/Tests/PKHeX.Core.Tests/Legality/LegalityTests.cs +++ b/Tests/PKHeX.Core.Tests/Legality/LegalityTests.cs @@ -20,9 +20,6 @@ public class LegalityTest if (IsInitialized) return; RibbonStrings.ResetDictionary(GameInfo.Strings.ribbons); - if (EncounterEvent.Initialized) - return; - EncounterEvent.RefreshMGDB(); IsInitialized = true; } } diff --git a/Tests/PKHeX.Core.Tests/Simulator/GeneratorTests.cs b/Tests/PKHeX.Core.Tests/Simulator/GeneratorTests.cs index a6813c4be..c3e07dedd 100644 --- a/Tests/PKHeX.Core.Tests/Simulator/GeneratorTests.cs +++ b/Tests/PKHeX.Core.Tests/Simulator/GeneratorTests.cs @@ -7,12 +7,6 @@ namespace PKHeX.Core.Tests.Simulator; public class GeneratorTests { - static GeneratorTests() - { - if (!EncounterEvent.Initialized) - EncounterEvent.RefreshMGDB(); - } - public static IEnumerable PokemonGenerationTestData() { for (int i = 1; i <= 807; i++) @@ -45,7 +39,7 @@ public class GeneratorTests { const Species species = Species.Haxorus; var pk5 = new PK5 {Species = (int) species}; - var ez = EncounterMovesetGenerator.GenerateEncounters(pk5, pk5.Moves, GameVersion.W2).OfType().First(); + var ez = EncounterMovesetGenerator.GenerateEncounters(pk5, pk5.Moves, GameVersion.W2).OfType().First(); ez.Should().NotBeNull("Shiny Haxorus stationary encounter exists for B2/W2"); var criteria = EncounterCriteria.Unrestricted; diff --git a/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs b/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs index c4f02479e..52994a460 100644 --- a/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs +++ b/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs @@ -7,20 +7,14 @@ namespace PKHeX.Core.Tests.Simulator; public class ShowdownSetTests { - static ShowdownSetTests() - { - if (!EncounterEvent.Initialized) - EncounterEvent.RefreshMGDB(); - } - [Fact] public void SimulatorGetParse() { - foreach (var setstr in Sets) + foreach (ReadOnlySpan setstr in Sets) { - var set = new ShowdownSet(setstr).Text; - var lines = set.Split('\n').Select(z => z.Trim()); - Assert.True(lines.All(setstr.Contains), setstr); + var set = new ShowdownSet(setstr).GetSetLines(); + foreach (var line in set) + setstr.Contains(line, StringComparison.Ordinal).Should().BeTrue($"Line {line} should be in the set {setstr}"); } } @@ -42,7 +36,7 @@ public class ShowdownSetTests Assert.True(pk.Species != set.Species); var la = new LegalityAnalysis(pk); - Assert.True(la.Valid); + la.Valid.Should().BeTrue($"Encounter should have generated legally: {egg} {la.Report()}"); var test = EncounterMovesetGenerator.GenerateEncounters(pk7, info, pk7.Moves).ToList(); for (var i = 0; i < test.Count; i++) @@ -50,7 +44,7 @@ public class ShowdownSetTests var t = test[i]; var convert = t.ConvertToPKM(info); var la2 = new LegalityAnalysis(convert); - la2.Valid.Should().BeTrue($"Encounter {i} should have generated legally: {t}"); + la2.Valid.Should().BeTrue($"Encounter {i} should have generated legally: {t} {la2.Report()}"); } } @@ -182,13 +176,13 @@ public class ShowdownSetTests public void SimulatorParseEncounter(string text) { var set = new ShowdownSet(text); - var pk7 = new PK7 { Species = set.Species, Form = set.Form, Moves = set.Moves, CurrentLevel = set.Level }; + var pk7 = new PK3 { Species = set.Species, Form = set.Form, Moves = set.Moves, CurrentLevel = set.Level }; var encs = EncounterMovesetGenerator.GenerateEncounters(pk7, set.Moves); var tr3 = encs.First(z => z is EncounterTrade3); var pk3 = tr3.ConvertToPKM(new SAV3FRLG()); var la = new LegalityAnalysis(pk3); - la.Valid.Should().BeTrue(); + la.Valid.Should().BeTrue(la.Report()); } [Theory] @@ -250,7 +244,6 @@ Adamant Nature IVs: 0 Atk EVs: 252 HP / 252 SpA / 4 SpD Ability: Ice Body -Level: 100 Shiny: Yes Modest Nature - Blizzard