Compare commits

...

93 Commits

Author SHA1 Message Date
BtEtta
c2bb4188e5
Add a movement threshold for initiating drag & drop operations (#4794) 2026-04-26 16:49:49 -05:00
BtEtta
906cae24e8
Fix/optimisation pass on all PNG images (#4788)
Closes #4785
2026-04-25 12:22:16 -05:00
Coki628
2ff7ad40d9
Gen2 HP Up item name fix (#4787)
* Update text_ItemsG2_ja.txt
`ポイントアップ` means PP Up in Japanese, not HP Up.
HP Up is `マックスアップ`.

Updated unit tests to ensure all gen1-3 string lists are unique item names.
2026-04-25 12:16:54 -05:00
Kurt
cc7708e9d4 Misc fixes
Apply relearn moves when static8=>pk8 for dlc2 horses
Fix tera import regen; the PKH=>PK9 is correct, this rejuv was just undoing it.
2026-04-24 17:50:17 -05:00
Kurt
3d7a371395 Gen3: encounter legality tweaks
Gen3-5: Egg (breeding) requesting shiny now loops to match criteria.
Gen3: Add evolution nickname bypass for trash byte application
Gen3: Fix japanese OT prefill check from not actually working
Gen3: Add missing Voltorb encounter (was superseded by Wild slot, but aggressive checks since added).
2026-04-22 23:37:11 -05:00
Kurt
8a1c364207 Add threshold for transparent pixel handling
Artwork Pokemon Sprites have artefacts when rendering Sprite Glow due to them having small-value ARGB pixels. PKHeX was originally designed with the assumption of pure pixels, and not blurred edges from downscaling.

Co-Authored-By: abcboy101 <16735361+abcboy101@users.noreply.github.com>

#4785

#4785
2026-04-22 22:59:00 -05:00
Manu
0a1e955007
Handle HOME ZA gifts (#4786)
* Handle PA9 <-> PKH IsAlpha conversion

* IsHomeGift checks

* Read Ribbons, Scale, Height and Weight from HOME cards

* Extended legality checks
2026-04-21 22:31:59 -05:00
Ka-n00b
e3fd457272
Update Translations (#4784)
* Update const_oras_es-419.txt

* Update const_oras_es.txt

* Update flags_oras_es-419.txt

* Update flags_oras_es.txt

* Update flags_oras_ko.txt

* Update flags_sm_es-419.txt

* Update lang_de.txt

* Update lang_en.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_fr.txt

* Update lang_it.txt

* Update lang_ja.txt

* Update lang_ko.txt

* Update lang_zh-Hans.txt

* Update lang_zh-Hant.txt

* Update lang_de.txt

* Update lang_ko.txt

* Update lang_fr.txt

* Update lang_zh-Hant.txt

* Update lang_fr.txt

* Update lang_it.txt

* Update lang_fr.txt

* Update lang_ko.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_es.txt

* Update lang_es-419.txt

* Update lang_it.txt

* Update lang_ko.txt

* Update lang_es-419.txt

* Update lang_zh-Hans.txt

* Update lang_zh-Hant.txt

* Update lang_zh-Hans.txt

* Update lang_zh-Hant.txt

* Update lang_ja.txt

* Update lang_de.txt

* Update lang_de.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_de.txt

* Update lang_ko.txt

* Update lang_de.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_de.txt

* Update lang_es.txt

* Update lang_es-419.txt

* Update legality_es-419.json

* Update legality_es.json

* Update lang_de.txt
2026-04-21 01:17:41 -05:00
Kurt
08178f70fe Fix g1 outsider trade evo message
https://projectpokemon.org/home/forums/topic/67931-bug-report-transferring-haunter-from-red-save-file-to-blue-save-file-giving-error/#comment-300058
2026-04-19 22:08:44 -05:00
Kurt
75e2a7a497 Rearrange tab indexes to follow visual
Closes #4781
2026-04-17 23:10:10 -05:00
Ka-n00b
516cc25d91
Update Translations (#4782)
* Update legality_de.json

* Update lang_de.txt

* Update lang_en.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_fr.txt

* Update lang_it.txt

* Update lang_ja.txt

* Update lang_ko.txt

* Update lang_zh-Hans.txt

* Update lang_zh-Hant.txt

* Update lang_zh-Hant.txt

* Update lang_en.txt

* Update lang_fr.txt

* Update lang_it.txt

* Update lang_de.txt
2026-04-17 22:50:57 -05:00
Joel Suárez
4007eb7ca8
Battle Chateau rank editor for XY trainer data (#4780)
Directly edit Rank and Points
Translated rank titles
2026-04-17 22:50:25 -05:00
Kurt
452c8b5b81 Misc tweaks
SAV9ZA: Fix pc data import
SAV9ZA: More metadata writing for party; only write first `01` for non-basepatch saves.
2026-04-17 22:26:15 -05:00
Kurt
ee79ba2f61 WC9: Permit 255-all hisuian zoroark size
HOME misapplying PLA's size fixing, fun. Seems it was fixed in 4.0.0 (after 3.2.1?) or server side. A last-minute redeem=>deposit was "fixed" to 255, so the full date range of the card is allowed to be mutated.
2026-04-16 00:36:38 -05:00
Lusamine
dfcd86d950 Replace colon in French translation
Colon was previously removed because the label is too long, but it is still too long. Better fix would be to realign the labels (tbd).
2026-04-15 22:00:28 -05:00
Ka-n00b
c58981e365
Update Translations (#4776)
- Updated and revised the encountered Ground Tiles for generation 4 to match up with English in all other languages.
- Update Event Constants for some Spanish and Korean games.
- Additional translation additions, revisions, spacing adjustments, and typo fixes.
2026-04-15 21:49:36 -05:00
Kurt
2f9512e33b Misc tweaks
Fix box export (individual files) not including party data in the bin. Previous behavior would have 00'd party data if not force-calculated. Just calculate party stats if not present for the format. Log Database entity as party format, just to avoid using the Stored format size.
Revise gen2 odd egg declarations to group shiny eggs together, and enforce the Shiny property for object filtering. If IVs are specified, then Shininess property needs to be provided (rather than "random" which is untrue).
Simplify some logic paths for trade1/trade2. Extract some of the nuance of transferred Hiragana Dugtrio so the unit test is xref'd nicely.
2026-04-15 21:49:07 -05:00
ry
234f402beb
update odd egg IVs (#4777) 2026-04-14 15:40:14 -05:00
Kurt
021b93b87f HT lang: allow LATAM iff 9a
also allow WA9 latam lang (get/set was defaulting to english which probably matched anyway)
add a sanity check to TryGetSpecies which was only used by Gen1 with a hardcoded language ID. maybe someone will use it in their own project, might as well prevent an exception.
2026-04-12 10:20:23 -05:00
Kurt
227b9aaf13 Misc transfer regression for x=>SV 2026-04-11 20:16:12 -05:00
Kurt
2ece772735 Misc tweaks for Z-A slot presence
Not really an issue cuz the game sets them to `0` or `1` on startup, but clearing boxes/slots should match runtime behavior. livehex after clearing boxes can result in slots that don't update until the game is rebooted. no more ;)

should probably refactor the API a little better to avoid virtual spaghetti
2026-04-11 19:47:40 -05:00
Kurt
2cb62f7aa4 Update 26.04.11 2026-04-11 18:53:56 -05:00
Kurt
6b8599b6ed Revise PKM editor nickname check for gen4/5
Closes #4773
2026-04-11 18:33:00 -05:00
Kurt
b449867e72 Remove SAV.SetPKM formarg auto-fixing
Circling back to my notes on formarg updating; it's not ideal to update these values automatically, as it's wiping the streak values for the current game, or if carried forward into future games. The user will have to manage them since the legality flagging now handles warning the user. Better than hiding and overwriting values to something the user can't control.
5e7fc6c817
2026-04-11 18:06:08 -05:00
Kurt
9c03ef1f23 Support champions set imports 2026-04-11 18:00:42 -05:00
Kurt
4568392f61 Widen move 16px, extrabyte +4px
Shifted down extrabyte edits a little more for aesthetics.
Move combobox werent as wide as the relearn moves, so +16 to make them match.

update translation resources with latest additions
2026-04-11 10:45:51 -05:00
Kurt
2b4a01512e Add setting to quiet every other sound made
Closes #4774
2026-04-10 00:48:34 -05:00
sora10pls
13110adfd7 Add Pokémon and item icons from Champions 2026-04-08 10:08:14 -04:00
Kurt
2321e764cb Update GameDataPC9.cs 2026-04-08 01:43:01 -05:00
Kurt
298a53130c Refine home's champions data
PC9 in RAM is loosely deserialized, not a sequential blob. Will need to see how the json is stored in the at-rest savedata. Probably won't be something useful to directly integrate into PKHeX since it's online-only.
2026-04-08 00:10:42 -05:00
Ka-n00b
d7284ce590
Update Translations (#4772)
* Update lang_es.txt

* Update lang_es-419.txt

* Update lang_it.txt

* Update lang_fr.txt

* Update lang_de.txt

* Update lang_zh-Hant.txt

* Update lang_ko.txt

* Update lang_ko.txt

* Update lang_ko.txt

* Update lang_ko.txt

* Update lang_it.txt
2026-04-07 01:03:04 -05:00
Kurt
301bf1bec0 Add donut generator
Closes #4669
better late than never
2026-04-06 22:28:30 -05:00
Kurt
fc80208a06 Reattach form(arg) changed events
not sure how they were unhooked
slight unnoticeable tweaks to layout
2026-04-06 14:34:50 -05:00
Kurt
9742ed43da Bump test case for eh4
No structural change besides the first u16 `03` => `04`.
2026-04-06 10:13:49 -05:00
Kurt
0ebf575a03 gen5: fix dex skin footer, fix musical terminator
Closes #4603
Add some filename import length sanitization
2026-04-06 01:04:26 -05:00
Kurt
097ff3a870 Update SAV_Trainer7.cs
Fixes #4770
2026-04-05 23:59:07 -05:00
Kurt
6da88d6232 Reflow pkm editor main & ot tabs as TableLayout
less designer code (-250) so I think this is a win

* Friendship moved to OT/Misc tab since it is much less relevant in current formats; HT now shows the friendship rather than flexing CurrentFriendship.
2026-04-05 23:48:23 -05:00
Kurt
e132a2719a Remove unnecessary legality check text
Eggs no longer yield with event forms, so there's no need to say "this specific event form is not valid" when it's already flagged as such.
2026-04-05 23:44:04 -05:00
Kurt
5e7fc6c817 Rewrite formargument editing control
A fair bit of these SetPKM updaters need to stop touching FormArgument now.
2026-04-05 23:43:17 -05:00
Kurt
245873aa8a Extract formarg verification logic for tri-days 2026-04-05 23:42:31 -05:00
Kurt
f31503ac7c Update handling for HOME=>ZA edge cases 2026-04-04 15:53:27 -05:00
Kurt
8d24b16fe3 Misc tweaks
the final "TODO ZA" and "TODO HOME ZA"
remove champions from blank save selection (would need PC9 docs first)
2026-04-03 01:07:51 -05:00
Kurt
d21bc2ff6d Allow pkh => import 2026-04-03 00:41:09 -05:00
Kurt
7c4b15bffa Disallow affixed ribbons in ZA/LA w/o transfer 2026-04-03 00:11:52 -05:00
Kurt
02eaca9a0c Add initial legality handling for ZA HOME transfer 2026-04-02 23:55:01 -05:00
Ka-n00b
dc2a60f830
Update Spanish Event Flags (#4768)
Some more names, and also 2 small typo fixes in the French UI text adding s at the end of "Solde" for the Pass Powers.
2026-04-02 21:44:04 -05:00
Ka-n00b
d09a425e6a
Update Translations (#4767)
- Translations mostly complete for the UI and legality message strings in Spanish, Korean, and Traditional Chinese,
- Includes the titles for generation 5 Pass Powers, generation 6 O-Powers, as well as officially used terms for things such as Memories, Athlete Points etc.
- Some official nouns used in the Spain's Spanish translation were using a mix of LATAM terms; they've been corrected to the ones used for that version, and LATAM was corrected or retained to what it should use.
- Made some text shorter in some languages to fit the UI labels and buttons.
- A few typo corrections and additional translation text to the German, Spanish, and Korean READMEs.
2026-04-02 12:25:29 -05:00
Kurt
fcdce737a3 Allow LATAM Spanish to backwards transfer
I'll do it if you won't.
Also add handling for core=>ZA transfers, along with this ZA=>core=>side handling.
2026-04-02 12:24:20 -05:00
sora10pls
d9bdbcae68 Update ES-LA met strings to match HOME 4.0.0 2026-04-02 12:53:13 -04:00
Kurt
5f44fb75d4 add champions side-data storage
note lack of training values being stored in home; possibly they're managed server-side in champions. wonder if they'll allow inter-player training transfer?
the 0x10 could be a hash of immutable core data... still have to wait for Champions to release to see what data is filled in, since HOME doesn't seem to manage it?
2026-04-02 08:48:51 -05:00
Kurt
d15526a0be Add initial PKH side struct for ZA
Haven't bothered testing because maintenance; just disassembled the pa9=>pkh import function. There's a side(sizeof=25) assumedly allocated for Champions. Rare chance it's actually for Gen3; can't find the "create" method due to virtualization/indirection in the disassembly.
2026-04-02 01:14:56 -05:00
sora10pls
5567577a26 Add date ranges for HOME 4.0.0 Z-A connectivity gifts 2026-04-01 11:39:51 -04:00
Kurt
e41797d0cb oops
fix bit shift missed in prior commit
2026-03-29 21:30:01 -05:00
Kurt
5b511a8078 Misc champions version side-effects
fixes enc generator crash from bdsp
fixes enc generation yielding gen9 eggs for champions version
adds version text for champions based on whatever bulbapedia currently has for localizations (champions for all except ja)
2026-03-29 16:35:38 -05:00
Kurt
d8481e711b Fix gen3 options bitflags
off by a few bits
Closes #4766
2026-03-29 16:11:18 -05:00
Kurt
aad2839736 Fix party stat decrypt
from refactor
2026-03-26 15:36:14 -05:00
Kurt
49505a4a8c Update EntitySearchControl.cs 2026-03-25 21:58:36 -05:00
Kurt
ecfd8f6748 Misc tweaks
Fix ranch lengths (I wonder if BK4's handling can be adapted to RK4 for these appended-to-entity fields -- I doubt they modified the PK4 struct)
2026-03-25 08:35:46 -05:00
Kurt
5bf1e2cf45
PKM: Reduce allocation via in-place decryption logic (#4764) 2026-03-24 21:31:59 -05:00
Kurt
793525875f Misc tweaks
no functional change

Preview: fix hover card's off-by-one on move index indication.
PB7: Initialize editor's default date-time to a valid value when none present/invalid.
ID32: move to extension block with other methods
PIDVerifier: use xor method from ShinyUtil rather than inlined (can stay inlined elsewhere)
battlepass: revise parens for clarity
2026-03-24 00:31:51 -05:00
Kurt
492aea166e main dragout: track drag operation, keep cursor
drag enter was triggering once the mouse moved, resetting to hand -- not anymore.
2026-03-22 14:16:36 -05:00
Kurt
83071ca7c2 Update SAV_Trainer9a.Designer.cs
Closes #4762
2026-03-22 01:39:09 -05:00
Ka-n00b
6df330e62f
Update Translations and Event Flags (#4761) 2026-03-21 19:37:53 -05:00
间辞
66df00d038
Add files via upload (#4760) 2026-03-21 02:20:20 -05:00
Kurt
4784e2de82 Update ItemStorage3E.cs
Closes #4759
2026-03-21 01:43:10 -05:00
Kurt
a43b6a5d32 Update 26.03.20 2026-03-20 22:10:14 -05:00
Kurt
48938c5e14 Minor clean 2026-03-19 20:35:58 -05:00
Kurt
0e097b1fc6 Minor slot hover performance improvements
Skip repaint on cursor moving the hover window
Cache reference to the slot interaction types and "nothing" slot image
Dispose of slot sprites when updating with a new one
If scrolling box/group, auto-update hover with the newly displayed slot's content instead of hiding
2026-03-19 17:25:06 -05:00
Kurt
56ba92b68b Handle ZA's EOL revision (2)
When throwing arg out of range, pass the value so it's obvious the number
2026-03-19 15:39:07 -05:00
Kurt
3ad376be44 Misc tweaks
Add rejections for shiny criteria for gen8+ generators

Unrelated: use recent c# lang feature for settings property field
2026-03-19 09:38:02 -05:00
Kurt
ca6fbf024c Add new virtual method for pre-applying Nickname
Results in correct trash bytes for user-Nickname'd mons as if they were nicknamed via the in-game menu.
2026-03-19 03:20:42 -05:00
Kurt
3c1e7bdc6c Misc tweaks
Overworld8a: acknowledge Nature request
8U/8N: remove unnecessary auto-mint (not applied anywhere else)
Nature: use extension properties, use the `IsFixed` check throughout codebase
Wallpaper: add PLA default to pasture (not-obvious prior behavior was removed in refactor).
Tests: fix ck3 file with OT trash bytes (now cleared)
Tests: fix pk3 file with OT trash bytes now passes (added 1 trash pattern, future work)
Trash3: initial stubs for default OT trash recognition (one included to pass above ^)
2026-03-18 01:17:17 -05:00
Kurt
b1dbc6a82b Update SAV4.cs 2026-03-17 02:23:03 -05:00
Kurt
94ad477703 Fix rs/e inventory length
Closes #4756
2026-03-16 08:20:56 -05:00
Kurt
42496def98 PKM Editor BK4 don't set BallHGSS 2026-03-16 01:57:21 -05:00
Kurt
8950613422 Add party import for hall of fame 3
Closes #4754
2026-03-15 18:25:13 -05:00
Kurt
2faa1b10b1 Revise translation form scrape
Iterate through all constructor paths
2026-03-14 22:33:44 -05:00
Professor Dirty
ff69f82234
Add CHS translation and correct frlg flags format (#4753) 2026-03-14 22:28:18 -05:00
Kurt
3317a8bfda Add trash view/edit to all Trainer forms
Make PID text entry uppercase across the program
Standardize RivalTrash => RivalNameTrash, same for Rival => RivalName
2026-03-14 22:28:00 -05:00
Kurt
94f3937f2f Refactor: extract gen3 save block structures
Changed: Inventory editor no longer needs to clone the save file on GUI open
Changed: some method signatures have moved from SAV3* to the specific block
Allows the block structures to be used without a SAV3 object
Allows the Inventory editor to open from a blank save file.
2026-03-14 13:40:17 -05:00
Victor Borges
a3e0ed29b4
Fix YlwApricorn and BluApricorn gen 2 sprite IDs (#4752) 2026-03-13 00:39:02 -05:00
Kurt
d3a4ed6ad3 Update PKMEditor.Designer.cs 2026-03-12 01:34:05 -05:00
Kurt
bb363a7a3d Re-flow Stat editor for alignment/showing all text
Some languages have localized these labels to long strings; previously they were truncated (probably frustrating); now, they all show (wrapped text is the best I can do -- better than truncating?)
2026-03-12 01:29:58 -05:00
Professor Dirty
2f95536b13
Update CHS translation of FRLG flags (#4751) 2026-03-11 12:15:47 -05:00
Kurt
301a1e7664 Allow edits for SAV OT trash bytes for gen1-5 2026-03-11 00:15:58 -05:00
Kurt
662c3db7dc Faster box hover preview
Render it manually rather than let controls be goofy with draw calls.
2026-03-09 20:28:43 -05:00
Kurt
5b42ff746d Handle handle leaks on dragdrop cursor icon
Also pass the tooltips to the components container so that they dispose of anything if needed too.
A user had a long-running script/session that drag-dropped a few thousand times, which exhausted the Windows GDI handle limit (10,000 per process).
2026-03-09 12:25:15 -05:00
sora10pls
c7f02bcc20 GO: more tweaks to 30th Anniversary event handling 2026-03-09 13:11:23 -04:00
Kurt
7617f6dfa7 Revise trash check for Japanese nickname
Closes #4750
2026-03-08 23:41:20 -05:00
Kurt
8b08f263e5 Minor tweaks
Remove GameSync/SecureValue from SAV tab (still lives in Block Data)
Remove inaccessible items from FRLG/E key items
2026-03-08 23:40:56 -05:00
Professor Dirty
d827cec5a7
Update Crystal Event Flags translation in Chinese (#4749) 2026-03-07 23:39:32 -06:00
sora10pls
e5e7cc914c Pokémon 30th Anniversary: All Out event handling 2026-03-07 18:44:33 -05:00
Kurt
ff72af52ad Update 26.03.06 2026-03-06 23:15:14 -06:00
7783 changed files with 23229 additions and 16272 deletions

View File

@ -10,7 +10,7 @@ Die folgenden Dateien werden unterstützt:
* Einzelne Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* Wunderkarten (\*.pgt, \*.pcd, \*.pgf, .wc\*), inklusive Konvertierung zu .pk\*
* Import von GO Park Pokémon (\*.gp1) inklusive Konvertierung zu .pb7
* Import von Teams aus entschlüsselten 3DS Battle Videos.
* Import von Teams aus entschlüsselten 3DS Kampfvideos.
* Transfer von einer Generation zur anderen, mit automatischer Umwandlung des Formats.
Alle Daten werden so angezeigt, dass sie bearbeitet und gespeichert werden können.

View File

@ -10,7 +10,7 @@ Soporta los siguientes archivos:
* Archivos de entidades individuales de Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* Archivos de Regalos Misteriosos (\*.pgt, \*.pcd, \*.pgf, .wc\*) incluyendo conversión a .pk\*
* Importar archivos de entidades de GO Park (\*.gp1) incluyendo conversión a .pb7
* Importar equipos desde archivos Decrypted 3DS Battle Videos
* Importar equipos desde archivos de Vídeos de Combate de 3DS desencriptados.
* Pasar de una generación a la siguiente, convirtiendo los archivos en el proceso.
Los datos son visualizados en una forma que permite modificarlos y guardarlos.
@ -42,7 +42,7 @@ La generación de códigos QR de PKHeX es la de [QRCoder](https://github.com/cod
La colección de sprites de Pokémons Shiny de PKHeX fue tomada de [pokesprite](https://github.com/msikma/pokesprite), licenciado bajo [la licencia MIT](https://github.com/msikma/pokesprite/blob/master/LICENSE).
PKHeX's Pokémon Legends: Arceus sprite collection is taken from the [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) project and its abundance of collaborators and contributors.
La colección de sprites de Leyendas Pokémon: Arceus de PKHeX proviene del proyecto [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) y su gran cantidad de colaboradores y contribuyentes.
### IDE

View File

@ -10,7 +10,7 @@ PKHeX(포케헥스)
* 개별 포켓몬 엔티티 파일 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* 이상한소포 파일(\*.pgt, \*.pcd, \*.pgf, .wc\*)을 .pk로 변환하는 기능 포함
* GO 파크 엔티티 가져오기 (\*.gp1) .pb7로 변환 포함
* Decrypted 3DS Battle Videos에서 팀 가져오기
* 복호화된 3DS 배틀비디오에서 팀 가져오기
* 한 세대에서 다른 세대로 이동하면서 그 과정에서 형식이 변환됩니다.
데이터는 편집하고 저장할 수 있는 보기로 표시됩니다.
@ -42,7 +42,7 @@ PKHeX의 QR 코드 생성 코드는 [the MIT license](https://github.com/codebud
PKHeX의 이로치(색이다른) 스프라이트 컬렉션은 [the MIT license](https://github.com/msikma/pokesprite/blob/master/LICENSE)에 따라 라이선스가 부여된 [pokesprite](https://github.com/msikma/pokesprite)에서 가져왔습니다.
PKheX의 Pokémon LEGENDS 아르세우스 스프라이트 컬렉션은 [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) 프로젝트와 수많은 협력자 및 기여자의 도움을 받아 만들어졌습니다.
PKHeX의 Pokémon LEGENDS 아르세우스 스프라이트 컬렉션은 [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) 프로젝트와 수많은 협력자 및 기여자의 도움을 받아 만들어졌습니다.
### IDE(통합 개발 환경)

View File

@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>26.02.27</Version>
<Version>26.04.11</Version>
<LangVersion>14</LangVersion>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>

View File

@ -1,3 +1,5 @@
using System;
namespace PKHeX.Core;
/// <summary>
@ -70,4 +72,9 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
/// <see cref="PKM.Moves"/> of the Set entity.
/// </summary>
ushort[] Moves { get; }
/// <summary>
/// Checks if the properties are probably from a Pokémon Champions set.
/// </summary>
bool IsChampions => Level == 50 && !IVs.ContainsAnyExcept(31) && EffortValues.IsChampions(EVs);
}

View File

@ -342,12 +342,12 @@ private bool ParseLineNature(ReadOnlySpan<char> input, ReadOnlySpan<string> natu
return false;
var nature = (Nature)index;
if (!nature.IsFixed())
if (!nature.IsFixed)
{
LogError(NatureUnrecognized, input);
return false;
}
if (Nature != Nature.Random && Nature != nature)
if (Nature.IsFixed && Nature != nature)
{
LogError(NatureAlreadySpecified, input);
return false;
@ -627,7 +627,7 @@ private void AddEVs(List<string> result, in BattleTemplateExportSettings setting
BattleTemplateToken.EVsAppendNature => GetStringStatsNatureAmp(EVs, 0, nameEVs, Nature),
_ => GetStringStats(EVs, 0, nameEVs),
};
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed())
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed)
line += $" ({settings.Localization.Strings.natures[(int)Nature]})";
result.Add(cfg.Push(BattleTemplateToken.EVs, line));
}
@ -1081,7 +1081,7 @@ private bool ParseLineEVs(ReadOnlySpan<char> line, BattleTemplateLocalization lo
return false; // invalid line
}
if (Nature != Nature.Random) // specified in a separate Nature line
if (Nature.IsFixed) // specified in a separate Nature line
LogError(NatureEffortAmpAlreadySpecified, natureName);
else
Nature = (Nature)natureIndex;
@ -1100,7 +1100,7 @@ private bool ParseLineEVs(ReadOnlySpan<char> line, BattleTemplateLocalization lo
result.TreatAmpsAsSpeedNotLast();
var ampNature = AdjustNature(result.Plus, result.Minus);
success &= ampNature;
if (ampNature && currentNature != Nature.Random && currentNature != Nature)
if (ampNature && currentNature.IsFixed && currentNature != Nature)
{
LogError(NatureEffortAmpConflictNature);
Nature = currentNature; // revert to original

View File

@ -205,10 +205,10 @@ public ModifyResult TryModify(TObject entity, IEnumerable<StringInstruction> fil
return result;
}
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(IReadOnlyList<Type> types, int expectedMax)
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(ReadOnlySpan<Type> types, int expectedMax)
{
var result = new Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[types.Count];
for (int i = 0; i < types.Count; i++)
var result = new Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[types.Length];
for (int i = 0; i < types.Length; i++)
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic, expectedMax).GetAlternateLookup<ReadOnlySpan<char>>();
return result;
}

View File

@ -30,8 +30,10 @@ public void SetNickname(string nick)
pk.ClearNickname();
return;
}
pk.IsNicknamed = true;
pk.PrepareNickname();
pk.Nickname = nick;
pk.IsNicknamed = true;
}
/// <summary>
@ -137,7 +139,7 @@ public bool SetUnshiny()
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
public void SetNature(Nature nature)
{
if (!nature.IsFixed())
if (!nature.IsFixed)
nature = 0; // default valid
var format = pk.Format;
@ -193,6 +195,11 @@ public void ApplySetDetails(IBattleTemplate set)
}
else
{
// Champions revises EV behavior to be /8.
// If the user is requesting a Champions-like set import, apply EVs that way.
if (set.IsChampions)
pk.SetEVsChampions(evs);
else
pk.SetEVs(evs);
}
@ -264,6 +271,13 @@ public void ApplySetDetails(IBattleTemplate set)
pk.RefreshChecksum();
}
private void SetEVsChampions(ReadOnlySpan<int> evs)
{
Span<int> final = stackalloc int[6];
EffortValues.ConvertFromChampions(evs, final);
pk.SetEVs(final);
}
/// <summary>
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index &amp; format.
/// </summary>

View File

@ -100,7 +100,13 @@ public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inh
public string Relearn2 => Get(Strings.movelist, Entity.RelearnMove2);
public string Relearn3 => Get(Strings.movelist, Entity.RelearnMove3);
public string Relearn4 => Get(Strings.movelist, Entity.RelearnMove4);
public ushort Checksum => Entity is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(Entity.Data[Entity.SIZE_STORED..]);
public ushort Checksum => Entity switch
{
ISanityChecksum s => s.Checksum,
PK1 gb => gb.GetSingleListChecksum(),
PK2 gb => gb.GetSingleListChecksum(),
_ => Checksums.CRC16_CCITT(Entity.Data[..Entity.SIZE_STORED]),
};
public int Friendship => Entity.OriginalTrainerFriendship;
public int EggYear => Entity.EggMetDate.GetValueOrDefault().Year;
public int EggMonth => Entity.EggMetDate.GetValueOrDefault().Month;

View File

@ -40,7 +40,8 @@ public static string GetMessage(PKM pk)
return GetMessage(pk7);
var server = GetExploitURLPrefixPKM(pk.Format);
var data = pk.EncryptedBoxData;
Span<byte> data = stackalloc byte[pk.SIZE_STORED];
pk.WriteEncryptedDataStored(data);
return GetMessageBase64(data, server);
}

View File

@ -17,6 +17,9 @@ public sealed class AdvancedSettings
[LocalizedDescription("Hide event variable names for that contain any of the comma-separated substrings below. Removes event values from the GUI that the user doesn't care to view.")]
public string HideEvent8Contains { get; set; } = string.Empty;
[LocalizedDescription("Minimum distance threshold that mouse movement must exceed before a drag operation is started from a slot.")]
public int DragStartThreshold { get; set; } = 0;
[Browsable(false)]
public string[] GetExclusionList8() => Array.ConvertAll(HideEvent8Contains.Split(',', StringSplitOptions.RemoveEmptyEntries), z => z.Trim());
}

View File

@ -4,6 +4,10 @@ public sealed class SoundSettings
{
[LocalizedDescription("Play Sound when loading a new Save File")]
public bool PlaySoundSAVLoad { get; set; } = true;
[LocalizedDescription("Play Sound when popping up Legality Report")]
public bool PlaySoundLegalityCheck { get; set; } = true;
[LocalizedDescription("Play Sound when performing any other action that would be reasonable to sound alert.")]
public bool PlaySoundOther { get; set; } = true;
}

View File

@ -1,21 +1,12 @@
using System;
using System;
namespace PKHeX.Core;
/// <summary>
/// Base class for defining a manipulation of box data.
/// </summary>
public abstract class BoxManipBase : IBoxManip
public abstract record BoxManipBase(BoxManipType Type, Func<SaveFile, bool> Usable) : IBoxManip
{
public BoxManipType Type { get; }
public Func<SaveFile, bool> Usable { get; }
protected BoxManipBase(BoxManipType type, Func<SaveFile, bool> usable)
{
Type = type;
Usable = usable;
}
public abstract string GetPrompt(bool all);
public abstract string GetFail(bool all);
public abstract string GetSuccess(bool all);

View File

@ -5,7 +5,7 @@ namespace PKHeX.Core;
/// <summary>
/// Clears contents of boxes by deleting all that satisfy a criteria.
/// </summary>
public sealed class BoxManipClear(BoxManipType Type, Func<PKM, bool> criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
public sealed record BoxManipClear(BoxManipType Type, Func<PKM, bool> Criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
{
public BoxManipClear(BoxManipType Type, Func<PKM, bool> Criteria) : this(Type, Criteria, _ => true) { }
@ -18,6 +18,6 @@ public override int Execute(SaveFile sav, BoxManipParam param)
var (start, stop, reverse) = param;
return sav.ClearBoxes(start, stop, Method);
bool Method(PKM p) => reverse ^ criteria(p);
bool Method(PKM p) => reverse ^ Criteria(p);
}
}

View File

@ -5,7 +5,7 @@ namespace PKHeX.Core;
/// <summary>
/// Clears contents of boxes by deleting all that satisfy a criteria based on a <see cref="SaveFile"/>.
/// </summary>
public sealed class BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
public sealed record BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> Criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
{
public BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> Criteria) : this(Type, Criteria, _ => true) { }
@ -18,6 +18,6 @@ public override int Execute(SaveFile sav, BoxManipParam param)
var (start, stop, reverse) = param;
return sav.ClearBoxes(start, stop, Method);
bool Method(PKM p) => reverse ^ criteria(p, sav);
bool Method(PKM p) => reverse ^ Criteria(p, sav);
}
}

View File

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// Clears contents of boxes by deleting all but the first duplicate detected.
/// </summary>
/// <typeparam name="T">Base type of the "is duplicate" hash for the duplicate detection.</typeparam>
public sealed class BoxManipClearDuplicate<T> : BoxManipBase
public sealed record BoxManipClearDuplicate<T> : BoxManipBase
{
private readonly HashSet<T> HashSet = [];
private readonly Func<PKM, bool> Criteria;

View File

@ -5,8 +5,8 @@ namespace PKHeX.Core;
/// <summary>
/// Modifies contents of boxes by using an <see cref="Action"/> to change data.
/// </summary>
public sealed class BoxManipModify(BoxManipType type, Action<PKM> Action, Func<SaveFile, bool> Usable)
: BoxManipBase(type, Usable)
public sealed record BoxManipModify(BoxManipType Type, Action<PKM> Action, Func<SaveFile, bool> Usable)
: BoxManipBase(Type, Usable)
{
public BoxManipModify(BoxManipType type, Action<PKM> Action) : this(type, Action, _ => true) { }

View File

@ -5,7 +5,7 @@ namespace PKHeX.Core;
/// <summary>
/// Modifies contents of boxes by using an <see cref="Action"/> (referencing a Save File) to change data.
/// </summary>
public sealed class BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action, Func<SaveFile, bool> Usable)
public sealed record BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action, Func<SaveFile, bool> Usable)
: BoxManipBase(Type, Usable)
{
public BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action) : this(Type, Action, _ => true) { }

View File

@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Sorts contents of boxes by using a Sorter to determine the order.
/// </summary>
public sealed class BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
public sealed record BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
{
public BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter) : this(Type, Sorter, _ => true) { }

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Sorts contents of boxes by using a <see cref="Sorter"/> (referencing a Save File) to determine the order.
/// </summary>
public sealed class BoxManipSortComplex : BoxManipBase
public sealed record BoxManipSortComplex : BoxManipBase
{
private readonly Func<IEnumerable<PKM>, SaveFile, int, IEnumerable<PKM>> Sorter;
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, IEnumerable<PKM>> sorter) : this(type, sorter, _ => true) { }

View File

@ -47,10 +47,10 @@ public sealed class FakeSaveFile : SaveFile
protected override void SetChecksums() { }
public override GameVersion Version { get => GameVersion.R; set { } }
public override Type PKMType => typeof(PK3);
protected override PK3 GetPKM(byte[] data) => BlankPKM;
protected override byte[] DecryptPKM(byte[] data) => data;
protected override PK3 GetPKM(Memory<byte> data) => BlankPKM;
protected override void DecryptPKM(Span<byte> data) { }
public override PK3 BlankPKM => new();
public override EntityContext Context => EntityContext.Gen3;
protected override int SIZE_STORED => 0;
protected override int SIZE_PARTY => 0;
public override int SIZE_STORED => 0;
public override int SIZE_PARTY => 0;
}

View File

@ -86,7 +86,9 @@ public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, B
int count = GetSlotCountForBox(boxSlotCount, box, total);
int ctr = 0;
// Export each slot in the box.
// Export each slot in the box with party stats, to be nice to any external analysis.
bool isPartyFormat = sav.SIZE_BOXSLOT == sav.SIZE_PARTY;
Span<byte> data = stackalloc byte[sav.SIZE_PARTY];
for (int slot = 0; slot < count; slot++)
{
var pk = sav.GetBoxSlotAtIndex(box, slot);
@ -98,7 +100,13 @@ public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, B
var fileName = GetFileName(pk, settings.FileIndexPrefix, namer, box, slot, boxSlotCount);
var fn = Path.Combine(destPath, fileName);
File.WriteAllBytes(fn, pk.DecryptedPartyData);
// Assume that all PKM read for the loop all are the same shape; the if-else will always travel one path.
// We don't have to worry about lingering party data from a previous loop iteration.
if (!isPartyFormat)
pk.ForcePartyData(); // Rather than export all-zero party stats, calculate what they would be.
pk.WriteDecryptedDataParty(data);
File.WriteAllBytes(fn, data);
ctr++;
}
return ctr;

View File

@ -61,11 +61,11 @@ private static List<SlotInfoMisc> GetExtraSlots2(SAV2 sav)
private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
{
if (sav is not SAV3FRLG)
if (sav is not SAV3FRLG frlg)
return None;
return
[
new(sav.LargeBuffer[0x3C98..], 0) {Type = StorageSlotType.Daycare},
new(frlg.LargeBlock.SingleDaycareRoute5, 0) {Type = StorageSlotType.Daycare},
];
}
@ -254,7 +254,7 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
{
const int size = 0x1F0;
var ofs = (i * size) + 8;
var entry = shinyCache.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
var entry = shinyCache.Raw.Slice(ofs, PokeCrypto.SIZE_8PARTY);
if (EntityDetection.IsPresent(entry.Span))
list.Add(new(entry, i, true) { Type = StorageSlotType.Shiny, HideLegality = true }); // no OT info
else
@ -266,7 +266,7 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
{
const int size = 0x1A8;
var ofs = (i * size) + 8;
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_8PARTY);
if (EntityDetection.IsPresent(entry.Span))
list.Add(new(entry, i, true, Mutable: true) { Type = StorageSlotType.Scripted });
else

View File

@ -55,7 +55,7 @@ public static class NatureUtil
/// Checks if the provided <see cref="value"/> is a valid stored <see cref="Nature"/> value.
/// </summary>
/// <returns>True if value is an actual nature.</returns>
public bool IsFixed() => value < Nature.Random;
public bool IsFixed => value != Nature.Random;
/// <summary>
/// Checks if the provided <see cref="value"/> is a possible mint nature.
@ -63,12 +63,12 @@ public static class NatureUtil
/// <remarks>
/// The only valid mint natures are those which have a stat amp applied, or neutral nature being Serious.
/// </remarks>
public bool IsMint() => (value.IsFixed() && (byte)value % 6 != 0) || value == Nature.Serious;
public bool IsMint => (value.IsFixed && (byte)value % 6 != 0) || value == Nature.Serious;
/// <summary>
/// Checks if the provided <see cref="value"/> is a neutral nature which has no stat amps applied.
/// </summary>
public bool IsNeutral() => value.IsFixed() && (byte)value % 6 == 0;
public bool IsNeutral => value.IsFixed && (byte)value % 6 == 0;
/// <summary>
/// Converts the provided <see cref="value"/> to a neutral nature.

View File

@ -100,6 +100,7 @@ public GameDataSource(GameStrings s)
/// <remarks>Most recent games are at the top, loosely following Generation groups.</remarks>
private static ReadOnlySpan<byte> OrderedVersionArray =>
[
53, // Champions
52, // 9 Z-A
50, 51, // 9 S/V
47, // 8 PLA

View File

@ -448,6 +448,10 @@ private void SanitizeMetLocations()
Gen6.Met4[35] += " (-)";
Gen7.Met4[38] += " (-)";
Gen7b.Met4[27] += " (-)";
// only a duplicate in LATAM Spanish
if (Language is SpanishL)
Gen5.Met4[47] += " (-)";
}
if (Language is Korean)

View File

@ -146,7 +146,7 @@ private EntityContext GetContextInternal()
BD or SP => EntityContext.Gen8b,
SW or SH => EntityContext.Gen8,
SL or VL => EntityContext.Gen9,
ZA => EntityContext.Gen9a,
ZA or CP => EntityContext.Gen9a,
_ => 0
};
@ -248,7 +248,7 @@ public bool Contains(GameVersion g2)
Gen8 => version1 is SW or SH or BD or SP or SWSH or BDSP or PLA,
SV => version1 is SL or VL,
Gen9 => version1 is SL or VL or SV or ZA,
Gen9 => version1 is SL or VL or SV or ZA or CP,
_ => false,
};

View File

@ -6,8 +6,6 @@ namespace PKHeX.Core;
public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
{
private const int BaseOffset = 0x0498;
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3E.Instance);
public override ItemStorage3E Info => ItemStorage3E.Instance;
@ -21,7 +19,7 @@ public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
new(0x000, 50, 999, info, PCItems),
];
public PlayerBag3E(SAV3E sav) : this(sav.Large[BaseOffset..], sav.SecurityKey) { }
public PlayerBag3E(SAV3E sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey) { }
public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
{
UpdateSecurityKey(security);
@ -29,7 +27,7 @@ public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
}
public override void CopyTo(SaveFile sav) => CopyTo((SAV3E)sav);
public void CopyTo(SAV3E sav) => CopyTo(sav.Large[BaseOffset..]);
public void CopyTo(SAV3E sav) => CopyTo(sav.LargeBlock.Inventory);
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
public override int GetMaxCount(InventoryType type, int itemIndex)

View File

@ -6,7 +6,6 @@ namespace PKHeX.Core;
public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
{
private const int BaseOffset = 0x0298;
public override IItemStorage Info => GetInfo(VC);
private static IItemStorage GetInfo(bool vc) => vc ? ItemStorage3FRLG_VC.Instance : ItemStorage3FRLG.Instance;
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(GetInfo(VC));
@ -21,7 +20,7 @@ public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
new(0x000, 30, 999, info, PCItems),
];
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.Large[BaseOffset..], sav.SecurityKey, sav.IsVirtualConsole) { }
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey, sav.IsVirtualConsole) { }
public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security, bool vc) : this(vc)
{
UpdateSecurityKey(security);
@ -29,7 +28,7 @@ public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security, bool vc) : this(vc
}
public override void CopyTo(SaveFile sav) => CopyTo((SAV3FRLG)sav);
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.Large[BaseOffset..]);
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.LargeBlock.Inventory);
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
public override int GetMaxCount(InventoryType type, int itemIndex)

View File

@ -6,8 +6,6 @@ namespace PKHeX.Core;
public sealed class PlayerBag3RS : PlayerBag
{
private const int BaseOffset = 0x0498;
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3RS.Instance);
public override ItemStorage3RS Info => ItemStorage3RS.Instance;
@ -21,11 +19,11 @@ public sealed class PlayerBag3RS : PlayerBag
new(0x000, 50, 999, info, PCItems),
];
public PlayerBag3RS(SAV3RS sav) : this(sav.Large[BaseOffset..]) { }
public PlayerBag3RS(SAV3RS sav) : this(sav.LargeBlock.Inventory) { }
public PlayerBag3RS(ReadOnlySpan<byte> data) => Pouches.LoadAll(data);
public override void CopyTo(SaveFile sav) => CopyTo((SAV3RS)sav);
public void CopyTo(SAV3RS sav) => CopyTo(sav.Large[BaseOffset..]);
public void CopyTo(SAV3RS sav) => CopyTo(sav.LargeBlock.Inventory);
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
public override int GetMaxCount(InventoryType type, int itemIndex)

View File

@ -13,9 +13,9 @@ public sealed class ItemStorage3E : IItemStorage
public static ReadOnlySpan<ushort> Key =>
[
// R/S
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
// FR/LG
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
370, 371, 372,
// E
375, 376,
];

View File

@ -13,7 +13,7 @@ public sealed class ItemStorage3FRLG : IItemStorage
public static ReadOnlySpan<ushort> Key =>
[
// R/S
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
260, 261, 262, 263, 264, 265,
// FR/LG
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
];

View File

@ -168,7 +168,9 @@ public static InventoryType GetInventoryPouch(ushort itemIndex)
}
public static bool IsMegaStone(ushort item) => MegaStones.Contains(item);
public static bool IsUniqueHeldItem(ushort item) => IsMegaStone(item) || item is (0534 or 0535); // Primal Orbs
public static bool IsUniqueHeldItem(ushort item) => IsMegaStone(item) || IsOrb(item);
public static bool IsOrb(ushort item) => item is (0534 or 0535); // Primal Orbs
public static ushort[] GetAllUniqueHeldItems() => [..MegaStones, 0534, 0535];
/// <summary>

View File

@ -32,6 +32,20 @@ private static void AnalyzeInventory(BulkAnalysis input, SAV9ZA za)
continue;
var item = items.GetItem(stone);
if (ItemStorage9ZA.IsOrb(stone))
{
// Handled via Other items (give, unique), not Mega Stones (loan).
if (item.IsNew) // Not acquired/given by the save file, thus not able to be held.
input.AddLine(slot, Identifier, BulkCheckResult.NoIndex, BulkHeldItemInventoryNotAcquired_0, stone);
else if (seenStones.TryGetValue(stone, out var otherIndex)) // Already given to another slot.
input.AddLine(slot, input.AllData[otherIndex], Identifier, i, index2: otherIndex, BulkHeldItemInventoryMultipleSlots_0, stone);
else // First time seeing this item, all good.
seenStones[stone] = i;
continue;
}
// Mega Stone
if (item.Count == 0) // Not acquired by the save file, thus not able to be held.
input.AddLine(slot, Identifier, BulkCheckResult.NoIndex, BulkHeldItemInventoryNotAcquired_0, stone);
else if (!item.IsHeld) // Not marked as held, so it's still "in the bag" (not given).

View File

@ -95,15 +95,26 @@ internal static class Encounters2
new(202, 15, C) { Location = 016 }, // Wobbuffet @ Goldenrod City (Game Corner)
];
private static IndividualValueSet AllZero => new(00, 00, 00, 00, 00, 00);
private static IndividualValueSet Shiny2 => new(00, 02, 10, 10, 10, 10);
public static readonly EncounterStatic2[] StaticOddEggC =
[
new(172, 05, C) { IsEgg = true, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Pichu
new(173, 05, C) { IsEgg = true, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Cleffa
new(174, 05, C) { IsEgg = true, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Igglybuff
new(236, 05, C) { IsEgg = true, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Tyrogue
new(238, 05, C) { IsEgg = true, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Smoochum
new(239, 05, C) { IsEgg = true, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Elekid
new(240, 05, C) { IsEgg = true, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Magby
new(172, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Pichu
new(173, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Cleffa
new(174, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Igglybuff
new(236, 05, C) { IsEgg = true, Gender = 0, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Tyrogue
new(238, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Smoochum
new(239, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Elekid
new(240, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Magby
new(172, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Pichu
new(173, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Cleffa
new(174, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Igglybuff
new(236, 05, C) { IsEgg = true, Gender = 0, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Shiny Tyrogue
new(238, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Shiny Smoochum
new(239, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Shiny Elekid
new(240, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Shiny Magby
];
internal static readonly EncounterStatic2 CelebiVC = new(251, 30, C) { Location = 014 }; // Celebi @ Ilex Forest (VC)
@ -121,7 +132,7 @@ internal static class Encounters2
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
new(TradeNames, 9, 021, 10, 01001) { Shiny = Shiny.Random }, // Spearow @ Goldenrod City for free
new(TradeNames, 10, 213, 15, 00518) { Shiny = Shiny.Random }, // Shuckle @ Cianwood City for free
];
}

View File

@ -61,6 +61,7 @@ private static EncounterArea3[] GetSwarm([ConstantExpected] string resource, [Le
// Stationary
new(352, 30, RSE) { Location = 034 }, // Kecleon @ Route 119
new(352, 30, RSE) { Location = 035 }, // Kecleon @ Route 120
new(100, 25, RSE) { Location = 062 }, // Voltorb @ New Mauville
// Stationary Lengendary
new(377, 40, RSE) { Location = 082 }, // Regirock @ Desert Ruins

View File

@ -255,5 +255,9 @@ public static class EncounterServerDate
{0102, new(2025, 10, 23, 2026, 02, 01, +2)}, // Slowpoke PokéCenter Gift
{0101, new(2025, 10, 31, 2027, 02, 01)}, // PokéCenter Audino Birthday Gift
{1607, new(2025, 12, 09, 2026, 01, 20)}, // Alpha Charizard
{9031, new(2026, 04, 02)}, // Alpha Chikorita
{9032, new(2026, 04, 02)}, // Alpha Tepig
{9033, new(2026, 04, 02)}, // Alpha Totodile
};
}

View File

@ -112,7 +112,7 @@ public EncounterCriteria()
/// Determines whether a specific Nature is specified in the criteria or if complex nature mutations are allowed.
/// </summary>
/// <returns>><see langword="true"/> if a Nature is specified or complex nature mutations are allowed; otherwise, <see langword="false"/>.</returns>
public bool IsSpecifiedNature() => Nature != Nature.Random || Mutations.IsComplexNature();
public bool IsSpecifiedNature() => Nature.IsFixed || Mutations.IsComplexNature();
/// <summary>
/// Determines whether a level range is specified in the criteria.
@ -126,6 +126,12 @@ public EncounterCriteria()
/// <returns>><see langword="true"/> if an Ability is specified; otherwise, <see langword="false"/>.</returns>
public bool IsSpecifiedAbility() => Ability != Any12H;
/// <summary>
/// Determines whether the shiny value is explicitly specified rather than set to random.
/// </summary>
/// <returns>><see langword="true"/> if a Shiny is specified; otherwise, <see langword="false"/>.</returns>
public bool IsSpecifiedShiny() => Shiny != Shiny.Random;
/// <summary>
/// Determines whether all IVs are specified in the criteria.
/// </summary>
@ -183,6 +189,20 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV)
_ => throw new ArgumentOutOfRangeException(nameof(ability), ability, null),
};
/// <summary>
/// Determines whether the specified shiny properties satisfy the shiny criteria based on the current <see cref="Shiny"/> setting.
/// </summary>
/// <returns>><see langword="true"/> if the index satisfies the shiny criteria; otherwise, <see langword="false"/>.</returns>
public bool IsSatisfiedShiny(uint xor, uint cmp) => Shiny switch
{
Shiny.Random => true,
Shiny.Never => xor > cmp, // not shiny
Shiny.AlwaysSquare => xor == 0, // square shiny
Shiny.AlwaysStar => xor < cmp && xor != 0, // star shiny
Shiny.Always => xor < cmp, // shiny
_ => false, // shouldn't be set
};
/// <summary>
/// Determines whether the specified Nature satisfies the criteria.
/// </summary>
@ -191,7 +211,7 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV)
public bool IsSatisfiedNature(Nature nature)
{
if (Mutations.HasFlag(AllowOnlyNeutralNature))
return nature.IsNeutral();
return nature.IsNeutral;
if (Nature == Nature.Random)
return true;
return nature == Nature || Mutations.HasFlag(CanMintNature);
@ -300,7 +320,7 @@ public Nature GetNature(Nature encValue)
/// </summary>
public Nature GetNature()
{
if (Nature != Nature.Random)
if (Nature.IsFixed)
return Nature;
var result = (Nature)Util.Rand.Next(25);
if (Mutations.HasFlag(AllowOnlyNeutralNature))

View File

@ -64,7 +64,8 @@ public static class EncounterGenerator
9 => version switch
{
GameVersion.ZA => EncounterGenerator9a.Instance,
_ => EncounterGenerator9.Instance,
GameVersion.SL or GameVersion.VL => EncounterGenerator9.Instance,
_ => EncounterGeneratorDummy.Instance, // Champions
},
_ => EncounterGeneratorDummy.Instance,
};

View File

@ -12,6 +12,8 @@ public enum PogoType : byte
// Pokémon captured in the wild.
Wild,
WildLevel20,
WildLevel25,
// Pokémon hatched from Eggs.
Egg,
@ -137,6 +139,8 @@ public static class PogoTypeExtensions
public byte LevelMin => encounterType switch
{
Wild => 1,
WildLevel20 => 20,
WildLevel25 => 25,
Egg => 1,
Egg12km => 8,
Raid => 20,
@ -204,6 +208,8 @@ public static class PogoTypeExtensions
public int MinimumIV => encounterType switch
{
Wild => 0,
WildLevel20 => 0,
WildLevel25 => 0,
RaidMythical => 10,
RaidShadowMythical => 8,
RaidShadowMythicalGOWA => 8,
@ -324,6 +330,6 @@ public bool IsBallValid(Ball ball)
_ => Ball.None, // Poké, Great, Ultra
};
public bool IsSpecialResearch => encounterType is >= SpecialMythical and < TimedMythical;
public bool IsSpecialResearch => encounterType is SpecialResearch or >= SpecialMythical and < TimedMythical;
}
}

View File

@ -48,27 +48,25 @@ public EncounterTrade1(ReadOnlySpan<string[]> names, byte index, ushort species,
LevelMinGSC = levelMinGSC;
}
/// <summary>
/// When transferred to Gen7+ via Bank, the nickname for a Japanese Dugtrio in Hiragana changes from "ぐリお" to "ぐりお".
/// </summary>
public const string HiraganaDugtrio7 = "ぐりお";
private bool IsNicknameValid(PKM pk, ReadOnlySpan<char> nick)
{
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 is "ぐりお";
return nick.SequenceEqual(Nicknames.Span[(int)LanguageID.Japanese]);
}
if (!pk.Japanese)
return DetectLanguage(nick, Nicknames.Span, 2) >= 2;
return GetNicknameIndex(nick) >= 2;
// Converted Japanese strings 1/2->7 can mutate from an exact match.
// Special consideration for Hiragana strings that are transferred: only Dugtrio's nickname changes when transferred to Gen7+.
if (pk.Format > 2 && Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio)
return nick is HiraganaDugtrio7;
// Otherwise, must match the Japanese nickname exactly.
return Nicknames.Span[(int)LanguageID.Japanese].SequenceEqual(nick);
}
private bool IsNicknameAnyMatch(ReadOnlySpan<char> current) => GetNicknameIndex(current) >= 0;
private static bool IsTrainerNameValid(PKM pk)
{
if (pk.Format <= 2)
@ -83,12 +81,12 @@ private static bool IsTrainerNameValid(PKM pk)
return trainer.SequenceEqual(expect);
}
private int GetNicknameIndex(ReadOnlySpan<char> nickname) => GetIndex(nickname, Nicknames.Span);
private static int GetIndex(ReadOnlySpan<char> name, ReadOnlySpan<string> arr)
private static int DetectLanguage(ReadOnlySpan<char> name, ReadOnlySpan<string> arr, int start = 1)
{
for (int i = 0; i < arr.Length; i++)
for (int i = start; i < arr.Length; i++)
{
if (i == (int)LanguageID.UNUSED_6)
continue;
if (name.SequenceEqual(arr[i]))
return i;
}

View File

@ -16,7 +16,6 @@ public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IEncount
public bool IsEgg => false;
public Ball FixedBall => Ball.Poke;
public AbilityPermission Ability => AbilityPermission.OnlyHidden;
public Shiny Shiny => Shiny.Random;
public bool IsShiny => false;
public ushort EggLocation => 0;
public bool IsFixedTrainer => true;
@ -33,6 +32,7 @@ public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IEncount
private readonly ReadOnlyMemory<string> TrainerNames;
private readonly ReadOnlyMemory<string> Nicknames;
public Shiny Shiny { get; init; } = Shiny.Never;
public byte Gender { get; init; }
public byte OTGender { get; init; }
public IndividualValueSet IVs { get; init; }
@ -168,8 +168,8 @@ private int DetectLanguage(PKM pk, ReadOnlySpan<char> trainer, ReadOnlySpan<char
return -1;
return (int)LanguageID.Korean;
}
for (int i = 2; i < TrainerNames.Length; i++)
// Skip languages that are not-transferable to International games.
for (int i = 2; i < (int)LanguageID.Korean; i++)
{
if (i == (int)LanguageID.UNUSED_6)
continue;

View File

@ -79,14 +79,14 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
// Get a random PID that matches gender/nature/ability criteria
var pi = PersonalTable.E[Species];
var gr = pi.Gender;
var pid = GetRandomPID(criteria, gr);
var pid = GetRandomPID(criteria, gr, tr.ID32);
pk.PID = pid;
pk.RefreshAbility((int)(pid % 2));
return pk;
}
private uint GetRandomPID(in EncounterCriteria criteria, byte gr)
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32)
{
var seed = Util.Rand32();
while (true)
@ -109,6 +109,10 @@ private uint GetRandomPID(in EncounterCriteria criteria, byte gr)
if (!Daycare3.IsValidProcPID(pid, Version))
continue; // 0-value PID is invalid
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
if (criteria.Shiny.IsShiny() != shiny)
continue;
return pid;
}
}

View File

@ -84,7 +84,7 @@ public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
// Get a random PID that matches gender/nature/ability criteria
var pi = PersonalTable.HGSS[Species];
var gr = pi.Gender;
var pid = GetRandomPID(criteria, gr, out var gender);
var pid = GetRandomPID(criteria, gr, tr.ID32, out var gender);
pk.PID = pid;
pk.Gender = gender;
pk.RefreshAbility((int)(pid & 1));
@ -92,7 +92,7 @@ public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
return pk;
}
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gender)
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32, out byte gender)
{
var seed = Util.Rand32();
while (true)
@ -115,6 +115,10 @@ private uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gende
// PID is rolled forward upon picking up the egg.
// Not worth skipping 0-value PIDs. Too rare to be worth trying again, since it can be a valid PID.
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
if (criteria.Shiny.IsShiny() != shiny)
continue;
return pid;
}
}

View File

@ -72,7 +72,7 @@ public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
var pi = PersonalTable.B2W2[Species];
var gr = pi.Gender;
var ability = criteria.GetAbilityFromNumber(Ability);
var pid = GetRandomPID(criteria, gr, out var gender);
var pid = GetRandomPID(criteria, gr, tr.ID32, out var gender);
pid = (pid & 0xFFFEFFFFu) | (uint)(ability & 1) << 16; // 0x00000000 or 0x00010000
pk.PID = pid;
pk.Gender = gender;
@ -81,7 +81,7 @@ public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
return pk;
}
private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gender)
private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32, out byte gender)
{
var seed = Util.Rand32();
while (true)
@ -91,6 +91,9 @@ private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byt
gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
continue;
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
if (criteria.Shiny.IsShiny() != shiny)
continue;
return pid;
}
}

View File

@ -155,7 +155,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
return false;
if (Gender != FixedGenderUtil.GenderRandom && Gender != pk.Gender)
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
return true;
}

View File

@ -153,7 +153,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk))
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;

View File

@ -167,7 +167,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
return false;
if (Gender != pk.Gender)
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
return true;
}

View File

@ -145,7 +145,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk))
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;

View File

@ -166,7 +166,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
return false;
if (Gender != pk.Gender)
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
return true;
}

View File

@ -104,6 +104,9 @@ public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
pk.SetMoves(Moves);
else
EncounterUtil.SetEncounterMoves(pk, version, Level);
if (Relearn.HasMoves)
pk.SetRelearnMoves(Relearn);
pk.ResetPartyStats();
return pk;

View File

@ -122,8 +122,6 @@ protected virtual void SetPINGA(PK8 pk, in EncounterCriteria criteria, PersonalI
}
FinishCorrelation(pk, seed);
if (criteria.IsSpecifiedNature() && criteria.Nature != pk.Nature && criteria.Nature.IsMint())
pk.StatNature = criteria.Nature;
}
protected GenerateParam8 GetParam() => GetParam(Info);

View File

@ -57,6 +57,10 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal
{
Span<int> iv = stackalloc int[6];
// Honor a shiny request only at the end; generate as never-shiny to avoid shiny PID rejection in main RNG method.
var isShinyRequested = criteria.Shiny.IsShiny();
var iterCriteria = criteria with { Shiny = ShinyMethod };
int ctr = 0;
var rand = new Xoroshiro128Plus(Util.Rand.Rand64());
var param = GetParam(pi);
@ -64,23 +68,22 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal
const int max = 100_000;
do
{
if (TryApply(pk, seed = rand.Next(), iv, param, criteria))
if (TryApply(pk, seed = rand.Next(), iv, param, iterCriteria))
break;
} while (++ctr < max);
if (ctr == max) // fail
{
if (!TryApply(pk, seed = rand.Next(), iv, param, criteria.WithoutIVs()))
iterCriteria = iterCriteria.WithoutIVs();
if (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria))
{
var tmp = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod };
while (!TryApply(pk, seed = rand.Next(), iv, param, tmp)) { }
iterCriteria = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod };
while (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria)) { }
}
}
FinishCorrelation(pk, seed);
if (criteria.IsSpecifiedNature() && criteria.Nature != pk.Nature && criteria.Nature.IsMint())
pk.StatNature = criteria.Nature;
if (criteria.Shiny.IsShiny())
if (isShinyRequested)
pk.PID = ShinyUtil.GetShinyPID(pk.TID16, pk.SID16, pk.PID, ShinyXor);
}

View File

@ -209,7 +209,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
{
if (!Shiny.IsValid(pk))
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
if (Gender != FixedGenderUtil.GenderRandom && pk.Gender != Gender)
return false;

View File

@ -154,7 +154,7 @@ private void SetPINGA(PK9 pk, in EncounterCriteria criteria, PersonalInfo9SV pi)
if (Gender != FixedGenderUtil.GenderRandom)
pk.Gender = Gender;
if (Nature != Nature.Random)
if (Nature.IsFixed)
pk.Nature = pk.StatNature = Nature;
}
#endregion
@ -178,7 +178,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal))
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
return true;

View File

@ -158,7 +158,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
return false;

View File

@ -135,7 +135,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
return false;

View File

@ -155,7 +155,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
return false;
if (pk.Gender != Gender)
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
return true;
}
@ -192,7 +192,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return false;
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
return false;
if (Nature != Nature.Random && pk.Nature != Nature)
if (Nature.IsFixed && pk.Nature != Nature)
return false;
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
return false;

View File

@ -36,10 +36,10 @@ public static bool GenerateData(PA9 pk, in GenerateParam9a enc, in EncounterCrit
{
var rand = new Xoroshiro128Plus(seed);
pk.EncryptionConstant = (uint)rand.NextInt(uint.MaxValue);
pk.PID = GetAdaptedPID(ref rand, pk, enc);
if (enc.Shiny is Shiny.Random && criteria.Shiny.IsShiny() != pk.IsShiny)
var pid = GetAdaptedPID(ref rand, pk, enc);
if (enc.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(ShinyUtil.GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
Span<int> ivs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET];
if (enc.IVs.IsSpecified)
@ -104,7 +104,7 @@ public static bool GenerateData(PA9 pk, in GenerateParam9a enc, in EncounterCrit
return false;
pk.Gender = gender;
var nature = enc.Nature != Nature.Random ? enc.Nature
var nature = enc.Nature.IsFixed ? enc.Nature
: (Nature)rand.NextInt(25);
// Compromise on Nature -- some are fixed, some are random. If the request wants a specific nature, just mint it.
@ -264,7 +264,7 @@ private static bool IsMatchIVsAndFollowing(PKM pk, in GenerateParam9a enc, Xoros
if (pk.Gender != gender)
return false;
var nature = enc.Nature != Nature.Random ? enc.Nature
var nature = enc.Nature.IsFixed ? enc.Nature
: (Nature)rand.NextInt(25);
if (pk.Nature != nature)
return false;

View File

@ -13,5 +13,5 @@ public interface IFixedNature
/// <summary>
/// Indicates if the nature is a single value (not random).
/// </summary>
bool IsFixedNature => Nature != Nature.Random;
bool IsFixedNature => Nature.IsFixed;
}

View File

@ -15,10 +15,9 @@ public sealed class EvolutionGroupHOME : IEvolutionGroup
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc)
{
return null; // TODO HOME ZA2: Re-enable when we have more info.
// if (pk.Format <= 9 && pk.Context is not EntityContext.Gen9a)
// return null;
// return EvolutionGroupHOME.Instance;
if (pk is { Format: <= 9, Context: not EntityContext.Gen9a })
return null;
return EvolutionGroupHOME2.Instance;
}
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc)
@ -50,7 +49,16 @@ public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk, EvolutionOrigin e
/// </summary>
/// <returns>True if we should check all adjacent evolution sources.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CheckAllAdjacent(PKM pk, EvolutionOrigin enc) => enc.SkipChecks || pk is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker;
private static bool CheckAllAdjacent(PKM pk, EvolutionOrigin enc)
{
if (enc.SkipChecks)
return true;
if (IsOutsideContext(pk.Context))
return true; // transferred through HOME already
return pk is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker;
}
private static bool IsOutsideContext(EntityContext context) => context is not (EntityContext.Gen8 or EntityContext.Gen8a or EntityContext.Gen8b or EntityContext.Gen9);
public int Devolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc)
{

View File

@ -15,15 +15,14 @@ public sealed class EvolutionGroupHOME2 : IEvolutionGroup
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc)
{
return null; // TODO HOME ZA2: Re-enable when we have more info.
// if (enc.Generation > 9 || enc.Context is EntityContext.Gen9a)
// return null;
// return EvolutionGroupHOME.Instance;
if (enc is { Generation: > 9} or { Context: EntityContext.Gen9a })
return null;
return EvolutionGroupHOME.Instance;
}
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc)
{
if (pk.ZA) // TODO HOME ZA2: did they force realign everything and fix their bug?
if (pk.ZA)
{
var table = PersonalTable.ZA;
if (enc.Options.HasFlag(OriginOptions.SkipChecks))
@ -33,8 +32,10 @@ public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk, EvolutionOrigin e
}
// Check if ability was possibly realigned by form change; if not, discard anything that doesn't have the ability.
// If touched by HOME, HOME realigns tracker, so we can't discard in that case.
var index = pk.AbilityNumber >> 1;
if (index is 0 or 1)
if (index is 0 or 1 && pk is not IHomeTrack { HasTracker: true })
{
var didRealignAbility = false;
var ability = pk.Ability;

View File

@ -138,4 +138,22 @@ private static int GetSpeciesIndex(ReadOnlySpan<EvoCriteria> array, ushort speci
}
return -1;
}
public bool HasVisitedExcept(params ReadOnlySpan<EntityContext> context)
{
if (HasVisitedZA && !context.Contains(EntityContext.Gen9a)) return true;
if (HasVisitedGen9 && !context.Contains(EntityContext.Gen9)) return true;
if (HasVisitedBDSP && !context.Contains(EntityContext.Gen8a)) return true;
if (HasVisitedPLA && !context.Contains(EntityContext.Gen8b)) return true;
if (HasVisitedSWSH && !context.Contains(EntityContext.Gen8)) return true;
if (HasVisitedLGPE && !context.Contains(EntityContext.Gen7b)) return true;
if (HasVisitedGen7 && !context.Contains(EntityContext.Gen7)) return true;
if (HasVisitedGen6 && !context.Contains(EntityContext.Gen6)) return true;
if (HasVisitedGen5 && !context.Contains(EntityContext.Gen5)) return true;
if (HasVisitedGen4 && !context.Contains(EntityContext.Gen4)) return true;
if (HasVisitedGen3 && !context.Contains(EntityContext.Gen3)) return true;
if (HasVisitedGen2 && !context.Contains(EntityContext.Gen2)) return true;
if (HasVisitedGen1 && !context.Contains(EntityContext.Gen1)) return true;
return false;
}
}

View File

@ -20,7 +20,8 @@ public static class Legal
internal const int MaxSpeciesID_3 = 386;
internal const int MaxMoveID_3 = 354;
internal const int MaxItemID_3 = 374;
internal const int MaxItemID_3_RS = 346;
internal const int MaxItemID_3_FRLG = 374;
internal const int MaxItemID_3_E = 376;
internal const int MaxItemID_3_COLO = 547;
internal const int MaxItemID_3_XD = 593;
@ -151,7 +152,7 @@ public static class Legal
internal const int MaxAbilityID_9a_MD = MaxAbilityID_9a_IK;
internal const int MaxBallID_9 = (int)Ball.LAOrigin;
internal const GameVersion MaxGameID_HOME = GameVersion.VL; // TODO HOME ZA - Replace with ZA when HOME; if backwards transfer is allowed. If prevented, rename epoch as HOME1.
internal const GameVersion MaxGameID_HOME = GameVersion.VL;
internal const GameVersion MaxGameID_HOME2 = GameVersion.ZA;
internal static readonly ushort[] HeldItems_GSC = ItemStorage2.GetAllHeld();

View File

@ -18,451 +18,446 @@ public sealed class LegalityCheckLocalization
#region General Strings
/// <summary>Default text for indicating validity.</summary>
public string Valid { get; set; } = "Valid.";
public string Valid { get; init; } = "Valid.";
/// <summary>Default text for indicating legality.</summary>
public string Legal { get; set; } = "Legal!";
public string Legal { get; init; } = "Legal!";
/// <summary>Default text for indicating an error has occurred.</summary>
public string Error { get; set; } = "Internal error.";
public string Error { get; init; } = "Internal error.";
/// <summary>Analysis not available for the <see cref="PKM"/></summary>
public string AnalysisUnavailable { get; set; } = "Analysis not available for this Pokémon.";
public string AnalysisUnavailable { get; init; } = "Analysis not available for this Pokémon.";
/// <summary>Format text for exporting a legality check result.</summary>
public string F0_1 { get; set; } = "{0}: {1}";
public string F0_1 { get; init; } = "{0}: {1}";
/// <summary>Severity string for <see cref="Severity.Invalid"/></summary>
public string SInvalid { get; set; } = "Invalid";
public string SInvalid { get; init; } = "Invalid";
/// <summary>Severity string for <see cref="Severity.Fishy"/></summary>
public string SFishy { get; set; } = "Fishy";
public string SFishy { get; init; } = "Fishy";
/// <summary>Severity string for <see cref="Severity.Valid"/></summary>
public string SValid { get; set; } = "Valid";
public string SValid { get; init; } = "Valid";
/// <summary>Severity string for anything not implemented.</summary>
public string NotImplemented { get; set; } = "Not Implemented";
public string NotImplemented { get; init; } = "Not Implemented";
public string AbilityCapsuleUsed { get; set; } = "Ability available with Ability Capsule.";
public string AbilityPatchUsed { get; set; } = "Ability available with Ability Patch.";
public string AbilityPatchRevertUsed { get; set; } = "Ability available with Ability Patch Revert.";
public string AbilityFlag { get; set; } = "Ability matches ability number.";
public string AbilityHiddenFail { get; set; } = "Hidden Ability mismatch for encounter type.";
public string AbilityHiddenUnavailable { get; set; } = "Hidden Ability not available.";
public string AbilityMismatch { get; set; } = "Ability mismatch for encounter.";
public string AbilityMismatch3 { get; set; } = "Ability does not match Generation 3 species ability.";
public string AbilityMismatchFlag { get; set; } = "Ability does not match ability number.";
public string AbilityMismatchGift { get; set; } = "Ability does not match Mystery Gift.";
public string AbilityMismatchPID { get; set; } = "Ability does not match PID.";
public string AbilityUnexpected { get; set; } = "Ability is not valid for species/form.";
public string AbilityCapsuleUsed { get; init; } = "Ability available with Ability Capsule.";
public string AbilityPatchUsed { get; init; } = "Ability available with Ability Patch.";
public string AbilityPatchRevertUsed { get; init; } = "Ability available with Ability Patch Revert.";
public string AbilityFlag { get; init; } = "Ability matches ability number.";
public string AbilityHiddenFail { get; init; } = "Hidden Ability mismatch for encounter type.";
public string AbilityHiddenUnavailable { get; init; } = "Hidden Ability not available.";
public string AbilityMismatch { get; init; } = "Ability mismatch for encounter.";
public string AbilityMismatch3 { get; init; } = "Ability does not match Generation 3 species ability.";
public string AbilityMismatchFlag { get; init; } = "Ability does not match ability number.";
public string AbilityMismatchGift { get; init; } = "Ability does not match Mystery Gift.";
public string AbilityMismatchPID { get; init; } = "Ability does not match PID.";
public string AbilityUnexpected { get; init; } = "Ability is not valid for species/form.";
public string AwakenedCap { get; set; } = "Individual AV cannot be greater than {0}.";
public string AwakenedShouldBeValue { get; set; } = "{1} AV should be greater than {0}.";
public string AwakenedCap { get; init; } = "Individual AV cannot be greater than {0}.";
public string AwakenedShouldBeValue { get; init; } = "{1} AV should be greater than {0}.";
public string BallAbility { get; set; } = "Can't obtain Hidden Ability with Ball.";
public string BallEggCherish { get; set; } = "Can't have Cherish Ball for regular Egg.";
public string BallEggMaster { get; set; } = "Can't have Master Ball for regular Egg.";
public string BallEnc { get; set; } = "Correct ball for encounter type.";
public string BallEncMismatch { get; set; } = "Can't have ball for encounter type.";
public string BallHeavy { get; set; } = "Can't have Heavy Ball for light, low-catch rate species (Gen VII).";
public string BallSpecies { get; set; } = "Can't obtain species in Ball.";
public string BallSpeciesPass { get; set; } = "Ball possible for species.";
public string BallUnavailable { get; set; } = "Ball unobtainable in origin Generation.";
public string BallG4Sinnoh { get; set; } = "Ball value for D/P/Pt (0x83) is not within range.";
public string BallG4Johto { get; set; } = "Extended Ball value for HG/SS (0x86) is not within range.";
public string BallAbility { get; init; } = "Can't obtain Hidden Ability with Ball.";
public string BallEggCherish { get; init; } = "Can't have Cherish Ball for regular Egg.";
public string BallEggMaster { get; init; } = "Can't have Master Ball for regular Egg.";
public string BallEnc { get; init; } = "Correct ball for encounter type.";
public string BallEncMismatch { get; init; } = "Can't have ball for encounter type.";
public string BallHeavy { get; init; } = "Can't have Heavy Ball for light, low-catch rate species (Gen VII).";
public string BallSpecies { get; init; } = "Can't obtain species in Ball.";
public string BallSpeciesPass { get; init; } = "Ball possible for species.";
public string BallUnavailable { get; init; } = "Ball unobtainable in origin Generation.";
public string BallG4Sinnoh { get; init; } = "Ball value for D/P/Pt (0x83) is not within range.";
public string BallG4Johto { get; init; } = "Extended Ball value for HG/SS (0x86) is not within range.";
public string ContestZero { get; set; } = "Contest Stats should be 0.";
public string ContestZeroSheen { get; set; } = "Contest Stat Sheen should be 0.";
public string ContestSheenGEQ_0 { get; set; } = "Contest Stat Sheen should be >= {0}.";
public string ContestSheenLEQ_0 { get; set; } = "Contest Stat Sheen should be <= {0}.";
public string ContestZero { get; init; } = "Contest Stats should be 0.";
public string ContestZeroSheen { get; init; } = "Contest Stat Sheen should be 0.";
public string ContestSheenGEQ_0 { get; init; } = "Contest Stat Sheen should be >= {0}.";
public string ContestSheenLEQ_0 { get; init; } = "Contest Stat Sheen should be <= {0}.";
public string DateCalendarInvalidMet { get; set; } = "Met Date is not a valid calendar date.";
public string DateCalendarInvalidEgg { get; set; } = "Egg Met Date is not a valid calendar date.";
public string DateLocalInvalidDate { get; set; } = "Local Date is outside of console's local time window.";
public string DateLocalInvalidTime { get; set; } = "Local Time is not a valid timestamp.";
public string DateOutsideDistributionWindow { get; set; } = "Met Date is outside of distribution window.";
public string DateCalendarInvalidMet { get; init; } = "Met Date is not a valid calendar date.";
public string DateCalendarInvalidEgg { get; init; } = "Egg Met Date is not a valid calendar date.";
public string DateLocalInvalidDate { get; init; } = "Local Date is outside of console's local time window.";
public string DateLocalInvalidTime { get; init; } = "Local Time is not a valid timestamp.";
public string DateOutsideDistributionWindow { get; init; } = "Met Date is outside of distribution window.";
public string EggContest { get; set; } = "Cannot increase Contest Stats of an Egg.";
public string EggEXP { get; set; } = "Eggs cannot receive experience.";
public string EggFMetLevel_0 { get; set; } = "Invalid Met Level, expected {0}.";
public string EggHatchCycles { get; set; } = "Invalid Egg hatch cycles.";
public string EggLocation { get; set; } = "Able to hatch an Egg at Met Location.";
public string EggLocationInvalid { get; set; } = "Can't hatch an Egg at Met Location.";
public string EggLocationNone { get; set; } = "Invalid Egg Location, expected none.";
public string EggLocationPalPark { get; set; } = "Invalid Met Location, expected Pal Park.";
public string EggLocationTrade { get; set; } = "Able to hatch a traded Egg at Met Location.";
public string EggLocationTradeFail { get; set; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg.";
public string EggMetLocationFail { get; set; } = "Can't obtain Egg from Egg Location.";
public string EggNature { get; set; } = "Eggs cannot have their Stat Nature changed.";
public string EggPP { get; set; } = "Eggs cannot have modified move PP counts.";
public string EggPPUp { get; set; } = "Cannot apply PP Ups to an Egg.";
public string EggRelearnFlags { get; set; } = "Expected no Relearn Move Flags.";
public string EggShinyPokeStar { get; set; } = "Eggs cannot be a Pokéstar Studios star.";
public string EggSpecies { get; set; } = "Can't obtain Egg for this species.";
public string EggUnhatched { get; set; } = "Valid un-hatched Egg.";
public string EggContest { get; init; } = "Cannot increase Contest Stats of an Egg.";
public string EggEXP { get; init; } = "Eggs cannot receive experience.";
public string EggFMetLevel_0 { get; init; } = "Invalid Met Level, expected {0}.";
public string EggHatchCycles { get; init; } = "Invalid Egg hatch cycles.";
public string EggLocation { get; init; } = "Able to hatch an Egg at Met Location.";
public string EggLocationInvalid { get; init; } = "Can't hatch an Egg at Met Location.";
public string EggLocationNone { get; init; } = "Invalid Egg Location, expected none.";
public string EggLocationPalPark { get; init; } = "Invalid Met Location, expected Pal Park.";
public string EggLocationTrade { get; init; } = "Able to hatch a traded Egg at Met Location.";
public string EggLocationTradeFail { get; init; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg.";
public string EggMetLocationFail { get; init; } = "Can't obtain Egg from Egg Location.";
public string EggNature { get; init; } = "Eggs cannot have their Stat Nature changed.";
public string EggPP { get; init; } = "Eggs cannot have modified move PP counts.";
public string EggPPUp { get; init; } = "Cannot apply PP Ups to an Egg.";
public string EggRelearnFlags { get; init; } = "Expected no Relearn Move Flags.";
public string EggShinyPokeStar { get; init; } = "Eggs cannot be a Pokéstar Studios star.";
public string EggSpecies { get; init; } = "Can't obtain Egg for this species.";
public string EggUnhatched { get; init; } = "Valid un-hatched Egg.";
public string EncCondition { get; set; } = "Valid Wild Encounter at location.";
public string EncConditionBadRNGFrame { get; set; } = "Unable to match encounter conditions to a possible RNG frame.";
public string EncConditionBadSpecies { get; set; } = "Species does not exist in origin game.";
public string EncCondition { get; init; } = "Valid Wild Encounter at location.";
public string EncConditionBadRNGFrame { get; init; } = "Unable to match encounter conditions to a possible RNG frame.";
public string EncConditionBadSpecies { get; init; } = "Species does not exist in origin game.";
public string EncGift { get; set; } = "Unable to match a gift Egg encounter from origin game.";
public string EncGiftEggEvent { get; set; } = "Unable to match an event Egg encounter from origin game.";
public string EncGiftIVMismatch { get; set; } = "IVs do not match Mystery Gift Data.";
public string EncGiftNicknamed { get; set; } = "Event gift has been nicknamed.";
public string EncGiftNotFound { get; set; } = "Unable to match to a Mystery Gift in the database.";
public string EncGiftPIDMismatch { get; set; } = "Mystery Gift fixed PID mismatch.";
public string EncGiftShinyMismatch { get; set; } = "Mystery Gift shiny mismatch.";
public string EncGiftVersionNotDistributed { get; set; } = "Mystery Gift cannot be received by this version.";
public string EncGift { get; init; } = "Unable to match a gift Egg encounter from origin game.";
public string EncGiftEggEvent { get; init; } = "Unable to match an event Egg encounter from origin game.";
public string EncGiftIVMismatch { get; init; } = "IVs do not match Mystery Gift Data.";
public string EncGiftNicknamed { get; init; } = "Event gift has been nicknamed.";
public string EncGiftNotFound { get; init; } = "Unable to match to a Mystery Gift in the database.";
public string EncGiftPIDMismatch { get; init; } = "Mystery Gift fixed PID mismatch.";
public string EncGiftShinyMismatch { get; init; } = "Mystery Gift shiny mismatch.";
public string EncGiftVersionNotDistributed { get; init; } = "Mystery Gift cannot be received by this version.";
public string EncInvalid { get; set; } = "Unable to match an encounter from origin game.";
public string EncMasteryInitial { get; set; } = "Initial move mastery flags do not match the encounter's expected state.";
public string EncInvalid { get; init; } = "Unable to match an encounter from origin game.";
public string EncMasteryInitial { get; init; } = "Initial move mastery flags do not match the encounter's expected state.";
public string EncTradeChangedNickname { get; set; } = "In-game Trade Nickname has been altered.";
public string EncTradeChangedOT { get; set; } = "In-game Trade OT has been altered.";
public string EncTradeIndexBad { get; set; } = "In-game Trade invalid index?";
public string EncTradeMatch { get; set; } = "Valid In-game trade.";
public string EncTradeUnchanged { get; set; } = "In-game Trade OT and Nickname have not been altered.";
public string EncTradeChangedNickname { get; init; } = "In-game Trade Nickname has been altered.";
public string EncTradeChangedOT { get; init; } = "In-game Trade OT has been altered.";
public string EncTradeIndexBad { get; init; } = "In-game Trade invalid index?";
public string EncTradeMatch { get; init; } = "Valid In-game trade.";
public string EncTradeUnchanged { get; init; } = "In-game Trade OT and Nickname have not been altered.";
public string EncStaticPIDShiny { get; set; } = "Encounter shiny mismatch.";
public string EncTypeMatch { get; set; } = "Encounter Type matches encounter.";
public string EncTypeMismatch { get; set; } = "Encounter Type does not match encounter.";
public string EncUnreleased { get; set; } = "Unreleased event.";
public string EncUnreleasedEMewJP { get; set; } = "Non japanese Mew from Faraway Island. Unreleased event.";
public string EncStaticPIDShiny { get; init; } = "Encounter shiny mismatch.";
public string EncTypeMatch { get; init; } = "Encounter Type matches encounter.";
public string EncTypeMismatch { get; init; } = "Encounter Type does not match encounter.";
public string EncUnreleased { get; init; } = "Unreleased event.";
public string EncUnreleasedEMewJP { get; init; } = "Non japanese Mew from Faraway Island. Unreleased event.";
public string EReaderAmerica { get; set; } = "American E-Reader Berry in Japanese save file.";
public string EReaderInvalid { get; set; } = "Invalid E-Reader Berry.";
public string EReaderJapan { get; set; } = "Japanese E-Reader Berry in international save file.";
public string EReaderAmerica { get; init; } = "American E-Reader Berry in Japanese save file.";
public string EReaderInvalid { get; init; } = "Invalid E-Reader Berry.";
public string EReaderJapan { get; init; } = "Japanese E-Reader Berry in international save file.";
public string Effort2Remaining { get; set; } = "2 EVs remaining.";
public string EffortAbove252 { get; set; } = "EVs cannot go above 252.";
public string EffortAbove510 { get; set; } = "EV total cannot be above 510.";
public string EffortAllEqual { get; set; } = "EVs are all equal.";
public string EffortCap100 { get; set; } = "Individual EV for a level 100 encounter in Generation 4 cannot be greater than 100.";
public string EffortEgg { get; set; } = "Eggs cannot receive EVs.";
public string EffortShouldBeZero { get; set; } = "Cannot receive EVs.";
public string EffortEXPIncreased { get; set; } = "All EVs are zero, but leveled above Met Level.";
public string EffortUntrainedCap { get; set; } = "Individual EV without changing EXP cannot be greater than {0}.";
public string Effort2Remaining { get; init; } = "2 EVs remaining.";
public string EffortAbove252 { get; init; } = "EVs cannot go above 252.";
public string EffortAbove510 { get; init; } = "EV total cannot be above 510.";
public string EffortAllEqual { get; init; } = "EVs are all equal.";
public string EffortCap100 { get; init; } = "Individual EV for a level 100 encounter in Generation 4 cannot be greater than 100.";
public string EffortEgg { get; init; } = "Eggs cannot receive EVs.";
public string EffortShouldBeZero { get; init; } = "Cannot receive EVs.";
public string EffortEXPIncreased { get; init; } = "All EVs are zero, but leveled above Met Level.";
public string EffortUntrainedCap { get; init; } = "Individual EV without changing EXP cannot be greater than {0}.";
public string EvoInvalid { get; set; } = "Evolution not valid (or level/trade evolution unsatisfied).";
public string EvoTradeReqOutsider { get; set; } = "Outsider {0} should have evolved into {1}.";
public string EvoTradeRequired { get; set; } = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required.";
public string EvoInvalid { get; init; } = "Evolution not valid (or level/trade evolution unsatisfied).";
public string EvoTradeReqOutsider { get; init; } = "Outsider {0} should have evolved into {1}.";
public string EvoTradeRequired { get; init; } = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required.";
public string FatefulGiftMissing { get; set; } = "Fateful Encounter with no matching Encounter. Has the Mystery Gift data been contributed?";
public string FatefulInvalid { get; set; } = "Fateful Encounter should not be checked.";
public string FatefulMissing { get; set; } = "Special In-game Fateful Encounter flag missing.";
public string FatefulMystery { get; set; } = "Mystery Gift Fateful Encounter.";
public string FatefulMysteryMissing { get; set; } = "Mystery Gift Fateful Encounter flag missing.";
public string FatefulGiftMissing { get; init; } = "Fateful Encounter with no matching Encounter. Has the Mystery Gift data been contributed?";
public string FatefulInvalid { get; init; } = "Fateful Encounter should not be checked.";
public string FatefulMissing { get; init; } = "Special In-game Fateful Encounter flag missing.";
public string FatefulMystery { get; init; } = "Mystery Gift Fateful Encounter.";
public string FatefulMysteryMissing { get; init; } = "Mystery Gift Fateful Encounter flag missing.";
public string FavoriteMarkingUnavailable { get; set; } = "Favorite Marking is not available.";
public string FavoriteMarkingUnavailable { get; init; } = "Favorite Marking is not available.";
public string FormArgumentLEQ_0 { get; set; } = "Form argument is too high for current form.";
public string FormArgumentGEQ_0 { get; set; } = "Form argument is too low for current form.";
public string FormArgumentNotAllowed { get; set; } = "Form argument is not allowed for this encounter.";
public string FormArgumentValid { get; set; } = "Form argument is valid.";
public string FormArgumentInvalid { get; set; } = "Form argument is not valid.";
public string FormBattle { get; set; } = "Form cannot exist outside of a battle.";
public string FormEternal { get; set; } = "Valid Eternal Flower encounter.";
public string FormEternalInvalid { get; set; } = "Invalid Eternal Flower encounter.";
public string FormInvalidGame { get; set; } = "Form cannot be obtained in origin game.";
public string FormInvalidNature { get; set; } = "Form cannot have this nature.";
public string FormItem { get; set; } = "Held item matches Form.";
public string FormItemInvalid { get; set; } = "Held item does not match Form.";
public string FormParty { get; set; } = "Form cannot exist outside of Party.";
public string FormPikachuCosplay { get; set; } = "Only Cosplay Pikachu can have this form.";
public string FormPikachuCosplayInvalid { get; set; } = "Cosplay Pikachu cannot have the default form.";
public string FormPikachuEventInvalid { get; set; } = "Event Pikachu cannot have the default form.";
public string FormInvalidExpect_0 { get; set; } = "Form is invalid, expected form index {0}.";
public string FormValid { get; set; } = "Form is Valid.";
public string FormVivillon { get; set; } = "Valid Vivillon pattern.";
public string FormVivillonEventPre { get; set; } = "Event Vivillon pattern on pre-evolution.";
public string FormVivillonInvalid { get; set; } = "Invalid Vivillon pattern.";
public string FormVivillonNonNative { get; set; } = "Non-native Vivillon pattern.";
public string FormArgumentLEQ_0 { get; init; } = "Form argument is too high for current form.";
public string FormArgumentGEQ_0 { get; init; } = "Form argument is too low for current form.";
public string FormArgumentNotAllowed { get; init; } = "Form argument is not allowed for this encounter.";
public string FormArgumentValid { get; init; } = "Form argument is valid.";
public string FormArgumentInvalid { get; init; } = "Form argument is not valid.";
public string FormBattle { get; init; } = "Form cannot exist outside of a battle.";
public string FormInvalidGame { get; init; } = "Form cannot be obtained in origin game.";
public string FormInvalidNature { get; init; } = "Form cannot have this nature.";
public string FormItem { get; init; } = "Held item matches Form.";
public string FormItemInvalid { get; init; } = "Held item does not match Form.";
public string FormParty { get; init; } = "Form cannot exist outside of Party.";
public string FormInvalidExpect_0 { get; init; } = "Form is invalid, expected form index {0}.";
public string FormValid { get; init; } = "Form is Valid.";
public string FormVivillon { get; init; } = "Valid Vivillon pattern.";
public string FormVivillonEventPre { get; init; } = "Event Vivillon pattern on pre-evolution.";
public string FormVivillonInvalid { get; init; } = "Invalid Vivillon pattern.";
public string FormVivillonNonNative { get; init; } = "Non-native Vivillon pattern.";
public string G1CatchRateChain { get; set; } = "Catch rate does not match any species from Pokémon evolution chain.";
public string G1CatchRateEvo { get; set; } = "Catch rate match species without encounters. Expected a preevolution catch rate.";
public string G1CatchRateItem { get; set; } = "Catch rate does not match a valid held item from Generation 2.";
public string G1CatchRateMatchPrevious { get; set; } = "Catch Rate matches a species from Pokémon evolution chain.";
public string G1CatchRateMatchTradeback { get; set; } = "Catch rate matches a valid held item from Generation 2.";
public string G1CatchRateNone { get; set; } = "Catch rate does not match any species from Pokémon evolution chain or any Generation 2 held items.";
public string G1CharNick { get; set; } = "Nickname from Generation 1/2 uses unavailable characters.";
public string G1CharOT { get; set; } = "OT from Generation 1/2 uses unavailable characters.";
public string G1OTGender { get; set; } = "Female OT from Generation 1/2 is invalid.";
public string G1Stadium { get; set; } = "Incorrect Stadium OT.";
public string G1Type1Fail { get; set; } = "Invalid Type A, does not match species type.";
public string G1Type2Fail { get; set; } = "Invalid Type B, does not match species type.";
public string G1TypeMatch1 { get; set; } = "Valid Type A, matches species type.";
public string G1TypeMatch2 { get; set; } = "Valid Type B, matches species type.";
public string G1TypeMatchPorygon { get; set; } = "Porygon with valid Type A and B values.";
public string G1TypePorygonFail { get; set; } = "Porygon with invalid Type A and B values. Does not a match a valid type combination.";
public string G1TypePorygonFail1 { get; set; } = "Porygon with invalid Type A value.";
public string G1TypePorygonFail2 { get; set; } = "Porygon with invalid Type B value.";
public string G2InvalidTileTreeNotFound { get; set; } = "Could not find a tree for Crystal headbutt encounter that matches OTID.";
public string G2TreeID { get; set; } = "Found a tree for Crystal headbutt encounter that matches OTID.";
public string G2OTGender { get; set; } = "OT from Virtual Console games other than Crystal cannot be female.";
public string G1CatchRateChain { get; init; } = "Catch rate does not match any species from Pokémon evolution chain.";
public string G1CatchRateEvo { get; init; } = "Catch rate match species without encounters. Expected a pre-evolution catch rate.";
public string G1CatchRateItem { get; init; } = "Catch rate does not match a valid held item from Generation 2.";
public string G1CatchRateMatchPrevious { get; init; } = "Catch Rate matches a species from Pokémon evolution chain.";
public string G1CatchRateMatchTradeback { get; init; } = "Catch rate matches a valid held item from Generation 2.";
public string G1CatchRateNone { get; init; } = "Catch rate does not match any species from Pokémon evolution chain or any Generation 2 held items.";
public string G1CharNick { get; init; } = "Nickname from Generation 1/2 uses unavailable characters.";
public string G1CharOT { get; init; } = "OT from Generation 1/2 uses unavailable characters.";
public string G1OTGender { get; init; } = "Female OT from Generation 1/2 is invalid.";
public string G1Stadium { get; init; } = "Incorrect Stadium OT.";
public string G1Type1Fail { get; init; } = "Invalid Type A, does not match species type.";
public string G1Type2Fail { get; init; } = "Invalid Type B, does not match species type.";
public string G1TypeMatch1 { get; init; } = "Valid Type A, matches species type.";
public string G1TypeMatch2 { get; init; } = "Valid Type B, matches species type.";
public string G1TypeMatchPorygon { get; init; } = "Porygon with valid Type A and B values.";
public string G1TypePorygonFail { get; init; } = "Porygon with invalid Type A and B values. Does not a match a valid type combination.";
public string G1TypePorygonFail1 { get; init; } = "Porygon with invalid Type A value.";
public string G1TypePorygonFail2 { get; init; } = "Porygon with invalid Type B value.";
public string G2InvalidTileTreeNotFound { get; init; } = "Could not find a tree for Crystal headbutt encounter that matches OTID.";
public string G2TreeID { get; init; } = "Found a tree for Crystal headbutt encounter that matches OTID.";
public string G2OTGender { get; init; } = "OT from Virtual Console games other than Crystal cannot be female.";
public string G3EReader { get; set; } = "Non Japanese Shadow E-reader Pokémon. Unreleased encounter.";
public string G3OTGender { get; set; } = "OT from Colosseum/XD cannot be female.";
public string G4InvalidTileR45Surf { get; set; } = "Johto Route 45 surfing encounter. Unreachable Water tiles.";
public string G4PartnerMoodEgg { get; set; } = "Eggs cannot have an Mood stat value.";
public string G4PartnerMoodZero { get; set; } = "Mood stat value should be zero when not in the player's party.";
public string G4ShinyLeafBitsInvalid { get; set; } = "Shiny Leaf/Crown bits are not valid.";
public string G4ShinyLeafBitsEgg { get; set; } = "Eggs cannot have Shiny Leaf/Crown.";
public string G5IVAll30 { get; set; } = "All IVs of N's Pokémon should be 30.";
public string G5PIDShinyGrotto { get; set; } = "Hidden Grotto captures cannot be shiny.";
public string G5SparkleInvalid { get; set; } = "Special In-game N's Sparkle flag should not be checked.";
public string G5SparkleRequired { get; set; } = "Special In-game N's Sparkle flag missing.";
public string G5PokeStarMustBeZero { get; set; } = "Pokéstar Studios fame must be zero, cannot participate.";
public string G5PokeStarImpossibleValue { get; set; } = "Pokéstar Studios fame value is unreachable.";
public string G7BSocialShouldBe100Spirit { get; set; } = "Spirit should be 100 for Pokémon not in the player's party.";
public string G7BSocialShouldBe100Mood { get; set; } = "Mood should be 100 for Pokémon not in the player's party.";
public string G3EReader { get; init; } = "Non Japanese Shadow E-reader Pokémon. Unreleased encounter.";
public string G3OTGender { get; init; } = "OT from Colosseum/XD cannot be female.";
public string G4InvalidTileR45Surf { get; init; } = "Johto Route 45 surfing encounter. Unreachable Water tiles.";
public string G4PartnerMoodEgg { get; init; } = "Eggs cannot have an Mood stat value.";
public string G4PartnerMoodZero { get; init; } = "Mood stat value should be zero when not in the player's party.";
public string G4ShinyLeafBitsInvalid { get; init; } = "Shiny Leaf/Crown bits are not valid.";
public string G4ShinyLeafBitsEgg { get; init; } = "Eggs cannot have Shiny Leaf/Crown.";
public string G5IVAll30 { get; init; } = "All IVs of N's Pokémon should be 30.";
public string G5PIDShinyGrotto { get; init; } = "Hidden Grotto captures cannot be shiny.";
public string G5SparkleInvalid { get; init; } = "Special In-game N's Sparkle flag should not be checked.";
public string G5SparkleRequired { get; init; } = "Special In-game N's Sparkle flag missing.";
public string G5PokeStarMustBeZero { get; init; } = "Pokéstar Studios fame must be zero, cannot participate.";
public string G5PokeStarImpossibleValue { get; init; } = "Pokéstar Studios fame value is unreachable.";
public string G7BSocialShouldBe100Spirit { get; init; } = "Spirit should be 100 for Pokémon not in the player's party.";
public string G7BSocialShouldBe100Mood { get; init; } = "Mood should be 100 for Pokémon not in the player's party.";
public string GanbaruStatTooHigh { get; set; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus).";
public string GanbaruStatTooHigh { get; init; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus).";
public string GenderInvalidNone { get; set; } = "Genderless Pokémon should not have a gender.";
public string GeoBadOrder { get; set; } = "GeoLocation Memory: Gap/Blank present.";
public string GeoHardwareInvalid { get; set; } = "Geolocation: Country is not in 3DS region.";
public string GeoHardwareRange { get; set; } = "Invalid Console Region.";
public string GeoHardwareValid { get; set; } = "Geolocation: Country is in 3DS region.";
public string GeoMemoryMissing { get; set; } = "GeoLocation Memory: Memories should be present.";
public string GeoNoCountryHT { get; set; } = "GeoLocation Memory: HT Name present but has no previous Country.";
public string GeoNoRegion { get; set; } = "GeoLocation Memory: Region without Country.";
public string GenderInvalidNone { get; init; } = "Genderless Pokémon should not have a gender.";
public string GeoBadOrder { get; init; } = "GeoLocation Memory: Gap/Blank present.";
public string GeoHardwareInvalid { get; init; } = "Geolocation: Country is not in 3DS region.";
public string GeoHardwareRange { get; init; } = "Invalid Console Region.";
public string GeoHardwareValid { get; init; } = "Geolocation: Country is in 3DS region.";
public string GeoMemoryMissing { get; init; } = "GeoLocation Memory: Memories should be present.";
public string GeoNoCountryHT { get; init; } = "GeoLocation Memory: HT Name present but has no previous Country.";
public string GeoNoRegion { get; init; } = "GeoLocation Memory: Region without Country.";
public string HyperTrainLevelGEQ_0 { get; set; } = "Can't Hyper Train a Pokémon that isn't level {0}.";
public string HyperPerfectAll { get; set; } = "Can't Hyper Train a Pokémon with perfect IVs.";
public string HyperPerfectOne { get; set; } = "Can't Hyper Train a perfect IV.";
public string HyperPerfectUnavailable { get; set; } = "Can't Hyper Train any IV(s).";
public string HyperTrainLevelGEQ_0 { get; init; } = "Can't Hyper Train a Pokémon that isn't level {0}.";
public string HyperPerfectAll { get; init; } = "Can't Hyper Train a Pokémon with perfect IVs.";
public string HyperPerfectOne { get; init; } = "Can't Hyper Train a perfect IV.";
public string HyperPerfectUnavailable { get; init; } = "Can't Hyper Train any IV(s).";
public string ItemEgg { get; set; } = "Eggs cannot hold items.";
public string ItemUnreleased { get; set; } = "Held item is unreleased.";
public string ItemEgg { get; init; } = "Eggs cannot hold items.";
public string ItemUnreleased { get; init; } = "Held item is unreleased.";
public string IVAllEqual_0 { get; set; } = "All IVs are {0}.";
public string IVNotCorrect { get; set; } = "IVs do not match encounter requirements.";
public string IVFlawlessCountGEQ_0 { get; set; } = "Should have at least {0} IVs = 31.";
public string IVAllEqual_0 { get; init; } = "All IVs are {0}.";
public string IVNotCorrect { get; init; } = "IVs do not match encounter requirements.";
public string IVFlawlessCountGEQ_0 { get; init; } = "Should have at least {0} IVs = 31.";
public string LevelBoostNotZero { get; set; } = "Level Boost should be zero.";
public string LevelEXPThreshold { get; set; } = "Current experience matches level threshold.";
public string LevelEXPTooHigh { get; set; } = "Current experience exceeds maximum amount for level 100.";
public string LevelMetBelow { get; set; } = "Current level is below met level.";
public string LevelMetGift { get; set; } = "Met Level does not match Mystery Gift level.";
public string LevelMetGiftFail { get; set; } = "Current Level below Mystery Gift level.";
public string LevelMetSane { get; set; } = "Current level is not below met level.";
public string LevelBoostNotZero { get; init; } = "Level Boost should be zero.";
public string LevelEXPThreshold { get; init; } = "Current experience matches level threshold.";
public string LevelEXPTooHigh { get; init; } = "Current experience exceeds maximum amount for level 100.";
public string LevelMetBelow { get; init; } = "Current level is below met level.";
public string LevelMetGift { get; init; } = "Met Level does not match Mystery Gift level.";
public string LevelMetGiftFail { get; init; } = "Current Level below Mystery Gift level.";
public string LevelMetSane { get; init; } = "Current level is not below met level.";
public string MarkValueOutOfRange_0 { get; set; } = "Individual marking at index {0} is not within the allowed value range.";
public string MarkValueShouldBeZero { get; set; } = "Marking flags cannot be set.";
public string MarkValueUnusedBitsPresent { get; set; } = "Marking flags uses bits beyond the accessible range.";
public string MarkValueOutOfRange_0 { get; init; } = "Individual marking at index {0} is not within the allowed value range.";
public string MarkValueShouldBeZero { get; init; } = "Marking flags cannot be set.";
public string MarkValueUnusedBitsPresent { get; init; } = "Marking flags uses bits beyond the accessible range.";
public string MemoryArgBadCatch_H { get; set; } = "{0} Memory: {0} did not catch this.";
public string MemoryArgBadHatch_H { get; set; } = "{0} Memory: {0} did not hatch this.";
public string MemoryArgBadHT { get; set; } = "Memory: Can't have Handling Trainer Memory as Egg.";
public string MemoryArgBadID_H { get; set; } = "{0} Memory: Can't obtain Memory on {0} Version.";
public string MemoryArgBadItem_H1 { get; set; } = "{0} Memory: Species can't hold this item.";
public string MemoryArgBadLocation_H { get; set; } = "{0} Memory: Can't obtain Location on {0} Version.";
public string MemoryArgBadMove_H1 { get; set; } = "{0} Memory: Species can't learn {1}.";
public string MemoryArgBadOTEgg_H { get; set; } = "{0} Memory: Link Trade is not a valid first memory.";
public string MemoryArgBadSpecies_H1 { get; set; } = "{0} Memory: Can't capture species in game.";
public string MemoryArgSpecies_H { get; set; } = "{0} Memory: Species can be captured in game.";
public string MemoryCleared_H { get; set; } = "Memory: Not cleared properly.";
public string MemoryValid_H { get; set; } = "{0} Memory is valid.";
public string MemoryFeelInvalid_H { get; set; } = "{0} Memory: Invalid Feeling.";
public string MemoryHTFlagInvalid { get; set; } = "Untraded: Current handler should not be the Handling Trainer.";
public string MemoryHTGender_0 { get; set; } = "HT Gender invalid: {0}";
public string MemoryHTLanguage { get; set; } = "HT Language is missing.";
public string MemoryArgBadCatch_H { get; init; } = "{0} Memory: {0} did not catch this.";
public string MemoryArgBadHatch_H { get; init; } = "{0} Memory: {0} did not hatch this.";
public string MemoryArgBadHT { get; init; } = "Memory: Can't have Handling Trainer Memory as Egg.";
public string MemoryArgBadID_H { get; init; } = "{0} Memory: Can't obtain Memory on {0} Version.";
public string MemoryArgBadItem_H1 { get; init; } = "{0} Memory: Species can't hold this item.";
public string MemoryArgBadLocation_H { get; init; } = "{0} Memory: Can't obtain Location on {0} Version.";
public string MemoryArgBadMove_H1 { get; init; } = "{0} Memory: Species can't learn {1}.";
public string MemoryArgBadOTEgg_H { get; init; } = "{0} Memory: Link Trade is not a valid first memory.";
public string MemoryArgBadSpecies_H1 { get; init; } = "{0} Memory: Can't capture species in game.";
public string MemoryArgSpecies_H { get; init; } = "{0} Memory: Species can be captured in game.";
public string MemoryCleared_H { get; init; } = "Memory: Not cleared properly.";
public string MemoryValid_H { get; init; } = "{0} Memory is valid.";
public string MemoryFeelInvalid_H { get; init; } = "{0} Memory: Invalid Feeling.";
public string MemoryHTFlagInvalid { get; init; } = "Untraded: Current handler should not be the Handling Trainer.";
public string MemoryHTGender_0 { get; init; } = "HT Gender invalid: {0}";
public string MemoryHTLanguage { get; init; } = "HT Language is missing.";
public string MemoryIndexArgHT { get; set; } = "Should have a HT Memory TextVar value (somewhere).";
public string MemoryIndexFeel_H1 { get; set; } = "{0} Memory: Feeling should be index {1}.";
public string MemoryIndexFeelHTLEQ9 { get; set; } = "Should have a HT Memory Feeling value 0-9.";
public string MemoryIndexID_H1 { get; set; } = "{0} Memory: Should be index {1}.";
public string MemoryIndexIntensity_H1 { get; set; } = "{0} Memory: Intensity should be index {1}.";
public string MemoryIndexIntensityHT1 { get; set; } = "Should have a HT Memory Intensity value (1st).";
public string MemoryIndexIntensityMin_H1 { get; set; } = "{0} Memory: Intensity should be at least {1}.";
public string MemoryIndexLinkHT { get; set; } = "Should have a Link Trade HT Memory.";
public string MemoryIndexVar { get; set; } = "{0} Memory: TextVar should be index {1}.";
public string MemoryMissingHT { get; set; } = "Memory: Handling Trainer Memory missing.";
public string MemoryMissingOT { get; set; } = "Memory: Original Trainer Memory missing.";
public string MemoryIndexArgHT { get; init; } = "Should have a HT Memory TextVar value (somewhere).";
public string MemoryIndexFeel_H1 { get; init; } = "{0} Memory: Feeling should be index {1}.";
public string MemoryIndexFeelHTLEQ9 { get; init; } = "Should have a HT Memory Feeling value 0-9.";
public string MemoryIndexID_H1 { get; init; } = "{0} Memory: Should be index {1}.";
public string MemoryIndexIntensity_H1 { get; init; } = "{0} Memory: Intensity should be index {1}.";
public string MemoryIndexIntensityHT1 { get; init; } = "Should have a HT Memory Intensity value (1st).";
public string MemoryIndexIntensityMin_H1 { get; init; } = "{0} Memory: Intensity should be at least {1}.";
public string MemoryIndexLinkHT { get; init; } = "Should have a Link Trade HT Memory.";
public string MemoryIndexVar { get; init; } = "{0} Memory: TextVar should be index {1}.";
public string MemoryMissingHT { get; init; } = "Memory: Handling Trainer Memory missing.";
public string MemoryMissingOT { get; init; } = "Memory: Original Trainer Memory missing.";
public string MemorySocialZero { get; set; } = "Social Stat should be zero.";
public string MemoryStatSocialLEQ_0 { get; set; } = "Social Stat should be <= {0}";
public string MemorySocialZero { get; init; } = "Social Stat should be zero.";
public string MemoryStatSocialLEQ_0 { get; init; } = "Social Stat should be <= {0}";
public string MemoryStatAffectionHT0 { get; set; } = "Untraded: Handling Trainer Affection should be 0.";
public string MemoryStatAffectionOT0 { get; set; } = "OT Affection should be 0.";
public string MemoryStatFriendshipHT0 { get; set; } = "Untraded: Handling Trainer Friendship should be 0.";
public string MemoryStatFriendshipOTBaseEvent_0 { get; set; } = "Event OT Friendship does not match base friendship ({0}).";
public string MemoryStatAffectionHT0 { get; init; } = "Untraded: Handling Trainer Affection should be 0.";
public string MemoryStatAffectionOT0 { get; init; } = "OT Affection should be 0.";
public string MemoryStatFriendshipHT0 { get; init; } = "Untraded: Handling Trainer Friendship should be 0.";
public string MemoryStatFriendshipOTBaseEvent_0 { get; init; } = "Event OT Friendship does not match base friendship ({0}).";
public string MetDetailTimeOfDay { get; set; } = "Met Time of Day value is not within the expected range.";
public string MoveEvoFCombination_0 { get; set; } = "Moves combinations is not compatible with {0} evolution.";
public string MoveFExpectSingle_0 { get; set; } = "Expected: {0}";
public string MoveKeldeoMismatch { get; set; } = "Keldeo Move/Form mismatch.";
public string MovePPExpectHealed_01 { get; set; } = "Move {0} PP is below the amount expected ({1}).";
public string MovePPTooHigh_01 { get; set; } = "Move {0} PP is above the amount allowed ({1}).";
public string MovePPUpsTooHigh_01 { get; set; } = "Move {0} PP Ups is above the amount allowed ({1}).";
public string MetDetailTimeOfDay { get; init; } = "Met Time of Day value is not within the expected range.";
public string MoveEvoFCombination_0 { get; init; } = "Moves combinations is not compatible with {0} evolution.";
public string MoveFExpectSingle_0 { get; init; } = "Expected: {0}";
public string MoveKeldeoMismatch { get; init; } = "Keldeo Move/Form mismatch.";
public string MovePPExpectHealed_01 { get; init; } = "Move {0} PP is below the amount expected ({1}).";
public string MovePPTooHigh_01 { get; init; } = "Move {0} PP is above the amount allowed ({1}).";
public string MovePPUpsTooHigh_01 { get; init; } = "Move {0} PP Ups is above the amount allowed ({1}).";
public string MoveShopAlphaMoveShouldBeMastered_0 { get; set; } = "Alpha Move should be marked as mastered.";
public string MoveShopAlphaMoveShouldBeOther { get; set; } = "Alpha encounter cannot be found with this Alpha Move.";
public string MoveShopAlphaMoveShouldBeZero { get; set; } = "Only Alphas may have an Alpha Move set.";
public string MoveShopMasterInvalid_0 { get; set; } = "Cannot manually master {0}: not permitted to master.";
public string MoveShopMasterNotLearned_0 { get; set; } = "Cannot manually master {0}: not in possible learned level up moves.";
public string MoveShopPurchaseInvalid_0 { get; set; } = "Cannot purchase {0} from the move shop.";
public string MoveShopAlphaMoveShouldBeMastered_0 { get; init; } = "Alpha Move should be marked as mastered.";
public string MoveShopAlphaMoveShouldBeOther { get; init; } = "Alpha encounter cannot be found with this Alpha Move.";
public string MoveShopAlphaMoveShouldBeZero { get; init; } = "Only Alphas may have an Alpha Move set.";
public string MoveShopMasterInvalid_0 { get; init; } = "Cannot manually master {0}: not permitted to master.";
public string MoveShopMasterNotLearned_0 { get; init; } = "Cannot manually master {0}: not in possible learned level up moves.";
public string MoveShopPurchaseInvalid_0 { get; init; } = "Cannot purchase {0} from the move shop.";
public string MoveTechRecordFlagMissing_0 { get; set; } = "Unexpected Technical Record Learned flag: {0}";
public string MoveTechRecordFlagMissing_0 { get; init; } = "Unexpected Technical Record Learned flag: {0}";
public string NickFlagEggNo { get; set; } = "Egg must be not nicknamed.";
public string NickFlagEggYes { get; set; } = "Egg must be nicknamed.";
public string NickInvalidChar { get; set; } = "Cannot be given this Nickname.";
public string NickLengthLong { get; set; } = "Nickname too long.";
public string NickLengthShort { get; set; } = "Nickname is empty.";
public string NickMatchLanguage { get; set; } = "Nickname matches species name.";
public string NickMatchLanguageEgg { get; set; } = "Egg matches language Egg name.";
public string NickMatchLanguageEggFail { get; set; } = "Egg name does not match language Egg name.";
public string NickMatchLanguageFail { get; set; } = "Nickname does not match species name.";
public string NickMatchLanguageFlag { get; set; } = "Nickname flagged, matches species name.";
public string NickMatchNoOthers { get; set; } = "Nickname does not match another species name.";
public string NickMatchNoOthersFail { get; set; } = "Nickname matches another species name (+language).";
public string NickFlagEggNo { get; init; } = "Egg must be not nicknamed.";
public string NickFlagEggYes { get; init; } = "Egg must be nicknamed.";
public string NickInvalidChar { get; init; } = "Cannot be given this Nickname.";
public string NickLengthLong { get; init; } = "Nickname too long.";
public string NickLengthShort { get; init; } = "Nickname is empty.";
public string NickMatchLanguage { get; init; } = "Nickname matches species name.";
public string NickMatchLanguageEgg { get; init; } = "Egg matches language Egg name.";
public string NickMatchLanguageEggFail { get; init; } = "Egg name does not match language Egg name.";
public string NickMatchLanguageFail { get; init; } = "Nickname does not match species name.";
public string NickMatchLanguageFlag { get; init; } = "Nickname flagged, matches species name.";
public string NickMatchNoOthers { get; init; } = "Nickname does not match another species name.";
public string NickMatchNoOthersFail { get; init; } = "Nickname matches another species name (+language).";
public string OTLanguage { get; set; } = "Language ID should be {0}, not {1}.";
public string OTLong { get; set; } = "OT Name too long.";
public string OTShort { get; set; } = "OT Name too short.";
public string OTSuspicious { get; set; } = "Suspicious Original Trainer details.";
public string OTLanguage { get; init; } = "Language ID should be {0}, not {1}.";
public string OTLong { get; init; } = "OT Name too long.";
public string OTShort { get; init; } = "OT Name too short.";
public string OTSuspicious { get; init; } = "Suspicious Original Trainer details.";
public string OT_IDEqual { get; set; } = "TID16 and SID16 are equal.";
public string OT_IDs0 { get; set; } = "TID16 and SID16 are 0.";
public string OT_SID0 { get; set; } = "SID16 is zero.";
public string OT_SID0Invalid { get; set; } = "SID16 should be 0.";
public string OT_TID0 { get; set; } = "TID16 is zero.";
public string OT_IDInvalid { get; set; } = "TID16 and SID16 combination is not possible.";
public string OT_IDEqual { get; init; } = "TID16 and SID16 are equal.";
public string OT_IDs0 { get; init; } = "TID16 and SID16 are 0.";
public string OT_SID0 { get; init; } = "SID16 is zero.";
public string OT_SID0Invalid { get; init; } = "SID16 should be 0.";
public string OT_TID0 { get; init; } = "TID16 is zero.";
public string OT_IDInvalid { get; init; } = "TID16 and SID16 combination is not possible.";
public string PIDEncryptWurmple { get; set; } = "Wurmple evolution Encryption Constant mismatch.";
public string PIDEncryptZero { get; set; } = "Encryption Constant is not set.";
public string PIDEqualsEC { get; set; } = "Encryption Constant matches PID.";
public string PIDGenderMatch { get; set; } = "Gender matches PID.";
public string PIDGenderMismatch { get; set; } = "PID-Gender mismatch.";
public string PIDNatureMatch { get; set; } = "Nature matches PID.";
public string PIDNatureMismatch { get; set; } = "PID-Nature mismatch.";
public string PIDTypeMismatch { get; set; } = "PID+ correlation does not match what was expected for the Encounter's type.";
public string PIDZero { get; set; } = "PID is not set.";
public string PIDEncryptWurmple { get; init; } = "Wurmple evolution Encryption Constant mismatch.";
public string PIDEncryptZero { get; init; } = "Encryption Constant is not set.";
public string PIDEqualsEC { get; init; } = "Encryption Constant matches PID.";
public string PIDGenderMatch { get; init; } = "Gender matches PID.";
public string PIDGenderMismatch { get; init; } = "PID-Gender mismatch.";
public string PIDNatureMatch { get; init; } = "Nature matches PID.";
public string PIDNatureMismatch { get; init; } = "PID-Nature mismatch.";
public string PIDTypeMismatch { get; init; } = "PID+ correlation does not match what was expected for the Encounter's type.";
public string PIDZero { get; init; } = "PID is not set.";
public string PlusMoveAlphaMissing_0 { get; set; } = "Expected to have mastered the move {0} when encountered as an alpha.";
public string PlusMoveMultipleInvalid { get; set; } = "Multiple Plus Move flags are invalid.";
public string PlusMoveInvalid_0 { get; set; } = "{0} cannot be learned and set as a Plus Move.";
public string PlusMoveSufficientLevelMissing_0 { get; set; } = "Plus Move flag for {0} must be set."; // as the Pokémon's current level is above the level it becomes available for use as a Plus Move.
public string PlusMoveCountInvalid { get; set; } = "Out of range Plus Move flag index is set.";
public string PlusMoveAlphaMissing_0 { get; init; } = "Expected to have mastered the move {0} when encountered as an alpha.";
public string PlusMoveMultipleInvalid { get; init; } = "Multiple Plus Move flags are invalid.";
public string PlusMoveInvalid_0 { get; init; } = "{0} cannot be learned and set as a Plus Move.";
public string PlusMoveSufficientLevelMissing_0 { get; init; } = "Plus Move flag for {0} must be set."; // as the Pokémon's current level is above the level it becomes available for use as a Plus Move.
public string PlusMoveCountInvalid { get; init; } = "Out of range Plus Move flag index is set.";
public string PokerusDaysTooHigh_0 { get; set; } = "Pokérus Days Remaining value is too high; expected <= {0}.";
public string PokerusStrainUnobtainable_0 { get; set; } = "Pokérus Strain {0} cannot be obtained.";
public string PokerusDaysTooHigh_0 { get; init; } = "Pokérus Days Remaining value is too high; expected <= {0}.";
public string PokerusStrainUnobtainable_0 { get; init; } = "Pokérus Strain {0} cannot be obtained.";
public string RibbonAllValid { get; set; } = "All ribbons accounted for.";
public string RibbonEgg { get; set; } = "Can't receive Ribbon(s) as an Egg.";
public string RibbonsInvalid_0 { get; set; } = "Invalid Ribbons: {0}";
public string RibbonsMissing_0 { get; set; } = "Missing Ribbons: {0}";
public string RibbonMarkingInvalid_0 { get; set; } = "Invalid Marking: {0}";
public string RibbonMarkingMissing_0 { get; set; } = "Missing Marking: {0}";
public string RibbonMarkingAffixed_0 { get; set; } = "Invalid Affixed Ribbon/Marking: {0}";
public string RibbonAllValid { get; init; } = "All ribbons accounted for.";
public string RibbonEgg { get; init; } = "Can't receive Ribbon(s) as an Egg.";
public string RibbonsInvalid_0 { get; init; } = "Invalid Ribbons: {0}";
public string RibbonsMissing_0 { get; init; } = "Missing Ribbons: {0}";
public string RibbonMarkingInvalid_0 { get; init; } = "Invalid Marking: {0}";
public string RibbonMarkingMissing_0 { get; init; } = "Missing Marking: {0}";
public string RibbonMarkingAffixed_0 { get; init; } = "Invalid Affixed Ribbon/Marking: {0}";
public string StatDynamaxInvalid { get; set; } = "Dynamax Level is not within the expected range.";
public string StatIncorrectHeight { get; set; } = "Calculated Height does not match stored value.";
public string StatIncorrectWeight { get; set; } = "Calculated Weight does not match stored value.";
public string StatIncorrectHeightValue_0 { get; set; } = "Height should be {0}.";
public string StatIncorrectWeightValue_0 { get; set; } = "Weight should be {0}.";
public string StatIncorrectScaleValue_0 { get; set; } = "Scale should be {0}.";
public string StatInvalidHeightWeight { get; set; } = "Height / Weight values are statistically improbable.";
public string StatIncorrectCP { get; set; } = "Calculated CP does not match stored value.";
public string StatGigantamaxInvalid { get; set; } = "Gigantamax Flag mismatch.";
public string StatGigantamaxValid { get; set; } = "Gigantamax Flag was changed via Max Soup.";
public string StatNatureInvalid { get; set; } = "Stat Nature is not within the expected range.";
public string StatBattleVersionInvalid { get; set; } = "Battle Version is not within the expected range.";
public string StatNobleInvalid { get; set; } = "Noble Flag mismatch.";
public string StatAlphaInvalid { get; set; } = "Alpha Flag mismatch.";
public string StatDynamaxInvalid { get; init; } = "Dynamax Level is not within the expected range.";
public string StatIncorrectHeight { get; init; } = "Calculated Height does not match stored value.";
public string StatIncorrectWeight { get; init; } = "Calculated Weight does not match stored value.";
public string StatIncorrectHeightValue_0 { get; init; } = "Height should be {0}.";
public string StatIncorrectWeightValue_0 { get; init; } = "Weight should be {0}.";
public string StatIncorrectScaleValue_0 { get; init; } = "Scale should be {0}.";
public string StatInvalidHeightWeight { get; init; } = "Height / Weight values are statistically improbable.";
public string StatIncorrectCP { get; init; } = "Calculated CP does not match stored value.";
public string StatGigantamaxInvalid { get; init; } = "Gigantamax Flag mismatch.";
public string StatGigantamaxValid { get; init; } = "Gigantamax Flag was changed via Max Soup.";
public string StatNatureInvalid { get; init; } = "Stat Nature is not within the expected range.";
public string StatBattleVersionInvalid { get; init; } = "Battle Version is not within the expected range.";
public string StatNobleInvalid { get; init; } = "Noble Flag mismatch.";
public string StatAlphaInvalid { get; init; } = "Alpha Flag mismatch.";
public string StoredSourceEgg { get; set; } = "Egg must be in Box or Party.";
public string StoredSlotSourceInvalid_0 { get; set; } = "Invalid Stored Source: {0}";
public string StoredSourceEgg { get; init; } = "Egg must be in Box or Party.";
public string StoredSlotSourceInvalid_0 { get; init; } = "Invalid Stored Source: {0}";
public string SuperComplete { get; set; } = "Super Training complete flag mismatch.";
public string SuperDistro { get; set; } = "Distribution Super Training missions are not released.";
public string SuperEgg { get; set; } = "Can't Super Train an Egg.";
public string SuperNoComplete { get; set; } = "Can't have active Super Training complete flag for origins.";
public string SuperNoUnlocked { get; set; } = "Can't have active Super Training unlocked flag for origins.";
public string SuperUnavailable { get; set; } = "Super Training missions are not available in games visited.";
public string SuperUnused { get; set; } = "Unused Super Training Flag is flagged.";
public string G6SuperTrainEggBag { get; set; } = "Egg cannot use a Training Bag.";
public string G6SuperTrainEggHits { get; set; } = "Eggs cannot hit Training Bags.";
public string G6SuperTrainBagInvalid_0 { get; set; } = "Unrecognized Training Bag ID: {0}";
public string G6SuperTrainBagHitsInvalid_012 { get; set; } = "Training bag cannot have {0} hits; expected value within [{1},{2}].";
public string SuperComplete { get; init; } = "Super Training complete flag mismatch.";
public string SuperDistro { get; init; } = "Distribution Super Training missions are not released.";
public string SuperEgg { get; init; } = "Can't Super Train an Egg.";
public string SuperNoComplete { get; init; } = "Can't have active Super Training complete flag for origins.";
public string SuperNoUnlocked { get; init; } = "Can't have active Super Training unlocked flag for origins.";
public string SuperUnavailable { get; init; } = "Super Training missions are not available in games visited.";
public string SuperUnused { get; init; } = "Unused Super Training Flag is flagged.";
public string G6SuperTrainEggBag { get; init; } = "Egg cannot use a Training Bag.";
public string G6SuperTrainEggHits { get; init; } = "Eggs cannot hit Training Bags.";
public string G6SuperTrainBagInvalid_0 { get; init; } = "Unrecognized Training Bag ID: {0}";
public string G6SuperTrainBagHitsInvalid_012 { get; init; } = "Training bag cannot have {0} hits; expected value within [{1},{2}].";
public string TeraTypeIncorrect { get; set; } = "Tera Type does not match the expected value.";
public string TeraTypeMismatch { get; set; } = "Tera Type does not match either of the default types.";
public string TeraTypeIncorrect { get; init; } = "Tera Type does not match the expected value.";
public string TeraTypeMismatch { get; init; } = "Tera Type does not match either of the default types.";
public string TradeNotAvailable { get; set; } = "Encounter cannot be traded to the active trainer.";
public string TradeNotAvailable { get; init; } = "Encounter cannot be traded to the active trainer.";
public string TrainerIDNoSeed { get; set; } = "Trainer ID is not obtainable from any RNG seed.";
public string TrainerIDNoSeed { get; init; } = "Trainer ID is not obtainable from any RNG seed.";
public string TransferBad { get; set; } = "Incorrectly transferred from previous generation.";
public string TransferBad { get; init; } = "Incorrectly transferred from previous generation.";
public string TransferCurrentHandlerInvalid { get; set; } = "Invalid Current handler value, trainer details for save file expected another value.";
public string TransferEgg { get; set; } = "Can't transfer Eggs between Generations.";
public string TransferEggLocationTransporter { get; set; } = "Invalid Met Location, expected Poké Transfer.";
public string TransferEggMetLevel { get; set; } = "Invalid Met Level for transfer.";
public string TransferEggVersion { get; set; } = "Can't transfer Eggs to this game.";
public string TransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse).";
public string TransferHTFlagRequired { get; set; } = "Current handler cannot be the OT.";
public string TransferHTMismatchName { get; set; } = "Handling trainer does not match the expected trainer name.";
public string TransferHTMismatchGender { get; set; } = "Handling trainer does not match the expected trainer gender.";
public string TransferHTMismatchLanguage { get; set; } = "Handling trainer does not match the expected trainer language.";
public string TransferKoreanGen4 { get; set; } = "Korean Generation 4 games cannot interact with International Generation 4 games.";
public string TransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown.";
public string TransferNotPossible { get; set; } = "Unable to transfer into current format from origin format.";
public string TransferMetLocation { get; set; } = "Invalid Transfer Met Location.";
public string TransferNature { get; set; } = "Invalid Nature for transfer Experience.";
public string TransferObedienceLevel { get; set; } = "Invalid Obedience Level.";
public string TransferPIDECBitFlip { get; set; } = "PID should be equal to EC [with top bit flipped]!";
public string TransferPIDECEquals { get; set; } = "PID should be equal to EC!";
public string TransferPIDECXor { get; set; } = "Encryption Constant matches shinyxored PID.";
public string TransferTrackerMissing { get; set; } = "Pokémon HOME Transfer Tracker is missing.";
public string TransferTrackerShouldBeZero { get; set; } = "Pokémon HOME Transfer Tracker should be 0.";
public string TransferCurrentHandlerInvalid { get; init; } = "Invalid Current handler value, trainer details for save file expected another value.";
public string TransferEgg { get; init; } = "Can't transfer Eggs between Generations.";
public string TransferEggLocationTransporter { get; init; } = "Invalid Met Location, expected Poké Transfer.";
public string TransferEggMetLevel { get; init; } = "Invalid Met Level for transfer.";
public string TransferEggVersion { get; init; } = "Can't transfer Eggs to this game.";
public string TransferFlagIllegal { get; init; } = "Flagged as illegal by the game (glitch abuse).";
public string TransferHTFlagRequired { get; init; } = "Current handler cannot be the OT.";
public string TransferHTMismatchName { get; init; } = "Handling trainer does not match the expected trainer name.";
public string TransferHTMismatchGender { get; init; } = "Handling trainer does not match the expected trainer gender.";
public string TransferHTMismatchLanguage { get; init; } = "Handling trainer does not match the expected trainer language.";
public string TransferKoreanGen4 { get; init; } = "Korean Generation 4 games cannot interact with International Generation 4 games.";
public string TransferMet { get; init; } = "Invalid Met Location, expected Poké Transfer or Crown.";
public string TransferNotPossible { get; init; } = "Unable to transfer into current format from origin format.";
public string TransferMetLocation { get; init; } = "Invalid Transfer Met Location.";
public string TransferNature { get; init; } = "Invalid Nature for transfer Experience.";
public string TransferObedienceLevel { get; init; } = "Invalid Obedience Level.";
public string TransferPIDECBitFlip { get; init; } = "PID should be equal to EC [with top bit flipped]!";
public string TransferPIDECEquals { get; init; } = "PID should be equal to EC!";
public string TransferPIDECXor { get; init; } = "Encryption Constant matches shiny-xor'd PID.";
public string TransferTrackerMissing { get; init; } = "Pokémon HOME Transfer Tracker is missing.";
public string TransferTrackerShouldBeZero { get; init; } = "Pokémon HOME Transfer Tracker should be 0.";
public string TrashBytesExpected { get; set; } = "Expected Trash Bytes.";
public string TrashBytesMismatchInitial { get; set; } = "Expected initial trash bytes to match the encounter.";
public string TrashBytesMissingTerminatorFinal { get; set; } = "Final terminator missing.";
public string TrashBytesShouldBeEmpty { get; set; } = "Trash Bytes should be cleared.";
public string TrashBytesResetViaTransfer { get; set; } = "Trash Bytes were reset via transfer.";
public string TrashBytesExpected { get; init; } = "Expected Trash Bytes.";
public string TrashBytesMismatchInitial { get; init; } = "Expected initial trash bytes to match the encounter.";
public string TrashBytesMissingTerminatorFinal { get; init; } = "Final terminator missing.";
public string TrashBytesShouldBeEmpty { get; init; } = "Trash Bytes should be cleared.";
public string TrashBytesResetViaTransfer { get; init; } = "Trash Bytes were reset via transfer.";
#endregion
public string EncTradeShouldHaveEvolvedToSpecies_0 { get; set; } = "Trade Encounter should have evolved to species: {0}.";
public string EncGiftLanguageNotDistributed { get; set; } = "Gift Encounter was never distributed with this language.";
public string EncGiftRegionNotDistributed { get; set; } = "Gift Encounter was never distributed to this Console Region.";
public string FormInvalidRangeLEQ_0F { get; set; } = "Form Count is out of range. Expected <= {0}, got {1}.";
public string MovesShouldMatchRelearnMoves { get; set; } = "Moves should exactly match Relearn Moves.";
public string MemoryStatEnjoyment_0 { get; set; } = "Enjoyment should be {0}.";
public string MemoryStatFullness_0 { get; set; } = "Fullness should be {0}.";
public string MemoryStatFullnessLEQ_0 { get; set; } = "Fullness should be <= {0}.";
public string OTLanguageShouldBe_0 { get; set; } = "Language ID should be {0}, not {1}.";
public string OTLanguageShouldBe_0or1 { get; set; } = "Language ID should be {0} or {1}, not {2}.";
public string OTLanguageShouldBeLeq_0 { get; set; } = "Language ID should be <= {0}, not {1}.";
public string OTLanguageCannotPlayOnVersion_0 { get; set; } = "Language ID {0} cannot be played on this version.";
public string OTLanguageCannotTransferToConsoleRegion_0 { get; set; } = "Language ID {0} cannot be transferred to this Console Region.";
public string EncTradeShouldHaveEvolvedToSpecies_0 { get; init; } = "Trade Encounter should have evolved to species: {0}.";
public string EncGiftLanguageNotDistributed { get; init; } = "Gift Encounter was never distributed with this language.";
public string EncGiftRegionNotDistributed { get; init; } = "Gift Encounter was never distributed to this Console Region.";
public string FormInvalidRangeLEQ_0F { get; init; } = "Form Count is out of range. Expected <= {0}, got {1}.";
public string MovesShouldMatchRelearnMoves { get; init; } = "Moves should exactly match Relearn Moves.";
public string MemoryStatEnjoyment_0 { get; init; } = "Enjoyment should be {0}.";
public string MemoryStatFullness_0 { get; init; } = "Fullness should be {0}.";
public string MemoryStatFullnessLEQ_0 { get; init; } = "Fullness should be <= {0}.";
public string OTLanguageShouldBe_0 { get; init; } = "Language ID should be {0}, not {1}.";
public string OTLanguageShouldBe_0or1 { get; init; } = "Language ID should be {0} or {1}, not {2}.";
public string OTLanguageShouldBeLeq_0 { get; init; } = "Language ID should be <= {0}, not {1}.";
public string OTLanguageCannotPlayOnVersion_0 { get; init; } = "Language ID {0} cannot be played on this version.";
public string OTLanguageCannotTransferToConsoleRegion_0 { get; init; } = "Language ID {0} cannot be transferred to this Console Region.";
public string WordFilterInvalidCharacter_0 { get; set; } = "Word Filter: Invalid character '{0}' (0x{1}).";
public string WordFilterFlaggedPattern_01 { get; set; } = "Word Filter ({1}): Flagged pattern '{0}'.";
public string WordFilterTooManyNumbers_0 { get; set; } = "Word Filter: Too many numbers (>{0}).";
public string BulkCloneDetectedDetails { get; set; } = "Clone detected (Details).";
public string BulkCloneDetectedTracker { get; set; } = "Clone detected (Duplicate Tracker).";
public string HintEvolvesToSpecies_0 { get; set; } = "Evolves to species: {0}.";
public string HintEvolvesToRareForm_0 { get; set; } = "Evolves to rare form: {0}.";
public string BulkSharingEncryptionConstantGenerationDifferent { get; set; } = "Detected sharing of Encryption Constant across generations.";
public string BulkSharingEncryptionConstantGenerationSame { get; set; } = "Detected sharing of Encryption Constant.";
public string BulkSharingEncryptionConstantRNGType { get; set; } = "Detected sharing of Encryption Constant sharing for different RNG encounters.";
public string BulkSharingPIDGenerationDifferent { get; set; } = "Detected sharing of PID across generations.";
public string BulkSharingPIDGenerationSame { get; set; } = "Detected sharing of PID.";
public string BulkSharingPIDRNGType { get; set; } = "Detected sharing of PID for different RNG encounters.";
public string BulkDuplicateMysteryGiftEggReceived { get; set; } = "Detected multiple redemptions of the same non-repeatable Mystery Gift Egg.";
public string BulkSharingTrainerID { get; set; } = "Detected sharing of Trainer ID across multiple trainer names.";
public string BulkSharingTrainerVersion { get; set; } = "Detected sharing of Trainer ID across multiple versions.";
public string BulkDuplicateFusionSlot { get; set; } = "Detected multiple fusions of the same fusion stored slot species.";
public string BulkHeldItemInventoryAssignedNoneHeld_0 { get; set; } = "{0} is marked as held player inventory, but no Pokémon found in slots checked.";
public string BulkHeldItemInventoryMultipleSlots_0 { get; set; } = "{0} is a unique item and cannot be held by multiple Pokémon.";
public string BulkHeldItemInventoryNotAcquired_0 { get; set; } = "{0} has not been acquired in player inventory.";
public string BulkHeldItemInventoryUnassigned_0 { get; set; } = "{0} is not marked as assigned in player inventory.";
public string BulkFusionSourceInvalid { get; set; } = "The subsumed Species-Form stored in the save file does not match the expected Species-Form of the fused slot.";
public string WordFilterInvalidCharacter_0 { get; init; } = "Word Filter: Invalid character '{0}' (0x{1}).";
public string WordFilterFlaggedPattern_01 { get; init; } = "Word Filter ({1}): Flagged pattern '{0}'.";
public string WordFilterTooManyNumbers_0 { get; init; } = "Word Filter: Too many numbers (>{0}).";
public string BulkCloneDetectedDetails { get; init; } = "Clone detected (Details).";
public string BulkCloneDetectedTracker { get; init; } = "Clone detected (Duplicate Tracker).";
public string HintEvolvesToSpecies_0 { get; init; } = "Evolves to species: {0}.";
public string HintEvolvesToRareForm_0 { get; init; } = "Evolves to rare form: {0}.";
public string BulkSharingEncryptionConstantGenerationDifferent { get; init; } = "Detected sharing of Encryption Constant across generations.";
public string BulkSharingEncryptionConstantGenerationSame { get; init; } = "Detected sharing of Encryption Constant.";
public string BulkSharingEncryptionConstantRNGType { get; init; } = "Detected sharing of Encryption Constant sharing for different RNG encounters.";
public string BulkSharingPIDGenerationDifferent { get; init; } = "Detected sharing of PID across generations.";
public string BulkSharingPIDGenerationSame { get; init; } = "Detected sharing of PID.";
public string BulkSharingPIDRNGType { get; init; } = "Detected sharing of PID for different RNG encounters.";
public string BulkDuplicateMysteryGiftEggReceived { get; init; } = "Detected multiple redemptions of the same non-repeatable Mystery Gift Egg.";
public string BulkSharingTrainerID { get; init; } = "Detected sharing of Trainer ID across multiple trainer names.";
public string BulkSharingTrainerVersion { get; init; } = "Detected sharing of Trainer ID across multiple versions.";
public string BulkDuplicateFusionSlot { get; init; } = "Detected multiple fusions of the same fusion stored slot species.";
public string BulkHeldItemInventoryAssignedNoneHeld_0 { get; init; } = "{0} is marked as held player inventory, but no Pokémon found in slots checked.";
public string BulkHeldItemInventoryMultipleSlots_0 { get; init; } = "{0} is a unique item and cannot be held by multiple Pokémon.";
public string BulkHeldItemInventoryNotAcquired_0 { get; init; } = "{0} has not been acquired in player inventory.";
public string BulkHeldItemInventoryUnassigned_0 { get; init; } = "{0} is not marked as assigned in player inventory.";
public string BulkFusionSourceInvalid { get; init; } = "The subsumed Species-Form stored in the save file does not match the expected Species-Form of the fused slot.";
}
[JsonSerializable(typeof(LegalityCheckLocalization))]

View File

@ -134,7 +134,7 @@ public static class LegalityCheckResultCodeExtensions
// Evolution
EvoInvalid => localization.EvoInvalid,
EvoTradeReqOutsider_0 => localization.EvoTradeReqOutsider,
EvoTradeReqOutsider_01 => localization.EvoTradeReqOutsider,
EvoTradeRequired => localization.EvoTradeRequired,
// Form
@ -144,16 +144,11 @@ public static class LegalityCheckResultCodeExtensions
FormArgumentValid => localization.FormArgumentValid,
FormArgumentInvalid => localization.FormArgumentInvalid,
FormBattle => localization.FormBattle,
FormEternal => localization.FormEternal,
FormEternalInvalid => localization.FormEternalInvalid,
FormInvalidGame => localization.FormInvalidGame,
FormInvalidNature => localization.FormInvalidNature,
FormItemMatches => localization.FormItem,
FormItemInvalid => localization.FormItemInvalid,
FormParty => localization.FormParty,
FormPikachuCosplay => localization.FormPikachuCosplay,
FormPikachuCosplayInvalid => localization.FormPikachuCosplayInvalid,
FormPikachuEventInvalid => localization.FormPikachuEventInvalid,
FormInvalidExpect_0 => localization.FormInvalidExpect_0,
FormValid => localization.FormValid,
FormVivillon => localization.FormVivillon,

View File

@ -128,6 +128,7 @@ private string GetMemory(CheckResult chk, string template, LegalityCheckResultCo
EncTradeShouldHaveEvolvedToSpecies_0 => string.Format(format, GetSpeciesName(chk.Argument)),
MoveEvoFCombination_0 => string.Format(format, GetSpeciesName(chk.Argument)),
HintEvolvesToSpecies_0 => string.Format(format, GetSpeciesName(chk.Argument)),
EvoTradeReqOutsider_01 => string.Format(format, GetSpeciesName(chk.Argument), GetSpeciesName(chk.Argument2)),
RibbonMarkingInvalid_0 => string.Format(format, GetRibbonName((RibbonIndex)chk.Argument)),
RibbonMarkingMissing_0 => string.Format(format, GetRibbonName((RibbonIndex)chk.Argument)),

View File

@ -68,14 +68,15 @@ public static void Generate(PK5 pk, in EncounterCriteria criteria, byte gr, ulon
if (type is Shiny.Never)
{
if (ShinyUtil.GetIsShiny3(id32, pid))
pid ^= 0x1000_0000; // force not shiny. the wild xor is never true.
pid ^= 0x1000_0000; // force not shiny. the wild xor never undoes this fixing.
}
else if (type is Shiny.Always)
{
// inlined GetShinyPID without ability force (done later)
var gval = (byte)pid;
var trainerXor = (id32 >> 16) ^ (ushort)id32;
pid = ((gval ^ trainerXor) << 16) | gval;
// retain the random gender value, then ensure top16 bits yield a shiny.
// this results in xor = 0.
pid &= 0xFF;
pid |= ShinyUtil.GetShinyXor(pid, id32) << 16;
}
if (isSingleAbility)
@ -89,8 +90,11 @@ public static void Generate(PK5 pk, in EncounterCriteria criteria, byte gr, ulon
if (wildXor)
{
// great job checking the wrong bits to give 2x shiny chance for wild stuff.
var corr = (pid >> 31) ^ (pid & 1) ^ bitXor;
// Attempt to nullify the top bit of the shiny xor?
// It seems this was an attempt by the game developer to give 2x shiny chance for wild encounters...
// Checks and flips bits inconsistently?
// Possibly due to ability being moved to bit16 from bit0, thus shifting the anti-shiny to msb?
var corr = (pid >> 31) ^ (pid & 1) ^ bitXor; // msb, lsb, lsb of tid^sid
if (corr != 0)
pid ^= 0x8000_0000;
}

View File

@ -55,7 +55,8 @@ private static bool TryApplyFromSeed(PK8 pk, in EncounterCriteria criteria, Shin
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
return false;
}
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
// IVs

View File

@ -114,7 +114,7 @@ public static bool Verify(PKM pk, ulong seed, Span<int> ivs, in GenerateParam8 p
break;
}
var nature = param.Nature != Nature.Random ? param.Nature
var nature = param.Nature.IsFixed ? param.Nature
: param.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rng, pk.Form)
: (Nature)rng.NextInt(25);
@ -160,6 +160,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
var trID = (uint)rng.NextInt();
var pid = (uint)rng.NextInt();
// Battle
var xor = GetShinyXor(pid, trID);
bool isShiny = xor < 16;
if (isShiny && param.Shiny == Shiny.Never)
@ -167,9 +169,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
ForceShinyState(false, ref pid, trID, 0);
isShiny = false;
}
if (param.Shiny is Shiny.Random && isShiny != criteria.Shiny.IsShiny())
return false;
// Captured
if (isShiny)
{
if (!GetIsShiny6(pk.ID32, pid))
@ -181,6 +182,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
pid ^= 0x1000_0000;
}
if (param.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
const int UNSET = -1;
@ -236,7 +239,7 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
return false;
pk.Gender = gender;
var nature = param.Nature != Nature.Random ? param.Nature
var nature = param.Nature.IsFixed ? param.Nature
: param.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rng, pk.Form)
: (Nature)rng.NextInt(25);

View File

@ -129,6 +129,8 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
if (para.Shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
return false;
}
if (para.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
Span<int> ivs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET];
@ -172,6 +174,8 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
pk.Gender = gender;
var nature = (Nature)rand.NextInt(25);
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(nature))
return false;
pk.Nature = pk.StatNature = nature;
var (height, weight) = para.IsAlpha
@ -179,16 +183,10 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
: ((byte)(rand.NextInt(0x81) + rand.NextInt(0x80)),
(byte)(rand.NextInt(0x81) + rand.NextInt(0x80)));
if (pk is IScaledSize s)
{
s.HeightScalar = height;
s.WeightScalar = weight;
if (pk is IScaledSizeValue a)
{
a.ResetHeight();
a.ResetWeight();
}
}
pk.HeightScalar = height;
pk.WeightScalar = weight;
pk.ResetHeight();
pk.ResetWeight();
return true;
}

View File

@ -65,6 +65,8 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
return false;
}
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
// Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless.

View File

@ -65,6 +65,8 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
return false;
}
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
// Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless.

View File

@ -62,10 +62,10 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, in EncounterCrite
{
var rand = new Xoroshiro128Plus(seed);
pk.EncryptionConstant = (uint)rand.NextInt(uint.MaxValue);
pk.PID = GetAdaptedPID(ref rand, pk, enc);
if (enc.Shiny is Shiny.Random && criteria.Shiny.IsShiny() != pk.IsShiny)
var pid = GetAdaptedPID(ref rand, pk, enc);
if (enc.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
return false;
pk.PID = pid;
const int UNSET = -1;
const int MAX = 31;
@ -121,7 +121,7 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, in EncounterCrite
return false;
pk.Gender = gender;
var nature = enc.Nature != Nature.Random ? enc.Nature : enc.Species == (int)Species.Toxtricity
var nature = enc.Nature.IsFixed ? enc.Nature : enc.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rand, pk.Form)
: (Nature)rand.NextInt(25);
@ -205,7 +205,7 @@ public static bool IsMatch(PKM pk, in GenerateParam9 enc, in ulong seed)
if (pk.Gender != gender)
return false;
var nature = enc.Nature != Nature.Random ? enc.Nature : enc.Species == (int)Species.Toxtricity
var nature = enc.Nature.IsFixed ? enc.Nature : enc.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rand, pk.Form)
: (Nature)rand.NextInt(25);
if (pk.Nature != nature)

View File

@ -4,7 +4,7 @@ namespace PKHeX.Core;
/// Parameters used to generate data for an encounter.
/// </summary>
/// <param name="Species">Species to generate.</param>
/// <param name="GenderRatio">Gender ratio byte.</param>
/// <param name="GenderRatio">Gender ratio byte from Personal Info.</param>
/// <param name="FlawlessIVs">Count of IVs that are perfect.</param>
/// <param name="RollCount">Count of shiny rolls allowed for the PID calculation.</param>
/// <param name="Height">Height value to generate. If zero, full random.</param>

View File

@ -38,6 +38,7 @@ public static bool IsRequired(IEncounterTemplate enc, EntityContext current)
WB8 { IsHOMEGift: true } => true,
WA8 { IsHOMEGift: true } => true,
WC9 { IsHOMEGift: true } => true,
WA9 { IsHOMEGift: true } => true,
_ => enc.Generation < 8,
};
}

View File

@ -51,7 +51,7 @@ public static bool IsHeldItemAllowed(int item, EntityContext context)
// Combined bitflags for released held items across generations.
private static readonly bool[] ReleasedHeldItems_2 = GetPermitList(MaxItemID_2, HeldItems_GSC);
private static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3, HeldItems_RS, ItemStorage3RS.Unreleased); // Safari Ball
private static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3_RS, HeldItems_RS, ItemStorage3RS.Unreleased); // Safari Ball
private static readonly bool[] ReleasedHeldItems_4 = GetPermitList(MaxItemID_4_HGSS, HeldItems_HGSS, ItemStorage4.Unreleased);
private static readonly bool[] ReleasedHeldItems_5 = GetPermitList(MaxItemID_5_B2W2, HeldItems_BW, ItemStorage5.Unreleased);
private static readonly bool[] ReleasedHeldItems_6 = GetPermitList(MaxItemID_6_AO, HeldItems_AO, ItemStorage6XY.Unreleased);

View File

@ -20,9 +20,8 @@ public static MemorySource GetPossibleSources(EvolutionHistory history)
sources |= MemorySource.Bank; // Trade encounters from Gen7 also come with hardcoded memories.
if (history.HasVisitedSWSH)
sources |= MemorySource.Gen8;
if (history.HasVisitedGen9)
if (history.HasVisitedGen9 || history.HasVisitedZA)
sources |= MemorySource.Deleted;
// TODO HOME ZA
return sources;
}

View File

@ -138,16 +138,11 @@ public enum LegalityCheckResultCode : ushort
FormArgumentValid,
FormArgumentInvalid,
FormBattle,
FormEternal,
FormEternalInvalid,
FormInvalidGame,
FormInvalidNature,
FormItemMatches,
FormItemInvalid,
FormParty,
FormPikachuCosplay,
FormPikachuCosplayInvalid,
FormPikachuEventInvalid,
FormValid,
FormVivillon,
FormVivillonEventPre,
@ -393,7 +388,6 @@ public enum LegalityCheckResultCode : ushort
ContestSheenLEQ_0,
EggFMetLevel_0,
EffortUntrainedCap_0,
EvoTradeReqOutsider_0,
FormArgumentLEQ_0,
FormArgumentGEQ_0,
FormInvalidExpect_0,
@ -479,6 +473,7 @@ public enum LegalityCheckResultCode : ushort
EncTradeShouldHaveEvolvedToSpecies_0, // species
MoveEvoFCombination_0, // species
HintEvolvesToSpecies_0, // species
EvoTradeReqOutsider_01, // species, species
RibbonMarkingInvalid_0, // ribbon
RibbonMarkingMissing_0, // ribbon

View File

@ -137,7 +137,7 @@ public static bool IsFormChangeable(ushort species, byte oldForm, byte newForm,
{
EntityContext.Gen6 => false,
EntityContext.Gen7 => newForm >= 2 || (oldForm == 1 && newForm == 0),
EntityContext.Gen9a => newForm >= 2,
EntityContext.Gen9a when origin is EntityContext.Gen9a => newForm >= 2,
_ => true,
};
}
@ -420,7 +420,7 @@ public static bool IsLordForm(ushort species, byte form, EntityContext context)
/// <param name="pi">Game specific personal info</param>
/// <param name="species"><see cref="Species"/> ID</param>
/// <param name="format"><see cref="PKM.Form"/> ID</param>
/// <returns>True if it has forms that can be provided by <see cref="FormConverter.GetFormList"/>, otherwise false for none.</returns>
/// <returns>True if it has forms that can be provided by <see cref="FormConverter"/>, otherwise false for none.</returns>
public static bool HasFormSelection(IPersonalFormInfo pi, ushort species, byte format)
{
if (format <= 3 && species != (int)Unown)

View File

@ -1,3 +1,4 @@
using System;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@ -472,6 +473,8 @@ private CheckResult VerifyBirthAbility(LegalityAnalysis data, PA9 pa9)
var index = bitNum >> 1;
var species = pa9.Species;
var ability = pa9.Ability;
// In-battle forms allow mismatching for the current form.
if (FormInfo.HasMegaForm(species) || species is (int)Species.Aegislash)
{
var form = pa9.Form;
@ -492,10 +495,62 @@ private CheckResult VerifyBirthAbility(LegalityAnalysis data, PA9 pa9)
}
}
// If deposited in HOME, it will realign to the deposited species-form ability.
// If you transfer back into ZA then evolve it, you can disjoint it again.
// If it hasn't evolved, then it must have been realigned by HOME.
if (pa9 is IHomeTrack { HasTracker: true })
{
var form = pa9.Form;
var actual = PersonalTable.ZA[species, form];
var require = actual.GetAbilityAtIndex(index);
if (ability == require)
return VALID;
if (enc.Species == species || IsAnyMoveSourceNotZA_Head(data.Info.Moves)) // Not evolved after; must match.
return GetInvalid(AbilityMismatch);
}
// Otherwise, we expect the form's personal info to match the original encounter's ability.
var expect = pi.GetAbilityAtIndex(index);
if (ability != expect)
return GetInvalid(AbilityMismatch);
return VerifyFinalState(data, enc, index);
}
private CheckResult VerifyFinalState(LegalityAnalysis data, IEncounterTemplate enc, int currentIndex)
{
if (currentIndex == 2) // Not normally obtainable. Has to visit another game where it can be switched.
{
if (AbilityChangeRules.IsAbilityPatchPossible(data.Info.EvoChainsAllGens))
return GetValid(AbilityPatchUsed);
return GetInvalid(AbilityHiddenUnavailable);
}
if (!enc.Ability.IsSingleValue(out var bit) || bit == currentIndex) // Any/Unchanged
return VALID;
var history = data.Info.EvoChainsAllGens;
if (bit == 2 && AbilityChangeRules.IsAbilityPatchRevertPossible(history, currentIndex))
return GetValid(AbilityPatchRevertUsed);
if (bit != 2 && AbilityChangeRules.IsAbilityCapsuleAvailable(history))
return GetValid(AbilityCapsuleUsed);
// Can't change to current state.
return GetInvalid(AbilityMismatch);
}
private static bool IsAnyMoveSourceNotZA_Head(ReadOnlySpan<MoveResult> infoMoves)
{
foreach (var info in infoMoves)
{
if (info.EvoStage != 0) // pre-evo
continue;
if (info.Context == EntityContext.Gen9a)
continue;
// move verifier doesn't check for realignment lockout scenario; disable check.
// return true;
}
return false;
}
}

View File

@ -31,20 +31,8 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
return (Species)pk.Species switch
{
// Transfer Edge Cases -- Bank wipes the form but keeps old FormArgument value.
Furfrou when pk is { Context: EntityContext.Gen7, Form: 0 } &&
((enc.Generation == 6 && f.FormArgument <= byte.MaxValue) || IsFormArgumentDayCounterValid(f, 5, true))
=> GetValid(FormArgumentValid),
Furfrou when pk.Form != 0 => !IsFormArgumentDayCounterValid(f, 5, true) ? GetInvalid(FormArgumentInvalid) : GetValid(FormArgumentValid),
Hoopa when pk.Form == 1 => data.Info.EvoChainsAllGens switch
{
{ HasVisitedZA: true } when arg == 0 => GetValid(FormArgumentValid), // Value not applied on form change, and reset when reverted.
{ HasVisitedGen9: true } when arg == 0 => GetValid(FormArgumentValid), // Value not applied on form change, and reset when reverted.
{ HasVisitedGen6: true } when IsFormArgumentDayCounterValid(f, 3) => GetValid(FormArgumentValid), // 0-3 via OR/AS
{ HasVisitedGen7: true } when IsFormArgumentDayCounterValid(f, 3) && f.FormArgumentRemain != 0 => GetValid(FormArgumentValid), // 1-3 via Gen7
_ => GetInvalid(FormArgumentInvalid),
},
Furfrou => CheckFurfrou(pk, enc, f),
Hoopa when pk.Form == 1 => CheckHoopa(data, f, arg),
Yamask when pk.Form == 1 => arg switch
{
not 0 when pk.IsEgg => GetInvalid(FormArgumentNotAllowed),
@ -74,22 +62,8 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => arg == 0 || HasVisitedSV(data, Bisharp) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
},
Gimmighoul => arg switch
{
// Z-A evolutions do not set form argument to Gimmighoul.
0 when data.Info.EvoChainsAllGens.HasVisitedZA => GetValid(FormArgumentValid),
// When leveled up, the game copies the save file's current coin count to the arg (clamped to <=999). If >=999, evolution is triggered.
// Without being leveled up at least once, it cannot have a form arg value.
>= 999 => GetInvalid(FormArgumentLEQ_0, 999),
0 => GetValid(FormArgumentValid),
// S/V sets form argument to match coin count.
_ when !data.Info.EvoChainsAllGens.HasVisitedGen9 => GetInvalid(FormArgumentInvalid),
_ => pk.CurrentLevel != pk.MetLevel ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
},
Gholdengo when !data.Info.EvoChainsAllGens.HasVisitedGen9 => arg == 0 ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentInvalid),
Gholdengo => VerifyFormArgumentRange(enc.Species, Gholdengo, arg, 999, 999),
Gimmighoul => CheckGimmighoul(data.Info.EvoChainsAllGens, arg, pk),
Gholdengo => CheckGholdengo(data.Info.EvoChainsAllGens, arg, enc, pk),
Runerigus => VerifyFormArgumentRange(enc.Species, Runerigus, arg, 49, 9999),
Alcremie => VerifyFormArgumentRange(enc.Species, Alcremie, arg, 0, (ushort)AlcremieDecoration.Ribbon),
@ -119,6 +93,186 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
};
}
private CheckResult CheckHoopa(LegalityAnalysis data, IFormArgument f, uint arg)
{
var history = data.Info.EvoChainsAllGens;
if (arg == 0)
{
if (history.HasVisitedZA) // Value not applied on form change, and reset when reverted.
return GetValid(FormArgumentValid);
if (history.HasVisitedGen9) // Value not applied on form change, and reset when reverted.
return GetValid(FormArgumentValid);
}
else
{
if (history.HasVisitedGen7 && IsFormArgumentDayCounterValid(f, 3)) // 1-3 via Gen7
return GetValid(FormArgumentValid);
}
var pk = data.Entity;
if (pk is PK6 pk6)
{
// 0-3 via OR/AS
if (pk6.FormArgument != 0) // 0x3C not used (elapsed streak)
return GetInvalid(FormArgumentNotAllowed);
var elapsed = pk6.FormArgumentElapsed;
var remain = pk6.FormArgumentRemain;
var sum = elapsed + remain;
if (sum != 3)
return GetInvalid(FormArgumentInvalid);
return GetValid(FormArgumentValid);
}
return GetInvalid(FormArgumentInvalid);
}
private CheckResult CheckFurfrou(PKM pk, IEncounterTemplate enc, IFormArgument f)
{
// Transfer Edge Cases -- Bank wipes the form but keeps old FormArgument value.
// Gen6: Reverts when deposited.
// Gen7: Reverts form & arg when withdrawn, reverts form (NOT arg) when deposited in Bank.
// Gen9a: Doesn't decrease, always 5.
if (pk is PK6 pk6)
return CheckFurfrou6(pk6);
if (pk is PK7 pk7)
return CheckFurfrou7(pk7, enc);
if (pk is { GO_HOME: true } && f.FormArgument == 0)
return GetValid(FormArgumentValid); // GO transfers forget to set Form Argument.
if (pk is PA9 pa9)
return CheckFurfrou9a(pa9, enc);
// Only legal pathways are via methods above.
return GetInvalid(FormArgumentInvalid);
}
private CheckResult CheckFurfrou9a(PA9 pk, IEncounterTemplate enc)
{
// Gen6=>Gen7 transfer edge case: Form argument is not cleared when depositing in Bank, but form is.
if (enc.Generation == 6 && pk is { Form: 0, FormArgument: <= byte.MaxValue })
return GetValid(FormArgumentValid);
// Z-A trims set to 5.
if ((pk.FormArgument == 5) == (pk.Form != 0))
return GetValid(FormArgumentValid);
// Bank=>HOME with form 0 forgets to wipe, but Form is reverted.
if (pk.Form == 0 && enc.Generation <= 7 && IsFormArgumentDayCounterValid(pk, 5, true))
return GetValid(FormArgumentValid);
return GetInvalid(FormArgumentInvalid);
}
private CheckResult CheckFurfrou7(PK7 pk, IEncounterTemplate enc)
{
// Gen6=>Gen7 transfer edge case: Form argument is not cleared when depositing in Bank, but form is.
if (enc.Generation == 6 && pk is { Form: 0, FormArgument: <= byte.MaxValue })
return GetValid(FormArgumentValid);
if (pk is { Form: 0, FormArgument: 0 })
return GetValid(FormArgumentValid);
// Depositing into box no longer clears form; they only wipe it when you withdraw.
// Storing in Bank will revert the form, so any form is valid as long as the day counter values are valid for any trim.
if (!IsFormArgumentDayCounterValid(pk, 5, true))
return GetInvalid(FormArgumentInvalid);
return GetValid(FormArgumentValid);
}
private CheckResult CheckFurfrou6(PK6 pk)
{
// Can only exist in party.
// 0x3C: Current streak
// 0xED: Remaining days
// 0xEE: Elapsed days (same as current streak)
var arg = pk.FormArgument;
// Argument can be anything; depositing drops the form and party stats and forgets to clear the arg.
if (arg > byte.MaxValue)
return GetInvalid(FormArgumentLEQ_0, byte.MaxValue);
// Form can only exist inside party. Checked elsewhere.
var remain = pk.FormArgumentRemain;
var elapsed = pk.FormArgumentElapsed;
if (pk.Form != 0)
{
var sum = remain + elapsed;
if (sum < 5)
return GetInvalid(FormArgumentInvalid);
if (elapsed != arg)
return GetInvalid(FormArgumentInvalid);
}
else
{
// Party stat values must be zero.
if (remain != 0 || elapsed != 0)
return GetInvalid(FormArgumentNotAllowed);
}
return GetValid(FormArgumentValid);
}
private CheckResult CheckGimmighoul(EvolutionHistory history, uint arg, PKM pk)
{
if (arg == 0)
return GetValid(FormArgumentValid);
// The only game we can assign a form argument value is in S/V.
// Z-A evolutions do not set form argument to Gimmighoul.
if (history.HasVisitedGen9)
{
// When leveled up, the game copies the save file's current coin count to the arg (clamped to <=999). If >=999, evolution is triggered (can cancel).
// Without being leveled up at least once, it cannot have a form arg value.
if (arg > 999)
return GetInvalid(FormArgumentLEQ_0, 999);
if (pk.CurrentLevel == pk.MetLevel)
return GetInvalid(FormArgumentNotAllowed);
return GetValid(FormArgumentValid);
}
return GetInvalid(FormArgumentNotAllowed);
}
private CheckResult CheckGholdengo(EvolutionHistory history, uint arg, IEncounterable enc, PKM pk)
{
if (enc.Species == (ushort)Gholdengo)
{
if (arg == 0)
return GetValid(FormArgumentValid);
return GetInvalid(FormArgumentNotAllowed);
}
// Gimmighoul evolved.
// The only game we can assign a form argument value is in S/V.
// Z-A evolutions do not set form argument to Gimmighoul.
var hasVisitedNoArgGame = history.HasVisitedZA;
if (hasVisitedNoArgGame && arg == 0)
return GetValid(FormArgumentValid);
if (history.HasVisitedGen9)
{
// When leveled up, the game copies the save file's current coin count to the arg (clamped to <=999). If >=999, evolution is triggered (can cancel).
// Without being leveled up at least once, it cannot have a form arg value.
if (arg > 999)
return GetInvalid(FormArgumentLEQ_0, 999);
if (pk.CurrentLevel == pk.MetLevel)
return GetInvalid(FormArgumentNotAllowed);
if (!hasVisitedNoArgGame && arg != 999) // Evolving without visiting a less-restricted game requires 999.
return GetInvalid(FormArgumentGEQ_0, 999);
return GetValid(FormArgumentValid);
}
return GetInvalid(FormArgumentNotAllowed);
}
private static bool IsFormArgumentValidFurfrou8HOME(IFormArgument f, IEncounterTemplate enc)
{
if (f.FormArgument == 0 && enc is { Version: GameVersion.GO })
return true; // Does not come with a Form Argument.
return IsFormArgumentDayCounterValid(f, 5, enc.Generation < 8);
}
private CheckResult CheckPrimeape(LegalityAnalysis data, PKM pk, uint arg, IEncounterTemplate enc)
{
if (arg == 0)

View File

@ -43,15 +43,7 @@ private CheckResult VerifyForm(LegalityAnalysis data)
switch ((Species)species)
{
case Pikachu when enc.Generation == 6: // Cosplay
if (enc is not EncounterStatic6 s6)
{
if (form == 0)
break; // Regular Pikachu, OK.
return GetInvalid(FormPikachuCosplay);
}
if (form != s6.Form)
return GetInvalid(FormPikachuCosplayInvalid);
if (pk.Format != 6)
if (form != 0 && pk.Format != 6) // Regular Pikachu, OK.
return GetInvalid(TransferBad); // Can't transfer.
break;
@ -60,16 +52,6 @@ private CheckResult VerifyForm(LegalityAnalysis data)
case Eevee when form is not 0 && ParseSettings.ActiveTrainer is SAV7b {Version:GameVersion.GP}:
return GetInvalid(FormBattle);
case Pikachu when enc.Generation >= 7: // Cap
var expectForm = enc is EncounterInvalid or IEncounterEgg ? 0 : enc.Form;
if (form != expectForm)
{
bool gift = enc is MysteryGift g && g.Form != form;
var msg = gift ? FormPikachuEventInvalid : FormInvalidGame;
return GetInvalid(msg);
}
break;
case Unown when enc.Generation == 2 && form >= 26:
return GetInvalid(FormInvalidRangeLEQ_0F, 25);
case Unown when enc.Generation == 3:
@ -101,12 +83,9 @@ private CheckResult VerifyForm(LegalityAnalysis data)
case Genesect:
var genesect = FormItem.GetFormGenesect(pk.HeldItem);
return genesect != form ? GetInvalid(FormItemInvalid) : GetValid(FormItemMatches);
case Greninja:
if (form > 1) // Ash Battle Bond active
return GetInvalid(FormBattle);
if (form != 0 && enc is not MysteryGift) // Form can not be bred for, MysteryGift already checked
return GetInvalid(FormInvalidRangeLEQ_0F, 0);
break;
case Furfrou when pk.Context == EntityContext.Gen6 && form != 0 && !data.IsStoredSlot(StorageSlotType.Party):
return GetInvalid(FormParty);
case Scatterbug or Spewpa or Vivillon when enc.Context is EntityContext.Gen9:
if (form > 18 && enc.Form != form) // Pokéball
@ -139,10 +118,6 @@ private CheckResult VerifyForm(LegalityAnalysis data)
data.AddLine(Get(Severity.Fishy, FormVivillonNonNative));
break;
case Floette when form == 5: // Eternal Flower Floette - not released until Pokémon Legends: Z-A
if (enc is not EncounterGift9a)
return GetInvalid(FormEternalInvalid);
return GetValid(FormEternal);
case Meowstic when (form & 1) != pk.Gender:
return GetInvalid(GenderInvalidNone);

View File

@ -299,8 +299,10 @@ public static bool GetCanOTHandle(IEncounterTemplate enc, PKM pk, byte generatio
WB8 wb8 when wb8.GetHasOT(pk.Language) => false,
WA8 wa8 when wa8.GetHasOT(pk.Language) => false,
WC9 wc9 when wc9.GetHasOT(pk.Language) => false,
WA9 wa9 when wa9.GetHasOT(pk.Language) => false,
WC8 {IsHOMEGift: true} => false,
WC9 {IsHOMEGift: true} => false,
WA9 {IsHOMEGift: true} => false,
_ => true,
};

View File

@ -31,10 +31,9 @@ private void CheckLearnset(LegalityAnalysis data, PA9 pa)
if (moveCount == 4)
return;
// TODO ZA HOME
// // Flag move slots that are empty.
// if (pa.Tracker != 0 || !ParseSettings.IgnoreTransferIfNoTracker)
// return; // Can delete moves in PA9 moveset via HOME.
// Flag move slots that are empty.
if (pa is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker)
return; // Can delete moves in PA9 moveset via HOME.
if (e9a.Species is (int)Rotom && moveCount == 3 && pa.Form == 0)
{
@ -216,7 +215,8 @@ private void CheckFlagsPlus(LegalityAnalysis la, PA9 pk)
// Trade evolutions forget to set the Plus flags, unlike triggered evolutions.
// If the move is not present as a previous-evolution learnset move, and the head species is a Trade evo, skip the error.
// Assume the best case -- evolved at current level, so none would get set.
if (IsTradeEvoSkip(la.Info.EvoChainsAllGens.Gen9a, move))
var evos = la.Info.EvoChainsAllGens;
if (IsTradeEvoSkip(evos.Gen9a, move))
continue;
if (WasPossiblyObtainedBeforeDLC(pk, la.EncounterMatch) && IsPermittedUnsetPlusMove((Species)pk.Species, (Move)move))

View File

@ -95,7 +95,7 @@ public void VerifyG1(LegalityAnalysis data)
{
// Pokémon has been traded illegally between games without evolving.
// Trade evolution species IDs for Gen1 are sequential dex numbers.
data.AddLine(GetInvalid(EvoTradeReqOutsider_0, enc.Species + 1u));
data.AddLine(GetInvalid(EvoTradeReqOutsider_01, enc.Species, (ushort)(enc.Species + 1u)));
}
}

View File

@ -78,7 +78,8 @@ private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m)
return;
var affix = (RibbonIndex)affixValue;
var max = MarkRules.GetMaxAffixValue(data.Info.EvoChainsAllGens);
var evos = data.Info.EvoChainsAllGens;
var max = MarkRules.GetMaxAffixValue(evos);
if ((sbyte)max == -1 || affix > max)
{
data.AddLine(GetInvalid(RibbonMarkingAffixed_0, (ushort)affix));
@ -88,11 +89,19 @@ private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m)
if (m is not PKM pk)
return;
if (MarkRules.IsEncounterMarkLost(data.EncounterOriginal, data.Entity))
var enc = data.EncounterOriginal;
if (MarkRules.IsEncounterMarkLost(enc, data.Entity))
{
VerifyShedinjaAffixed(data, affix, pk, m);
return;
}
// Some games cannot affix ribbon unless it transfers to a game that can affix it.
if (enc.Context is EntityContext.Gen8a or EntityContext.Gen9a && pk.Context == enc.Context)
{
if (!evos.HasVisitedExcept(enc.Context))
data.AddLine(GetInvalid(RibbonMarkingAffixed_0, (ushort)affix));
}
EnsureHasRibbon(data, m, affix);
}

View File

@ -90,7 +90,8 @@ private void VerifyHTLanguage(LegalityAnalysis data, MemorySource source)
private static bool GetIsHTLanguageValid(IEncounterTemplate enc, PKM pk, byte language, MemorySource source)
{
// Bounds check.
if (language > (int)LanguageID.ChineseT)
var max = Legal.GetMaxLanguageID(pk.Format, pk.Context);
if (language > max)
return false;
// Gen6 and Bank don't have the HT language flag.

View File

@ -33,7 +33,7 @@ private void VerifyRandomCorrelationSwitch(LegalityAnalysis data, PKM pk, IEncou
{
VerifyCorrelation8b(data, s8b, pk);
}
else if (enc is ISeedCorrelation64<PKM> s64)
else if (enc is ISeedCorrelation64<PKM> s64 && !(enc is WA9 { IsHOMEGift: true }))
{
var pidiv = s64.TryGetSeed(pk, out var seed);
if (pidiv == SeedCorrelationResult.Success)

View File

@ -29,11 +29,14 @@ private static void VerifyHeightWeight(LegalityAnalysis data, PKM pk, IEncounter
}
else if (enc.Context is EntityContext.Gen9a)
{
// TODO HOME ZA
if (s2.HeightScalar != 0)
data.AddLine(GetInvalid(Encounter, StatIncorrectHeightValue_0, 0));
if (s2.WeightScalar != 0)
data.AddLine(GetInvalid(Encounter, StatIncorrectWeightValue_0, 0));
// By default, Gen9a does not apply height/weight properties, so 0-0 is expected.
// If touched by HOME, Scale is copied to both Height and Weight properties.
// Thus, only n-n is valid. Scale we'll check separately if the current object's format supports it.
var height = s2.HeightScalar;
var weight = s2.WeightScalar;
if (height != weight)
data.AddLine(GetInvalid(Encounter, StatIncorrectWeightValue_0, height));
}
else if (CheckHeightWeightOdds(enc))
{
@ -50,7 +53,7 @@ private void VerifyScale(LegalityAnalysis data, PKM pk, IEncounterTemplate enc,
// PLA static Alphas have potential for 127 scale; this is already checked explicitly in the matching check.
// Ensure all Alphas have 255 scale.
// Otherwise, ensure scale matches height scalar if required.
if (enc is IAlphaReadOnly { IsAlpha: true })
if (enc is { Context: EntityContext.Gen8a } and IAlphaReadOnly { IsAlpha: true })
{
byte expect = enc switch
{
@ -59,6 +62,27 @@ private void VerifyScale(LegalityAnalysis data, PKM pk, IEncounterTemplate enc,
};
if (s3.Scale != expect)
data.AddLine(GetInvalid(StatIncorrectScaleValue_0, expect));
return;
}
if (enc is { Context: EntityContext.Gen9a })
{
// Height != Weight already checked.
var scale = s3.Scale;
var height = s2.HeightScalar;
if (scale == 0)
{
// If scale is 0, then the only legal value for height/weight is also 0.
if (height != 0)
data.AddLine(GetInvalid(StatIncorrectHeightValue_0, 0));
}
else
{
// If scale is nonzero, then height/weight must be 0 or equal to scale.
// If it was definitely touched by HOME, then they can only be equal to scale, since HOME copies scale to height/weight.
if ((height != 0 || IsHeightScaleMatchRequired(pk)) && height != scale)
data.AddLine(GetInvalid(StatIncorrectHeightValue_0, height));
}
}
else if (IsHeightScaleMatchRequired(pk) && s2.HeightScalar != s3.Scale)
{
@ -72,8 +96,6 @@ private static bool CheckHeightWeightOdds(IEncounterTemplate enc)
{
if (enc.Generation < 8)
return false;
if (enc.Context is EntityContext.Gen9a) // TODO HOME ZA
return true;
if (enc is WC8 { IsHOMEGift: true })
return false;
if (enc is WC9) // fixed values (usually 0 or 128)

View File

@ -46,7 +46,7 @@ private void VerifyTrash(LegalityAnalysis data, G3PKM pk)
VerifyTrashCXD(data, pk);
}
private void VerifyTrashCXD(LegalityAnalysis data, G3PKM pk)
private static void VerifyTrashCXD(LegalityAnalysis data, G3PKM pk)
{
// Buffers should be entirely clean.
var ot = pk.OriginalTrainerTrash;
@ -122,7 +122,10 @@ private static void VerifyTrashINT(LegalityAnalysis data, PK3 pk)
var trash = pk.OriginalTrainerTrash;
// OT name from save file is copied byte-for-byte. All 8 bytes are initialized to FF on new game.
if (!TrashByteRules3.IsTerminatedFFZero(trash, 7))
{
if (!TrashByteRules3.IsTrashPatternDefaultTrainer(trash, pk.Version, (LanguageID)pk.Language))
data.AddLine(GetInvalid(Trainer, TrashBytesMissingTerminatorFinal));
}
// Nickname can be all FF's (nicknamed) or whatever random garbage is in the buffer before filling. Unsure if we can reliably check this, but it should be "dirty" usually.
// If it is clean, flag as fishy.
FlagIsNicknameClean(data, pk);
@ -132,11 +135,17 @@ private static void FlagIsNicknameClean(LegalityAnalysis data, PK3 pk)
{
if (!pk.IsNicknamed || pk.IsEgg)
return;
var nick = pk.NicknameTrash;
// Japanese only fills the first 5+1 bytes; everything else is trash.
// International games are 10 chars (full buffer) max; implicit terminator if full.
var nick = pk.GetNicknamePrefillRegion();
if (!TrashByteRules3.IsTerminatedFF(nick))
{
// Trade to another language and evolve will treat it like a nickname, without actually filling with FF.
if (!TrashByteRules3.IsTerminatedFFZero(nick) || pk.Species == data.EncounterOriginal.Species) // not evolved
data.AddLine(GetInvalid(Trainer, TrashBytesMismatchInitial));
}
}
}
public static class TrashByteRules3
{
@ -145,6 +154,10 @@ public static class TrashByteRules3
// When transferred to Colosseum/XD, the encoding method switches to u16[length], thus discarding the original buffer along with its "trash".
// For original encounters from a mainline save file,
// - OT Name: the game copies the entire buffer from the save file OT as the PK3's OT. Thus, that must match exactly.
// - - Japanese OT names are 5 chars, international is 7 chars. Manually entered strings are FF terminated to max length + 1.
// - - Default OT (Japanese) names were padded with FF to len=6, so they always match manually entered names (no trash).
// - - Default OT (International) names from the character select screen can have trash bytes due to being un-padded (single FF end of string, saves ROM space).
// - - verification of Default OTs todo (if OT dirty, check if is default with expected trash pattern)
// - Nickname: the buffer has garbage RAM data leftover in the nickname field, thus it should be "dirty" usually.
// - Nicknamed: when nicknamed, the game fills the buffer with FFs then applies the nickname.
// For event encounters from GameCube:
@ -173,7 +186,7 @@ public static bool IsResetTrash(PK3 pk3)
public static bool IsTerminatedZero(ReadOnlySpan<byte> data)
{
var first = TrashBytes8.GetTerminatorIndex(data);
if (first == -1 || first >= data.Length - 2)
if (first == -1 || first >= data.Length - 1)
return true;
return !data[(first+1)..].ContainsAnyExcept<byte>(0);
}
@ -181,28 +194,93 @@ public static bool IsTerminatedZero(ReadOnlySpan<byte> data)
public static bool IsTerminatedFF(ReadOnlySpan<byte> data)
{
var first = TrashBytes8.GetTerminatorIndex(data);
if (first == -1 || first >= data.Length - 2)
if (first == -1 || first >= data.Length - 1)
return true;
return !data[(first + 1)..].ContainsAnyExcept<byte>(0xFF);
return !data[(first + 1)..].ContainsAnyExcept(Terminator);
}
/// <summary>
/// Checks if the <see cref="data"/> matches the pattern of a pre-filled array with terminators of count <see cref="preFill"/>.
/// </summary>
/// <param name="data">Raw text string to check</param>
/// <param name="preFill">Count of chars filled with terminator.</param>
/// <returns><see langword="true"/> if the text matches the pre-fill pattern.</returns>
public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
{
if (preFill == 0)
return IsTerminatedZero(data);
var first = TrashBytes8.GetTerminatorIndex(data);
if (first == -1 || first == data.Length - 2)
if (first == -1 || first >= data.Length - 1)
return true;
first++;
if (first < preFill)
{
var inner = data[first..preFill];
if (inner.ContainsAnyExcept(Terminator))
return false;
first = preFill;
if (first >= data.Length - 2)
if (first >= data.Length)
return true;
}
return !data[(first + 1)..].ContainsAnyExcept<byte>(0);
return !data[first..].ContainsAnyExcept<byte>(0);
}
// TRASH BYTES: New Game Default OTs
// Default OT names in International (not JPN) Gen3 mainline games memcpy 7 chars and FF from the "default OT name" table, regardless of strlen.
// Copied strings therefore contain trash from the next string entry that is encoded into the rom's string table.
// Below is a list of possible (version, language, trash) default OTs, as initialized by the game. An `*` is used to denote the terminator.
/// <summary>
/// Checks if the specified trash byte pattern matches a default trainer name pattern for the given game <see cref="version"/> and <see cref="language"/>.
/// </summary>
/// <remarks>Default trainer names in certain Generation 3 Pokémon games may include trailing bytes ("trash") due to how names are stored in the game's ROM.
/// This method checks if the provided pattern matches any of these known default patterns for the specified version and language.
/// </remarks>
public static bool IsTrashPatternDefaultTrainer(ReadOnlySpan<byte> trash, GameVersion version, LanguageID language) => version switch
{
GameVersion.R or GameVersion.S => IsTrashPatternDefaultTrainerRS(trash, language),
GameVersion.E => IsTrashPatternDefaultTrainerE(trash, language),
GameVersion.FR => IsTrashPatternDefaultTrainerFR(trash, language),
GameVersion.LG => IsTrashPatternDefaultTrainerLG(trash, language),
_ => false,
};
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
/// <remarks>Default OT names present in <see cref="GameVersion.R"/> and <see cref="GameVersion.S"/> based on the language of the game.</remarks>
public static bool IsTrashPatternDefaultTrainerRS(ReadOnlySpan<byte> trash, LanguageID language) => language switch
{
// TODO
LanguageID.English => trash switch
{
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xCE, 0xDC] => true, // SARA*Th
_ => false,
},
_ => false,
};
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
/// <remarks>Default OT names present in <see cref="GameVersion.E"/> based on the language of the game.</remarks>
public static bool IsTrashPatternDefaultTrainerE(ReadOnlySpan<byte> trash, LanguageID language) => language switch
{
// TODO
_ => false,
};
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
/// <remarks>Default OT names present in <see cref="GameVersion.FR"/> based on the language of the game.</remarks>
public static bool IsTrashPatternDefaultTrainerFR(ReadOnlySpan<byte> trash, LanguageID language) => language switch
{
// TODO
_ => false,
};
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
/// <remarks>Default OT names present in <see cref="GameVersion.LG"/> based on the language of the game.</remarks>
public static bool IsTrashPatternDefaultTrainerLG(ReadOnlySpan<byte> trash, LanguageID language) => language switch
{
// TODO
_ => false,
};
}

View File

@ -17,7 +17,7 @@ internal static void VerifyStatNature(LegalityAnalysis data, PKM pk)
return;
// Must be a valid mint nature.
if (!statNature.IsMint())
if (!statNature.IsMint)
data.AddLine(Get(Invalid, Misc, StatNatureInvalid));
}

View File

@ -1,5 +1,6 @@
using System;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.MoveHealState;
namespace PKHeX.Core;
@ -66,68 +67,124 @@ private void VerifyEntity(LegalityAnalysis data)
}
}
var expectHeal = Legal.IsPPUnused(pk) || IsPPHealed(data, pk);
var allowedStates = GetPermittedStatePP(data, pk);
for (int i = 0; i < pp.Length; i++)
{
// Sometimes the PP count will exceed (such as VC=>Bank); just flag it as invalid so the user knows they need to heal them.
// Technically that case is legal (game bug) only if they never move it from the box, but we want to inform the user.
var expect = pk.GetMovePP(moves[i], ups[i]);
var healed = pk.GetMovePP(moves[i], ups[i]);
var value = pp[i];
if (value > expect)
if (value > healed)
{
data.AddLine(GetInvalid(MovePPTooHigh_01, (ushort)(i + 1), (ushort)value));
else if (expectHeal && value != expect)
data.AddLine(GetInvalid(MovePPExpectHealed_01, (ushort)(i + 1), (ushort)value));
continue;
}
if (allowedStates == Any)
continue;
if (value == healed && (allowedStates == OnlyHealed || allowedStates.HasFlag(AllowHealed)))
continue;
if (value == 0 && (allowedStates == Only0 || allowedStates.HasFlag(Allow0)))
continue;
// Not Valid. Add a flag.
var (message, expect) = allowedStates switch
{
OnlyHealed => (MovePPExpectHealed_01, healed),
Only0 => (MovePPTooHigh_01, 0),
_ => (MovePPExpectHealed_01, healed), // just pick one of the expected states; heal is safe default.
};
data.AddLine(GetInvalid(message, (ushort)(i + 1), (ushort)expect));
}
}
private static bool IsPPHealed(LegalityAnalysis data, PKM pk)
private static bool IsFreshZATransferFromHOME_400(PA9 pk, EntityContext context)
{
if (data.IsStoredSlot(StorageSlotType.Party))
if (context != EntityContext.Gen9a)
return true;
var scale = pk.Scale;
if (pk.HeightScalar == scale && pk.WeightScalar == scale)
return true;
return false;
}
private static MoveHealState GetPermittedStatePP(LegalityAnalysis data, PKM pk)
{
if (Legal.IsPPUnused(pk))
{
if (pk is PA9 pa9 && IsFreshZATransferFromHOME_400(pa9, data.EncounterOriginal.Context))
return AllowHealedOr0; // HOME sets 0 PP for all moves. Healing / reassigning moves in ZA will heal individual indexes.
return OnlyHealed;
}
if (data.IsStoredSlot(StorageSlotType.Party))
return Any;
return data.SlotOrigin switch
{
StorageSlotType.Box or StorageSlotType.GTS or StorageSlotType.BattleBox => GetIsStoredHealed(pk, data.EncounterOriginal),
_ => false, // Deposited slots pass through party.
_ => Any, // Deposited slots pass through party.
};
}
/// <summary>
/// Checks if the format is expected to have the Pokémon healed to full PP.
/// </summary>
private static bool GetIsStoredHealed(PKM pk, IEncounterTemplate enc) => pk switch
private static MoveHealState GetIsStoredHealed(PKM pk, IEncounterTemplate enc) => pk switch
{
// Boxes accessible from anywhere; retain HP and PP
PK9 => false,
PK8 or PA8 or PB8 => false,
PB7 => false,
PK9 => Any,
PK8 or PA8 or PB8 => Any,
PB7 => Any,
// Don't heal PP when deposited
PK1 or PK2 => false,
PK6 or PK7 => false,
PK1 or PK2 => Any,
PK6 or PK7 => Any,
// Do heal after capture/deposit
SK2 => true,
CK3 or XK3 => true,
PK4 or RK4 or BK4 or PK5 => true,
SK2 => OnlyHealed,
CK3 or XK3 => OnlyHealed,
PK4 or RK4 or BK4 or PK5 => OnlyHealed,
// Check if the encounter has left the boxes after being acquired by the player
// only reachable by PK3?
_ => HasLeftBoxAfterAcquisition(pk, enc),
};
private static bool HasLeftBoxAfterAcquisition(PKM pk, IEncounterTemplate enc)
private static MoveHealState HasLeftBoxAfterAcquisition(PKM pk, IEncounterTemplate enc)
{
if (enc.Context != pk.Context)
return true; // Different context, assume it was traded and thus is not a wild->box
return OnlyHealed; // Different context, assume it was traded and thus is not a wild->box
if (pk.EVTotal != 0)
return true; // EVs are not possible direct from wild encounters
return OnlyHealed; // EVs are not possible direct from wild encounters
if (!Experience.IsAtLevelThreshold(pk.EXP, pk.PersonalInfo.EXPGrowth, out var current))
return true; // gained experience
return OnlyHealed; // gained experience
// Only scenario is if it was leveled up AND matches that exp threshold
if (pk.Format >= 3) // has met level
return pk.MetLevel != current;
return !enc.IsLevelWithinRange(current);
return pk.MetLevel != current ? OnlyHealed : Any;
return !enc.IsLevelWithinRange(current) ? OnlyHealed : Any;
}
}
[Flags]
public enum MoveHealState
{
None, // Invalid result.
// Intermixed states for individual move indexes:
Allow0 = 1 << 0, // A zero PP value is allowed.
AllowHealed = 1 << 1, // Expect PP to be fully healed.
AllowUsed = 1 << 2, // Any value in-between is allowed.
AllowHealedOr0 = Allow0 | AllowHealed, // Expect PP to be either fully healed or 0.
// Overall states for all move indexes:
OnlyHealed = 1 << 3, // Expect PP of all indexes to be fully healed.
Only0 = 1 << 4, // Expect PP of all indexes to be 0.
Any = Allow0 | AllowHealed | AllowUsed, // No expectation on PP values.
}

View File

@ -24,7 +24,7 @@ public override void Verify(LegalityAnalysis data)
if (pk.PID == 0)
data.AddLine(Get(Severity.Fishy, PIDZero));
if (!pk.Nature.IsFixed()) // out of range
if (!pk.Nature.IsFixed) // out of range
data.AddLine(GetInvalid(PIDNatureMismatch));
if (data.Info.EncounterMatch is IEncounterEgg egg)
VerifyEggPID(data, pk, egg);
@ -207,9 +207,9 @@ public static bool GetTransferEC(PKM pk, out uint ec)
return false;
}
// get the current shiny xor value, check against the previous 1:8192 scheme
var pid = pk.PID;
var tmp = pid ^ pk.ID32;
var XOR = (ushort)(tmp ^ (tmp >> 16));
var XOR = ShinyUtil.GetShinyXor(pid, pk.ID32);
// Ensure we don't have a shiny.
if (XOR >> 3 == 1) // Illegal, fix. (not 16<XOR>=8)

View File

@ -147,20 +147,23 @@ public static bool IsMarkValidAlpha(PKM pk, bool wasAlpha)
return true;
if (!wasAlpha)
return !m.RibbonMarkAlpha; // Shouldn't have the flag.
if (!HasEnteredHOME300(pk))
if (!HasEnteredHOME_Alpha(pk))
return true; // Can be either state -- only HOME sets the flag.
return m.RibbonMarkAlpha; // Should have the flag.
}
private static bool HasEnteredHOME300(PKM pk)
private static bool HasEnteredHOME_Alpha(PKM pk)
{
// Mark is only set by HOME ingesting the data for the first time.
// Before HOME 3.0.0, this mark was never set.
// Could be okay as Gen8 format -- don't bother checking for "must have visited HOME 3.0.0+".
if (pk is IHomeTrack { HasTracker: false })
return false; // Hasn't been transferred to HOME yet.
// Could be okay as a Gen8* format -- don't bother checking for "must have visited HOME 3.0.0+".
if (pk.LA && pk is PK8 or PB8 or PA8)
return false; // Could have been moved prior to the HOME 3.0.0 update.
// Before HOME 4.0.0, this mark was only set when you moved it in for the first time.
// In HOME 4.0.0, the mark is set by HOME opening your save data and saving, modifying properties without you touching them.
if (pk.ZA && pk is IScaledSize { HeightScalar: 0 }) // Alphas would update to 255-255-255 scale.
return false; // Might not have touched HOME yet.
return true;
}
@ -169,7 +172,7 @@ private static bool HasEnteredHOME300(PKM pk)
/// </summary>
public static bool IsMarkValidAlpha(IEncounterTemplate enc, PKM pk)
{
var expect = enc is IAlphaReadOnly { IsAlpha: true } && enc.Context != EntityContext.Gen9a; // TODO ZA HOME
var expect = enc is IAlphaReadOnly { IsAlpha: true };
return IsMarkValidAlpha(pk, expect);
}

View File

@ -184,9 +184,6 @@ private void VerifyHOMETracker(LegalityAnalysis data, PKM pk)
// - Transfer a 0-Tracker pk to HOME to get assigned a valid Tracker via the game it originated from.
// - Don't make one up.
}
if (pk.ZA != pk is PA9) // TODO: ZA HOME Compatibility - flag in/out transfers for now.
data.AddLine(GetInvalid(TransferBad));
}
public void VerifyVCEncounter(PKM pk, IEncounterTemplate original, EncounterTransfer7 transfer, LegalityAnalysis data)

View File

@ -41,7 +41,7 @@ public override ReadOnlySpan<byte> Write()
{
// Ensure PGT content is encrypted
var clone = new PCD(Data.ToArray());
clone.Gift.VerifyPKEncryption();
clone.Gift.VerifyGiftEncryption();
return clone.Data;
}

View File

@ -45,17 +45,16 @@ public override byte Ball
public int ItemSubID { get => ReadInt32LittleEndian(Data[0x8..]); set => WriteInt32LittleEndian(Data[0x8..], value); }
public int PokewalkerCourseID { get => Data[0x4]; set => Data[0x4] = (byte)value; }
private Span<byte> DataGift => Data.Slice(8, PokeCrypto.SIZE_4PARTY);
public PK4 PK
{
get => field ??= new PK4(Data.Slice(8, PokeCrypto.SIZE_4PARTY).ToArray());
get => field ??= new PK4(DataGift.ToArray());
set
{
field = value;
var data = value.Data;
bool zero = !data.ContainsAnyExcept<byte>(0); // all zero
if (!zero)
data = PokeCrypto.EncryptArray45(data);
data.CopyTo(Data[8..]);
field = value.Clone(); // cache the PK4 for future use
value.Data.CopyTo(DataGift);
VerifyGiftEncryption();
}
}
@ -63,29 +62,27 @@ public override ReadOnlySpan<byte> Write()
{
// Ensure PGT content is encrypted
var clone = new PGT(Data.ToArray());
clone.VerifyPKEncryption();
clone.VerifyGiftEncryption();
return clone.Data;
}
/// <summary>
/// Double-checks the encryption of the gift data for Pokémon data.
/// Double-checks the encryption of the gift data.
/// </summary>
/// <returns>True if data was encrypted, false if the data was not modified.</returns>
public bool VerifyPKEncryption()
public bool VerifyGiftEncryption()
{
if (GiftType is not (Pokémon or PokémonEgg))
return false; // not encrypted
if (ReadUInt32LittleEndian(Data[(0x64 + 8)..]) != 0)
return false; // already encrypted (unused PK4 field, zero)
EncryptPK();
return true;
}
private void EncryptPK()
{
var span = Data.Slice(8, PokeCrypto.SIZE_4PARTY);
var ekdata = PokeCrypto.EncryptArray45(span);
ekdata.CopyTo(span);
var gift = DataGift;
var isEmpty = !gift.ContainsAnyExcept<byte>(0); // all zero
if (isEmpty) // shouldn't ever be empty, just return if so.
return false;
if (PokeCrypto.IsEncrypted45(gift)) // unused PK4 ribbon bits
return false;
PokeCrypto.Encrypt45(gift);
return true;
}
public GiftType4 GiftType { get => (GiftType4)CardType; set => CardType = (byte)value; }

View File

@ -39,6 +39,13 @@ public override int CardID
set => WriteUInt16LittleEndian(Data[0x8..], (ushort)value);
}
public bool IsHOMEGift => CardID >= 9000;
public int HomeBaseIV => CardID switch
{
>= 9031 and <= 9033 => 20,
_ => 0,
};
public byte RestrictVersion { get => Data[0xE]; set => Data[0xE] = value; } // 0x01 = ZA only (only one game in this Context, so always ZA).
public byte CardFlags { get => Data[0x10]; set => Data[0x10] = value; }
public GiftType CardType { get => (GiftType)Data[0x11]; set => Data[0x11] = (byte)value; }
@ -302,7 +309,7 @@ public bool CanBeAnyLanguage()
public bool CanHaveLanguage(int language)
{
if (language is < (int)LanguageID.Japanese or > (int)LanguageID.ChineseT)
if (language is < (int)LanguageID.Japanese or > (int)LanguageID.SpanishL)
return false;
if (CanBeAnyLanguage())
@ -326,7 +333,7 @@ public bool CanHaveLanguage(int language)
private static int GetLanguageIndex(int language)
{
var lang = (LanguageID) language;
if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT)
if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.SpanishL)
return (int) LanguageID.English; // fallback
return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2;
}
@ -360,7 +367,7 @@ public override string OriginalTrainerName
get => GetOT(Language);
set
{
for (int i = 1; i <= (int)LanguageID.ChineseT; i++)
for (int i = 1; i <= (int)LanguageID.SpanishL; i++)
SetOT(i, value);
}
}
@ -463,16 +470,6 @@ public override PA9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
pk.IsNicknamed = GetIsNicknamed(language);
pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation);
// No ribbons set.
// for (var i = 0; i < RibbonBytesCount; i++)
// {
// var ribbon = GetRibbonAtIndex(i);
// if (ribbon == RibbonByteNone)
// continue;
// pk.SetRibbon(ribbon);
// pk.AffixedRibbon = (sbyte)ribbon;
// }
SetPINGA(pk, criteria, pi);
SetMoves(currentLevel, pk, pi);
pk.HealPP();
@ -481,6 +478,18 @@ public override PA9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
SetEggMetData(pk);
pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship;
if (IsHOMEGift)
{
for (var i = 0; i < RibbonBytesCount; i++)
{
var ribbon = GetRibbonAtIndex(i);
if (ribbon == RibbonByteNone)
continue;
pk.SetRibbon(ribbon);
}
pk.Scale = pk.HeightScalar = pk.WeightScalar = Scale == 256 ? (byte)rnd.Next(256) : (byte)Scale;
}
pk.ResetPartyStats();
pk.RefreshChecksum();
return pk;
@ -515,6 +524,17 @@ private void SetEggMetData(PA9 pk)
}
private void SetPINGA(PA9 pk, EncounterCriteria criteria, PersonalInfo9ZA pi)
{
if (IsHOMEGift) // Do not use LumioseRNG for HOME gifts
{
pk.Nature = pk.StatNature = criteria.GetNature((sbyte)Nature == -1 ? Nature.Random : Nature);
pk.Gender = criteria.GetGender(Gender, pi);
var av = GetAbilityIndex(criteria, AbilityType);
pk.RefreshAbility(av);
SetPID(pk);
SetIVs(pk);
}
else
{
var param = GetParams(pi);
ulong init = Util.Rand.Rand64();
@ -525,6 +545,14 @@ private void SetPINGA(PA9 pk, EncounterCriteria criteria, PersonalInfo9ZA pi)
if (PIDType is not (ShinyType8.Never or ShinyType8.Random))
pk.PID = GetPID(pk, PIDType);
}
}
private int GetAbilityIndex(in EncounterCriteria criteria, int type) => type switch
{
00 or 01 or 02 => type, // Fixed 0/1/2
03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H
_ => throw new ArgumentOutOfRangeException(nameof(type)),
};
public override AbilityPermission Ability => AbilityType switch
{
@ -564,6 +592,39 @@ private static uint GetAntishiny(ITrainerID32 tr)
return pid;
}
private void SetPID(PA9 pk)
{
pk.PID = GetPID(pk, PIDType);
}
private void SetIVs(PA9 pk)
{
Span<int> finalIVs = stackalloc int[6];
GetIVs(finalIVs);
var ivflag = finalIVs.IndexOfAny(0xFC, 0xFD, 0xFE);
var rng = Util.Rand;
if (ivflag == -1) // Random IVs
{
for (int i = 0; i < finalIVs.Length; i++)
{
if (finalIVs[i] > 31)
finalIVs[i] = rng.Next(32);
}
}
else // 1/2/3 perfect IVs
{
int IVCount = finalIVs[ivflag] - 0xFB;
do { finalIVs[rng.Next(6)] = 31; }
while (finalIVs.Count(31) < IVCount);
for (int i = 0; i < finalIVs.Length; i++)
{
if (finalIVs[i] != 31)
finalIVs[i] = IsHOMEGift ? HomeBaseIV : rng.Next(32); // HOME ZA-starters gifts have 20 in non-perfect IVs
}
}
pk.SetIVs(finalIVs);
}
public override bool IsMatchExact(PKM pk, EvoCriteria evo)
{
if (!IsEgg)
@ -626,7 +687,7 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
if (MetLevel != 0 && MetLevel != pk.MetLevel) return false;
if ((Ball == 0 ? 4 : Ball) != pk.Ball) return false;
if (OTGender < 2 && OTGender != pk.OriginalTrainerGender) return false;
if (Nature != Nature.Random && pk.Nature != Nature) return false;
if (Nature.IsFixed && pk.Nature != Nature) return false;
if (Gender != 3 && Gender != pk.Gender) return false;
if (pk is IScaledSize s)
@ -641,6 +702,20 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
return true;
if (IsHOMEGift)
{
if (pk.FlawlessIVCount != FlawlessIVCount)
return false; // HOME ZA-starters have non-perfect IVs to 20, so IVs at 31 can't exceed the flawless count.
Span<int> IVs = stackalloc int[6];
pk.GetIVs(IVs);
foreach (var iv in IVs)
{
if (iv != 31 && iv != HomeBaseIV)
return false;
}
}
// PID Types 0 and 1 do not use the fixed PID value.
// Values 2,3 are specific shiny states, and 4 is fixed value.
// 2,3,4 can change if it is a traded egg to ensure the same shiny state.
@ -676,11 +751,11 @@ private bool IsMatchTrainerName(ReadOnlySpan<byte> trainerTrash, PKM pk)
protected override bool IsMatchDeferred(PKM pk) => false;
protected override bool IsMatchPartial(PKM pk) => TryGetSeed(pk, out _) != SeedCorrelationResult.Success;
protected override bool IsMatchPartial(PKM pk) => !IsHOMEGift && TryGetSeed(pk, out _) != SeedCorrelationResult.Success;
#region Lazy Ribbon Implementation
private static bool HasRibbon(RibbonIndex _) => false; // HasRibbon(index); // ZA is hard-coded to never set ribbons, so we need to return false for validation/setting.
private bool HasRibbon(RibbonIndex index) => IsHOMEGift && this.GetRibbonIndex(index);
public bool RibbonEarth { get => HasRibbon(Earth); set => this.SetRibbonIndex(Earth, value); }
public bool RibbonNational { get => HasRibbon(National); set => this.SetRibbonIndex(National, value); }
public bool RibbonCountry { get => HasRibbon(Country); set => this.SetRibbonIndex(Country, value); }

View File

@ -581,7 +581,17 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
}
if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context))
{
// Small bypass for Greninja-Ash when mega evolved (normal and Ash map to the same Mega-3, managed by game)
if (this is { Species: (ushort)Core.Species.Greninja, Form: 1 } && pk is { Context: EntityContext.Gen9a, Form: 3 })
{
// Allow
}
else
{
return false;
}
}
if (IsEgg)
{

Some files were not shown because too many files have changed in this diff Show More