Compare commits

..

1427 Commits

Author SHA1 Message Date
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
Kurt
d24d227df4 Push more trashbyte rework 2026-03-06 22:09:40 -06:00
Kurt
5a75fe4b89 Add delete menu item for Folder Browser 2026-03-06 12:22:41 -06:00
Kurt
49d9467d3c Update ParseSettings.cs 2026-03-05 22:12:27 -06:00
Carbonara
065d329546
Adjust event flag and constant categories (#4744)
* Adjust gen 2 flag categories

GSC:
- Move bedroom accessories from * (UsefulFeature) to b (currently unassigned, but ideally a new category dedicated to bedroom accessories)

C:
- Move GS Ball flags from */r (Rebattle) to e (EventEncounter)
- Move Odd Egg flag from r (Rebattle) to g (GiftAvailable)
- Add missing Mystery Gift item line to the Japanese, Spanish, Korean and Chinese translations (untranslated)

* Adjust RSFRLG event categories

RS:
- Move the HM 08 miss flag from Rebattle to StoryProgress
- Move the Fossil flags from Rebattle to GiftAvailable
- Move the Badges from UsefulFeature to StoryProgress
- Move the Pokelot and S. S. Tidal constants from StoryProgress to Misc
- Move the Professor Birch constant from Misc to StoryProgress

FRLG:
- Move the Lapras, Magikarp, Old Amber, Eevee, Trades and Fossil flags from Misc to GiftAvailable
- Move the Shown Mystic & Aurora Ticket flags from Misc to EventEncounter
Note: regular items were already classified into the nonexistent i section, keeping it for items
- Make the Spanish, Simplified Chinese and Traditional Chinese use the same lines than the other languages. If the lines they had translated existed in the new format, or were close enough, I reused those lines, otherwise they will need to be retranslated.

* Adjust E event categories

Gen 3 E
- Move Badges and Frontier Pass flags from UsefulFeature to StoryProgress
- Move Hidden items flags from Rebattle to HiddenItem
- Move Items flags from Rebattle to Item
- Move don't spawn flags from Rebattle to Misc unless the current category makes sense
- Move Items from Rebattle to Item
- Move Pokelot & S. S. Tidal constants from StoryProgress to Misc
- Move Professor Birch constant from Misc to StoryProgress

* Adjust Spanish and Chinese E flags

Same thing than with FRLG

* Make the amount of lines be consistent

+ Fix a line jump typo in the French DP flags

* Adjust remaining events

Gen 4 DP:
- Dialga/Palkia moved from StoryProgress to Rebattle
- Hidden items moved from StoryProgress to HiddenItem
- Items moved from StoryProgress to Item
- Trendy phrase moved from StoryProgress to Useful Feature

Gen 4 PT:
- Hidden items moved from StoryProgress to HiddenItem
- Items moved from StoryProgress to Item
- Trendy phrase moved from StoryProgress to Useful Feature
- Togepi moved from Rebattle to GiftAvailable

Gen 4 HGSS:
- Spiky-eared Pichu, Kanto Starters, Togepi Egg moved from Rebattle to GiftAvailable

Gen 5 BW:
- Zorua events moved from StoryProgress to EventEncounter
- Daily Royal Unova & Fossil moved from StoryProgress to Useful feature
- Darmanitan moved from GiftAvailable to Rebattle

Gen 5 B2W2:
- Daily Royal Unova & Fossil moved from StoryProgress to Useful feature

Gen 6 XY:
- Super Unlocked moved from Misc to Useful Feature
- Statuette moved from Misc to Achievement

Gen 6 ROSA:
- Items moved from StoryProgress to Item
2026-03-05 21:57:47 -06:00
Carbonara
93b9481393
Shorten text too long to be displayed in the French translation (#4745) 2026-03-05 21:57:33 -06:00
Kurt
244b34b8d3 Misc translatable util update
Allow EntitySearchSetup to be translated (rearrange the initialization, no need to retain/defer).
Rename Gen9a's gender label (previously blacklisted as an "auto-updating" label).

Other gender/Stat labels currently blacklisted in DevUtil probably need to get refactored to be enums/etc, but I currently lack the time/patience to understand those editors well enough to properly support the refactoring needed.

json exports: add newline at end to match the default editorconfig settings and general convention. we don't want textfiles loading in as a string[] with an empty string last entry.
2026-03-05 21:57:08 -06:00
Kurt
3e33f0fc2e Add options to sav3 accessor 2026-03-03 23:12:45 -06:00
Professor Dirty
3e33521796
Update CHS translation (#4743) 2026-03-03 08:14:30 -06:00
Kurt
79a08822ea FR/LG VC: Handle unobtainable balls
https: //github.com/kwsch/PKHeX/pull/4735#issuecomment-3986152531
Co-Authored-By: Carbonara <108797333+Mimigris@users.noreply.github.com>
2026-03-03 00:35:18 -06:00
Easy World
85ad6495e6
Update zh-Hans translation (#4742) 2026-03-02 21:39:56 -06:00
Carbonara
04c2063791
Translate remaining lines of the program to French (#4735) 2026-03-02 20:12:01 -06:00
Kurt
dd0d1fc07a Misc dex state fixes
Closes #4739
Closes #4740
Closes #4741

Co-Authored-By: Michael Bond <michael@bondcodes.com>
2026-03-02 17:36:24 -06:00
间辞
c64bc65359
Add files via upload (#4738) 2026-03-02 11:17:20 -06:00
Kurt
8587a88723 Update SAV_SimplePokedex.cs 2026-03-02 00:11:38 -06:00
Kurt
e56226f046 Single row select
Previously allowed cells and allowed multiple to be selected, resulting in some issues if users selected multiple cells and tried to trigger an open via contextmenu opening.
2026-03-01 23:16:04 -06:00
Kurt
6e48856bec Handle initial OT trash bytes for enc3->pk3 2026-03-01 22:42:15 -06:00
Kurt
51a012ff78 Add x/y coordinates for SAV3 2026-03-01 22:26:22 -06:00
Kurt
bd9f64b07e Remove duplicated encounters
Previously would recognize a LeafGreen Scyther from Celadon City as valid ;D
2026-03-01 22:12:54 -06:00
Kurt
b1dd981537 Update MiscVerifierG3.cs
eggs fixed
2026-03-01 22:09:25 -06:00
Kurt
553f154657 Add search box to flag/work row search 2026-03-01 19:59:57 -06:00
Kurt
b6eb0745a3 Add sanity check for enc gender request
If you set criteria to Male, and request to generate a Nidoran-F wild encounter, ofc the program will loop forever.
Oftentimes, users won't be looking at the criteria tab, and can stumble upon this accidentally.
Prevent the freeze entirely by just sanity checking and discarding the user's input if it is impossible.
2026-03-01 12:57:47 -06:00
Kurt
f382291de4 Improve translation of Extra Slots
SAV tab shows a bunch of extra slots from miscellaneous sources. The previous logic was a little clunky with fake labels; rewrite how it works so it's a little more transparent.

Misc is no more; I've created enum members with more descriptive names.

#4735
2026-03-01 09:58:40 -06:00
间辞
df39aff5e9
Add files via upload (#4737) 2026-03-01 08:35:04 -06:00
Kurt
20a92f533b Split event flag/work groups to tabs
Closes #4719

More groups can be added to the enum, and re-defined via their type-char column.
Updating translations will automatically add those types to the list of translatables.

Fixes the Dark Mode bug where the first tab of the Event flag/work editor (LGPE) didn't respect dark mode; now that all all event editors are sub-tabbed, we use the workaround present in all (on shown flip back to the first tab).
2026-03-01 00:00:34 -06:00
Kurt
b93d57cc9a FR/LG VC: handle TM/Tutors
Select a primary/secondary source verifier for better learn indication; not really worth doing this in mainline.
2026-02-28 15:09:59 -06:00
Kurt
2939bfae48 FR/LG VC: trash byte checks, reflow dex editor
a little more ergonomic in dex editor (size increased)

in-game trades now correctly allow initial contest stats, and their special handling for OT Trash bytes.
2026-02-28 13:32:12 -06:00
间辞
0d96d75b7a
Update CHS translations (#4736)
* Add files via upload

* Add files via upload
2026-02-28 08:25:24 -06:00
Kurt
8f0672b8c5 Update GiveAll for FR/LG VC, dex edit clean flags
When/if RSE added, these workarounds will be deleted.
2026-02-27 22:57:07 -06:00
Kurt
9420dcf44d Inventory: don't GiveAll unreleased items
Wasn't implemented for Gen3-5 storages
2026-02-27 22:46:31 -06:00
Kurt
bde5729883 FR/LG VC: disallow unavailable held items
Also ban Berry Juice from being released in mainline

fix casting issue
2026-02-27 18:47:04 -06:00
Kurt
fa0ac2a9ab FR/LG VC: flag unavailable evolutions/eggs
Need to check for traded eggs hatched in RSE as well; those must pass the first check based on their assigned version value.
2026-02-27 17:21:31 -06:00
Kurt
c037829b29 Initial gen3 virtual console checks
Disables branching when virtual console is the current save file
2026-02-27 13:26:05 -06:00
Kurt
5c4d27f7e4 Update 26.02.27 2026-02-27 09:49:09 -06:00
Carbonara
5c9f97b7fd
Translate FRLG flags to French (#4733)
Also contains the following changes:

French Emerald flags:
- Upper casing consistency for a reused line

English FireRed and LeafGreen flags:
- Fix Snorlax being called by its German name Relaxo
- Fix the Dotted Hole being misspelled in some places as Dotte Hole
- Change "Camper (Male)" and "Camper (Female)" in the unused flags to use the proper trainer class terms ("Camper (Female)" is Picnicker in English, meaning there is no need to specify that it is "Camper (Male)
- Alter the unused Interviewers line (it's supposed to refer to the Interviewers class from Ruby and Sapphire, which is always plural)
- Fix some typos in the trainer names (Psychic Tyron spelled as Pyschic Tyorn, Cooltrainer Brooke as Cooltrainer Brooker)
- Fix the Swimmer class missing the w in one instance
- Change Pkmn Ranger to Pokémon Ranger for consistency
- Remove unintended double spaces
2026-02-27 09:07:24 -06:00
sora10pls
ea36292994 Unban Garchompite Z 2026-02-27 08:39:57 -05:00
Kurt
7fad9a0c47 Add special scan pity counter property 2026-02-26 22:48:21 -06:00
Ka-n00b
2f456fceb3
Update FRLG Event Flags and some translations (#4732)
* Update lang_ko.txt

* Update lang_zh-Hant.txt

* Update lang_ko.txt

* Update const_frlg_en.txt

* Update const_frlg_es-419.txt

* Update const_frlg_es.txt

* Update const_frlg_fr.txt

* Update const_frlg_ja.txt

* Update const_frlg_zh-Hans.txt

* Update lang_ko.txt

* Update lang_ko.txt

* Update lang_ko.txt
2026-02-26 11:26:00 -06:00
Kurt
306c1329ed Add gameversion for Champions (53) 2026-02-25 23:25:32 -06:00
Kurt
07a826292c Update SaveBlockAccessor9ZA.cs 2026-02-25 01:20:15 -06:00
Kurt
3371c791ef Add HyperspaceZones9a
No GUI at this time, but seed can be changed via Block editor
2026-02-25 01:15:37 -06:00
abcboy101
2559f96439
Reset clothing when changing gender in ZA (#4728) 2026-02-23 08:35:05 -06:00
Kurt
2210068013 Revise shiny stars, only show squares in Gen8
They only exist in Gen8, no point showing in other contexts.
2026-02-22 23:33:00 -06:00
Kurt
31b7b7f723 Update BatchInfo.cs
https://github.com/kwsch/PKHeX/discussions/4725#discussioncomment-15891316
2026-02-22 23:22:05 -06:00
Kurt
364e014848 PA8: more initial move mastery suggest fixes
Evolved mon's with different learnsets need to use the initial encounter data species-form rather than the most-evolved, as some can be different.
https://github.com/kwsch/PKHeX/discussions/4725#discussioncomment-15889980
2026-02-22 12:22:53 -06:00
Kurt
3fc2971df1 SCBlock: Determine exact size on serialize
No need to estimate, just loop through. Prevents a large-object allocation for the stream.
Not sure if it is worth refactoring memorystream/binarywriter to just be raw spans to eliminate that overhead. Not that this is even a hot path, or that BinaryWriter adds much of anything besides moving stack logic to the object.
2026-02-22 12:22:07 -06:00
Kurt
d690f1c5d3 Check unsaved entity on sav export
Add setting to skip the unsaved entity check
Add setting to skip the overwrite? prompt and always call Save As

Change Overwrite prompt to have distinct buttons rather than rows that can be mis-clicked.

fix some comments/strings from Pokemon=>Pokémon

add some underline shortcut key for main menu for English translation
2026-02-22 11:11:16 -06:00
Kurt
1aedb012ac Add box popout button on left side of box control
Previously "hidden" in the shortcut keys by clicking on the Box tab, this makes it more discoverable. Old hotkeys still retained.

fixed hidden 0x200E char in Fashion button text (ancient typo)

Update Program.cs
2026-02-21 21:20:12 -06:00
Kurt
395bc1b1e9 Misc GUI tweaks
database Reset filters now auto-sizes, tabs are now taller
box popup now shows the switch-view button as an actual button rather than a flat image
2026-02-21 19:41:05 -06:00
Kurt
11c4fe446e Fix met location highlight on startup
Finally found the right place to put this
2026-02-21 19:38:02 -06:00
Carbonara
ddba4dae44
Translate the RSE flags to French (#4727)
* Add French translation for the RS flags

* Translate the Emerald flags in French

- Fix Peeko, Kindler Cole, Triathlete Talia and Psychic Mariela being misspelled in the English config
- Fix Aroma Lady Rose rematches being incorrectly listed for Aroma Lady Violet
- Fix Handsome the Zigzagoon being referred as being a guy
- Fix some obvious typos
- Changed the wording of some lines the flags_rs_fr file
2026-02-21 17:13:38 -06:00
Kurt
2efa19e5e3 Refactor batch editor to smaller components
Did something similar for NHSE years ago. Allows for much easier reuse elsewhere and clearer usage.
2026-02-21 00:22:32 -06:00
Kurt
0b42f57534 PA8: More tweaks to mastery setting 2026-02-20 23:21:33 -06:00
Kurt
0757ca3a5d PA8: Skip move purchase if can naturally learn
https://github.com/kwsch/PKHeX/discussions/4725
Enhances the .MoveMastery=$suggestAll
2026-02-20 09:22:11 -06:00
Kurt
6f9daaed04 Small tweaks to HGSS ball check
small lol
would need fully implemented pal park trash byte checks, big sad
leave stuff stubbed for now, can clamp down later.

restrict some method sigs for IEncounterTemplate (rather than more-derived IEncounterable) for consistency
2026-02-20 01:36:46 -06:00
Kurt
f14cfaf08d Unban Blazikenite
Season 7 has begun
2026-02-19 09:39:01 -06:00
Kurt
b6ae27e4e8 Add optional delegate for mid-batch-modify
Currently unused by everything; allows a compiled function to be run between the Filters and Modifiers
2026-02-19 09:37:25 -06:00
Carbonara
13fc0cdfeb
Update the French readme file (#4726) 2026-02-18 16:55:47 -06:00
Kurt
aab826ef13 Misc tweaks
Rename args for better clarity
Fix invalid pokerus on gen2 unit test case (apparently pkrs in gen2 was unchecked until recent miscverifier fix)
2026-02-18 07:46:39 -06:00
ShadowMario3
a9f449ff01
Update ItemStorage3E.cs (#4723)
Fix PC Storage for Emerald. Unlike R/S, key items aren't allowed.
2026-02-17 14:48:27 -06:00
Kurt
29a08bf988 Allow box dumper to retain Main control
Allows for quickly flipping current boxes.
2026-02-16 23:33:51 -06:00
Kurt
53c684a223 Add numeric operators to StringInstruction set
okay can we stop asking for this now? your "random fudging of EXP" to make it seem more legit really is ugly, and doesn't fool anyone lol
2026-02-16 23:14:38 -06:00
Kurt
f6bae2b8d7 Improve hash inflation of NSO saves
Wow such a bottleneck in the application (literally nothing calls this, but it was fun to optimize)
2026-02-16 23:13:41 -06:00
Carbonara
1d25b78a19
Translate more flags and constants to French (#4718)
- Write événement as évènement for consistency (both spellings are valid, but évènement is the one that is used in recent Pokémon games)
- Fix Réfrigérateur being written as Réfrigirateur
Translate constants for Ruby/Sapphire, FireRed/LeafGreen, Emerald, Diamond/Pearl, Platinum, X/Y, OR/AS
Translate flags for Diamond/Pearl, Platinum, X/Y
2026-02-15 22:54:58 -06:00
Kurt
ceb420a2a1 Misc gui tweaks
Only show Square shiny in Gen8 context (sw/sh only) to avoid confusion
Fix method name typo
Close subforms in reverse to avoid allocating a temp list
Close splash screen entirely async rather than dual Task.Run
Translate the entirety of the EntitySearchSetup (comparator button/menu now translates)
Launch box popup to the right of the main form, like the Search behavior
Fix dark mode coloring of popup box editor/group viewer images
Fix dark mode RichTextBox retaining border when it should be removed (white was annoying); was early-returning due to satisfying TextBoxBase
2026-02-15 02:19:09 -06:00
Kurt
9792455f34 Refactor to use Context over Generation
Generation was always more weak; am I paranoid about potential VC3? maybe
Better indicates the move source for LGPE exclusive moves, etc.
2026-02-15 02:15:50 -06:00
Kurt
387c254aa4 Split MiscVerifier into more focused checkers
Fixes logic flow for Stadium legality check (wasn't even called)
2026-02-15 02:12:39 -06:00
9B1td0
1e5663433a
Add EUIC 2026 Yuma Kinugawa's Hisuian Typhlosion date (#4716) 2026-02-13 07:42:29 -06:00
间辞
1ec0b22a0a
Add files via upload (#4715) 2026-02-12 22:18:05 -06:00
Kurt
782ee643d6 Replace box nav arrows with images 2026-02-11 23:43:54 -06:00
Kurt
7f51c125bd Add search nav buttons to panel
TopMost => Owner
pressing enter applies search (except if entering text to advanced, or focused on a button)

Co-Authored-By: RandomGuy <69272011+RandomGuy155@users.noreply.github.com>
2026-02-11 23:22:49 -06:00
Kurt
4ecd51e826 Add reverse search, remember result
ctrl+shift => reverse
shift => forwards
2026-02-11 22:59:33 -06:00
Kurt
3fa6ab9c23 Refactor Format to search Context instead
Increase size of left / right buttons to restore << >>
might change them to be icons later
2026-02-11 22:21:01 -06:00
Kurt
20905cbe67
Add a search interface for visually filtering all slots (#4712)
* Add slot search to box editor
Alt-Click: Clears the current search.
Shift-Click: Jump to the next box with a result.
2026-02-09 22:03:18 -06:00
Carbonara
0f8321cda4
Translate SMUSUSM flags and constants to French (#4713)
* Translate SMUSUSM flags and constants to French

* Adjust the text of some SMUSUM flags and constants

Put consistency for some lines between Ultra-Sun/Ultra-Moon and Sun/moon:
- Use the Hall of Fame/Magearna line from USUM for both games (same meaning, and no reason for it to be different; the Hant line was using the Hans line for USUM, so adjusted too)
- Adjust the name of Youngster Tristan to constantly be referred as such (Korean doesn't use the title if I'm not mistaken, but the naming is consistent so not an issue)

- Change the League Fangirls event line: as far as I understand, the fangirls have 3 status (not yet present, here to give Sweet Hearts, and Sweet Hearts given/gone). Logically as such, they are only here after 2 title defenses have been done, and do not appear if only a single title defense has been done.
2026-02-08 08:55:57 -06:00
Kurt
09d7fd9e31 Minor clean
Remove unused usings from bag refactor
remove unnecessary suppression (resharper fixed the ConstantExpected trickle up)
fix gen6/7 timestamp previous offset (has been broken for 6.5 years) 1b028198ad (diff-7e597cadc592f504e9105ba631f99ef9e1fe27ea9becbe191c15c00daa3272f2L211)
2026-02-08 01:22:21 -06:00
Kurt
dd1b55cb6a Update EncounterStatic3XD.cs
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/35/#findComment-299242
2026-02-07 16:24:21 -06:00
Kurt
51711bb659 Extract entity filters UserControl, add Nickname
Introduce nickname searching and a reusable EntitySearchControl UI.
SearchSettings: add Nickname property, centralize search predicate creation
Add SatisfiesFilterNickname that reads the PKM nickname (stackalloc buffer) and performs a case-insensitive substring match.
2026-02-07 02:30:03 -06:00
Kurt
0982e54dac Revise dppt/hgss ball value check: Ranger Manaphy 2026-02-07 00:32:19 -06:00
Kurt
3ef46268e9 Indicate invalid PP(ups) count on localize
Viewing the invalid mon will have the UI fix it, so at least it gives some clarity as to what is actually being flagged.
VC->Bank is the big offender here.
2026-02-05 10:04:32 -06:00
Kurt
c5b7eb4c7d Bag: reset quantity on selecting (None)
adds validation for the Item select column as well.
Validation for item=0 requiring count=0.
2026-02-05 04:33:15 -06:00
Kurt
916521f906 Keep 5 digits of text entry as enough 2026-01-31 22:26:42 -06:00
Kurt
91ac18dd34 Revise Inventory edit count entry
Previous: limited to log10(max) characters
Now: cell changed -> parse/check against item count max & replace if exceeds.

Let the validation run even for Removing all items, why not?

Resolves: allows manual entry of >=1000 Mega Shards (previous release wouldn't clamp to 999, at least).
2026-01-31 22:12:23 -06:00
Kurt
5efe8b05ea Update 26.01.31 2026-01-31 20:41:14 -06:00
Carbonara
4a3157a8a2
Add French translation for HGSS script (#4707)
- Fix School Kid Torin being listed as a Camper (English, Spanish and Korean, other languages already fixed it)
- Replace "Latias/Latios" by "Lati@s" in the HGSS flags for consistency (English only)
2026-01-31 10:34:02 -06:00
Carbonara
d266ec7b5f
Unban Swampertite (#4706)
The Swampertite is now available since the Season 6 release that occurred on Thursday.
2026-01-31 06:46:58 -06:00
Kurt
e6edb043c4
PlayerBag Abstraction (#4705)
* Refactor bag interactions

Still need to normalize the offsets for some of the games so that init-from-span can be used on un-padded RAM dumps.

* Convert offsets to relative, minor clean

b2w2 & xy MyItem type now returns the more-derived type for clarity
2026-01-30 16:17:56 -06:00
Kurt
11c0f86d80 Update SAV_BoxList.cs 2026-01-29 16:58:36 -06:00
Kurt
f816b06d97 Update check for Barb Barrage evolution move
Closes #4698
2026-01-23 23:41:02 -06:00
Kurt
ad96b048b2 ShowdownSet: Parse wrong-ordered EVs
Previously were ignored.

Thanks Claude Opus 4.5, it 1-shot the entire thing from my detailed prompt & unit test follow up request.
I added a skip-blank line for ParseLines when people import a set with a trailing newline. Rather than a blank "invalid line length {0}"
2026-01-23 16:41:16 -06:00
Kurt
fe32739494 Update 26.01.22 2026-01-22 19:07:33 -06:00
Kurt
106d09c74f SIZE_G9ZA_201 2026-01-21 20:07:39 -06:00
间辞
d9c7980fd3
Update CHS translations (#4694) 2026-01-14 09:44:06 -06:00
abcboy101
74ef6d7378
Validate Odd Egg OT when still an Egg (#4693) 2026-01-13 23:13:39 -06:00
Kurt
c7b5777068 Allow settings tab text to be translated
Remove unnecessary selection on launch (winforms bug?)
Increase first column width so OT Version doesn't wrap to 2 lines.

Remove some unused usings in other files (a result of color repointing to WinFormsUtil)

ty @randomguy155 for the OnShown workaround

Co-Authored-By: RandomGuy <69272011+RandomGuy155@users.noreply.github.com>
2026-01-13 01:45:38 -06:00
Kurt
aa2d83cda0 Update .editorconfig 2026-01-12 22:21:54 -06:00
Kurt
19655ec2ec Misc tweaks
no functional change
HaX popup now uses Task Dialog api for cleaner impl
2026-01-12 21:48:28 -06:00
Kurt
3489555f74 Add edge case handling for forgotten initial moves
bdsp/sv/swsh eggs in PLA: original egg relearn are unable to be referenced, so we need to permit all
similar for BDSP Underground special moves (egg move sharing via daycare though). Also for any oddballs in SWSH that had relearn moves for special moves.
2026-01-12 21:48:02 -06:00
Kurt
18c4f2be26 Relocate trade name fetch to EncounterUtil
Simplifies ResourceUtil to no longer have specialized methods specific to Pokémon

duplicates the gen7 zh trade files; not an issue in duplication (will compress out) and simplifies the array fetching operation to be a single method rather than many.
2026-01-12 21:07:20 -06:00
Kurt
2560b7c677 Check all languages for correct lengths
pesky eol
2026-01-12 21:05:25 -06:00
间辞
e63d514367
Update CHS Translation (#4691) 2026-01-11 01:14:24 -06:00
Kurt
6e482946e2 Add Gen4 HG/SS ball check
GUI would display the selected ball, but internally it was forced back to Poke.
Currently, the setter sanitizes both values, but if manually modified via external code, technically it could be in an invalid state.

- Add check if ball is disassociated from what is expected (modified outside of the GUI)
- GUI updates to the final (sanity checked) value regardless of what was selected.

https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/35/#findComment-298902
2026-01-11 00:55:05 -06:00
Kurt
3a8bc5889b Enhance H/W/S invalid messages, flag !255 alphas
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/35/#findComment-298916

ty ATRociousFuBear
2026-01-11 00:37:48 -06:00
Kurt
4ea08b3403 Update MoveApplicator.cs 2026-01-10 23:27:28 -06:00
Kurt
733c829570 Minor tweak
Small reduction in allocation for a method that is ever so rarely used, but yay me
2026-01-10 23:17:47 -06:00
Kurt
06d95efc64 Moveset: add implicit ReadOnlySpan conversion
Clean up some usages where we duplicated methods. The one that remains for Relearn sequence equality is OK.
JIT compiler can lower the AsSpan to new Span(4, ptr) and give near-similar performance to InlineArray. I prefer it this way because InlineArray wouldn't work with new(1), as all 4 moves would need declaration.
2026-01-10 23:16:33 -06:00
Kurt
0fe0b704d1 Minor tweaks
Allow localizing the Legality Report and File Overwrite dialogs added in .NET 10 update

Simplify evo restriction check
2026-01-10 19:27:45 -06:00
Ka-n00b
99cb6769bd
Update GSC Event Flags and ZA Block Data (#4689)
* Update const_c_es-419.txt
* Update const_c_es.txt
* Update const_gs_es-419.txt
* Update const_gs_es.txt
* Update flags_c_es-419.txt
* Update flags_c_es.txt
* Update flags_gs_es-419.txt
* Update flags_gs_es.txt

* Update SaveBlockAccessor9ZA.cs (Rogue Mega Simulator)
2026-01-09 15:17:45 -06:00
Kurt
18f95269c0 Misc edge case tests
Gen9a Antishiny edge case
Evolve-move traversal tweaks; eager checks and more
2026-01-08 23:41:02 -06:00
André Bastos Dias
812f8e847e
Add Dragon Pulse as a Species Evolution Move for Naganadel (#4687) 2026-01-08 21:07:52 -06:00
Kurt
e9cb358c50 Improve Battle Revolution checksum calc
4.5 ms => 2.4 ms on my cpu (nearly 2x as fast), even better for lesser CPUs. Probably isn't worth parallelizing.
2026-01-08 19:50:46 -06:00
Kurt
e2c09730b5 Update 26.01.07
Sceptilite released
Revise XD eevee encounter generating for shiny requests (disregard insufficient TID/SID)
Skip "overwrite" popup if savefile was loaded from a backup (bak)
2026-01-08 00:48:28 -06:00
Kurt
3d74e763ba Misc dark mode color tweaks
Centralize remaining vibrant colors to WinFormsUtil
2026-01-08 00:26:09 -06:00
Kurt
e3fa760f52 Gift2: fix template->pk2 japanese enc
as observed in discussion #4684

now matches Gift1 implementation details

I really wish we had separate classes for PK1/PK2 for each language since the string buffers are different length, oh well this footgun exists.
2026-01-05 00:42:20 -06:00
Kurt
85abb48da3 Misc tweaks for Hoopa-1 plus flags
Merge handling with Rotom's handling
Add logic for "Require" & "Set All" operations -- if the form isn't 0.

https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/35/#findComment-298854
2026-01-04 23:44:57 -06:00
André Bastos Dias
7621087e7e
Fix EncounterCriteria#IsSpecifiedIVs xmldoc description (#4683) 2026-01-03 18:38:37 -06:00
Jonathan Herbert
2ea7a60f3b
Donut Timestamp Fixes + Improvements (#4681)
* Place Time After Date In Donut Editor Format

* Fix Donut Millisecond Offset Being 1970 Not 1900
2026-01-03 12:01:13 -06:00
Jonathan Herbert
fecd2a3ddb
Fix Importing Donut (#4680)
Also fix donutEditor not following the variable naming convention
2026-01-02 21:07:57 -06:00
Kurt
8c5eb6fa9f Extract some logic from QR7
Update notes, update method names.
10 years since this (apparently custom?) format was whipped up. Probably good to have more accurate documentation for this early-injection format?
2026-01-02 13:25:12 -06:00
Kurt
fb52a5ef18 Update EffortValueVerifier.cs
Closes #4679
ty @andrebastosdias !

Revise untrained EV check to flag Pokespot encounters (the only varied level range encounter in gen3/4) where min level might be less than the actual level it was obtained at. If/when pokespot correlation is better refined, can switch to evotree level min for a gen3->4 transfer, so that something at a not-minimum exact level can be flagged if it has non-Vitamin EVs.
2026-01-02 13:10:17 -06:00
Kurt
5f65b333ba Update dependencies
Minor simplification in QRCode gen
2025-12-31 02:12:20 -06:00
Kurt
fc167caea3 Update readmes per net10/c#14 2025-12-31 01:50:51 -06:00
Kurt
2c541ad422
Update to .NET 10 (#4676)
* Update to .NET 10
* Property fields
* API signature updates
* Extension method blocks

* Completed dark mode support
  Outside of my control:
- vertical tab control (pkm editor)
- datetimepicker controls
- lgpe event flags (no idea)
- some control types having white-borders when they should really be gray

Box background is 50% transparency to effectively darken the image.

* Custom legality report popup
* Event diff dialog, version select dialog
* Add quick overwrite popup for export sav
* Extension methods
* Dark Mode: glow currently editing sprite
* Add invalid encounter hint for trade evolutions
* Extension properties
* Append legality hint on hover card
* Slot image loading: clear the screen-reader description if a slot is empty/invalid, rather than retain the previous description. Changing boxes would easily confuse users on this.
2025-12-31 01:42:05 -06:00
Parnassius
4ee9e4ad31
Misc fixes for FormInfo (#4678) 2025-12-31 01:21:44 -06:00
Parnassius
af98a5b5e9
Fix FormInfo.IsBattleOnlyForm for Zygarde-Complete (#4677) 2025-12-30 12:10:40 -06:00
Parnassius
972aaba5fa
Add Zygarde to FormInfo.BattleMegas (#4667)
`IsBattleMegaForm` already checks for Mega Zygarde, but since it's only
called if the species is in `BattleMegas`, both `IsBattleOnlyForm` and
`IsMegaForm` currently return false for Mega Zygarde
2025-12-29 15:24:40 -06:00
RandomGuy
3ca4acd7d2
Add Donut Flavor Profile Display to Donut Editor (#4673)
* Add Donut Flavor Profile Display to Donut Editor

* Update profile on berry change
2025-12-29 15:20:54 -06:00
RandomGuy
870c10ea5e
Refactor SAV_Misc3 Battle Frontier editor to use object-oriented block (#4670)
* Refactor SAV_Misc3 Battle Frontier editor to use object-oriented block class

Replaces direct byte array manipulation with BattleFrontier3 struct:
- Encapsulates all offset calculations and data access
- Uses type-safe enums for facilities, modes, and stats
2025-12-29 14:56:10 -06:00
902PM
305a2733c6
Update Japanese Flags Translations (#4671)
* Update const_c_ja.txt
* Update flags_c_ja.txt
* Update flags_gs_ja.txt
* Update flags_dp_ja.txt
* Update flags_pt_ja.txt
* Update const_bw_ja.txt
* Update const_b2w2_ja.txt
* Update flags_c_ja.txt
2025-12-29 14:13:14 -06:00
Kurt
e1f6847ed9 Extend duplicate mega stone checker -> unique
Now covers primal orbs too.
Update translations, enhanced to show the item ID that they're duplicate (less to check when inspecting an output)
2025-12-29 14:11:35 -06:00
Kurt
d6cb992d11 Fix typo
happy now, cleo-caretaker?
2025-12-21 14:26:10 -06:00
Kurt
61a13fda08 Update EncounterStatic9a.cs 2025-12-21 14:20:27 -06:00
Kurt
31edf20c87 Update 25.12.21
Moves one of the Evolution deferral checks to the encounter template where it triggers; no other encounter case will trip that check so it's OK to move it there. One less thing for every other encounter to check.

Revises the "met date present but no met location" to only flag if the encounter was matched to something. It'll already yell at mismatched encounters, no need to pile on more. The check only exists for eggs (no location).
2025-12-21 12:59:55 -06:00
Kurt
7d1bcfa354 Z-A: Add Street Name side-mission string 2025-12-20 14:49:22 -06:00
Kurt
6609dd210b Misc legality fixes for Z-A alterations
- Evolving knowing move: relearnable additions in the evolved stage was bypassing the requirement (Sylveon can relearn Charm at any level, but Eevee cannot). Prune tree to only check if pre-evolutions could have learned move.
- FormArgument requiring a minimum level to actually use the move (Primeape). Probably isn't a "complete" check, since it's implemented differently compared to Qwilfish's logic. Might be worth revising in the future to be consistent (using the same as Primeape logic? if in game, and can learn, can increase from 0).
- Flag Hangry Morpeko if cannot learn Aura Wheel yet
- Flag mega evo mismatches for Tatsugiri/Magearna/Meowstic
- Permit mega meowstic gender in party

- Remap DLC TMs (I forgot this remapping was needed; pkNX dumped it but I didn't update the table until now...)
2025-12-20 14:44:08 -06:00
RandomGuy
1edfbfab0e
Add Flavor Image Display to Donut Editor (#4666) 2025-12-20 12:45:10 -06:00
hexbyt3
cdbc2b5599
Z-A: Remove outdated comment in BallUseLegality.cs (#4664)
Removed comment for Dream/Beast Ball legality in ZA. Ball is available with the introduction of the Mega Dimension DLC.
2025-12-18 16:16:52 -06:00
间辞
bc0f255ae3
Z-A: Add CHS translation for trainer editor Collect TMs button (#4663) 2025-12-18 16:14:55 -06:00
sora10pls
2bba2b2c8d Unban Baxcalibrite 2025-12-18 07:52:40 -05:00
RandomGuy
bb41fb70e6
Add Star Display to Donut Editor (#4662) 2025-12-17 23:36:49 -06:00
Kurt
311314c3d3 Update FormArgumentVerifier.cs 2025-12-17 23:11:31 -06:00
Makio
58b2c31a66
Implement flavor score and star calculation for donuts (#4661)
* Implement flavor score and star calculation for donuts

Added logic to compute the flavor score and assign star ratings based on defined thresholds in the RecalculateDonutStats method. This enhances donut stat recalculation by including flavor and star values.

120 Points: 1 star
240 Points: 2 stars
360 Points: 3 stars
700 Points: 4 stars
960 Points: 5 stars
2025-12-17 23:05:16 -06:00
Kurt
b3fc5c62bf Update IFormArgument.cs 2025-12-17 11:39:03 -06:00
Kurt
19fde890fe Update personal_za 2025-12-17 10:28:13 -06:00
Professor Dirty
ada5d76a99
Update B/W Event Flags CHS Translation (#4659) 2025-12-17 02:53:38 -06:00
Kurt
3e0ea816cb minor tweaks
No functional change
2025-12-17 02:39:32 -06:00
Kurt
7695fb05bc Add handling for rotom form change
Plus moves permitted for all forms
(Wild) Encounter recognized if form is changed
2025-12-17 02:32:56 -06:00
Kurt
1d30ad0b43 Fix Raichu-1 alpha move required 2025-12-17 02:12:00 -06:00
Kurt
64f655b5b6 Z-A: Allow farfetch'd-1/sirfetch'd formarg
Revises the API for requesting a suggested Form Argument, based on visitation to various games with different rules.
2025-12-17 02:11:51 -06:00
Kurt
b078e0e735 Z-A: Disallow trading primal orbs 2025-12-17 02:10:54 -06:00
Kurt
7c63cacebc Z-A: Disallow holding Gimmighoul Coin
Only 3 items are classified as CanNotHold -- screw, coin, and rotom catalog (key item)
2025-12-17 02:10:41 -06:00
间辞
6b23c4b7d5
Update CHS translation of donut editor (#4658)
And hyperspace survey points in trainer editor
both new additions in the most recent release
2025-12-16 02:47:51 -06:00
Kurt
e5477d6e2d Disallow cherish ball
lol, lmao even
2025-12-16 02:37:50 -06:00
Kurt
e95dddd86d Fix initial move application of low leveled mons
Relearn moves shouldn't really be applied by default (fixes honedge expecting Sacred Sword)
Add edge case for modified learnset (Espurr) pre-dlc as an untouched level 7-8 capture. Only need 3 moves.

ty abby on discord
2025-12-16 01:18:13 -06:00
Kurt
c8eb4f548f Update 25.12.15 2025-12-15 23:19:16 -06:00
Kurt
a2a209ff2c Fix parse type on different columns
ColumnValue1 is long
ColumnValue2 is ulong

a value > long.MaxValue in ColumnValue2 would popup an error message on form load.
now fixed
2025-12-15 22:41:31 -06:00
Kurt
de2c6151e6 Minor tweaks
Simplify moveset application for Z-A; less branching since they all do essentially the same thing. The API is pretty stable so the simplifications are safe.
2025-12-15 22:34:57 -06:00
Kurt
84912a16e7 Misc encounter search/mutating fixes
ty santacrab
2025-12-15 22:33:42 -06:00
Kurt
ac25835d65 Finish Donut struct (0x00 is milliseconds!)
Interlink the GUI for ticks+calendar so that modifying one updates the other if applicable.

add randomize (very simplistic, just pick a random lv3 power)
add all-shiny (sparkling, alpha/big/little, catching)
2025-12-15 22:33:11 -06:00
Kurt
d45cdcb9e0 Misc tweaks
Fixes default moves being in inverse order
2025-12-15 15:28:26 -06:00
Kurt
6cefac2656 Update Primeape/Qwilfish formarg evo logic
Needs long-form logic to be more maintainable.

Closes #4656

Co-Authored-By: Dennis <64029159+CScorpion-h@users.noreply.github.com>
2025-12-15 01:40:01 -06:00
Kurt
9840120161 Add dragdrop for donut file
Export dialog now shows localized donut name rather than "donut"
2025-12-15 00:41:46 -06:00
Kurt
89fb9b0471 last batch of 🥒
- Updates the hyperspace pkl, adds the special band mons with their dedicated spawner's higher boost level
- Updates the overworld pkl, adds the feebas spawn (2 spawners, same location & data)

Add steelix to onix's plus move permit bypass, oops.

Interesting to note that Latias/Latios had some flying band encounters, but there's no spawner to generate them?
2025-12-15 00:00:14 -06:00
Kurt
4d5480e64c Update DonutEditor9a.cs 2025-12-14 20:06:07 -06:00
Kurt
1c4070b6a8 Update hyperspace encounters, misc checks
Adds pickle from all possible random encounter sets in hyperspace
Updates some formarg checks for certain species
Updates plus move checks for movesets that were revised by DLC

Hyperspace encounters are in a separate array, with a different slot type

Add note for Teensy/Humungo for wild encounters causing a fixed scale value rather than random.

Should be noted that this is a first-stab at encounters, and things have not been tested sufficiently to ensure the level ranges/etc are actually good data. please don't use the encounters yet; this just gets it out to testers for finding more edge cases.
2025-12-14 20:01:07 -06:00
sora10pls
0bc805b973 Add initial handling for donut icon loading 2025-12-14 17:57:42 -05:00
Kurt
29d364067a Donut9a: guard against bad donuts
moldy donuts yuck
2025-12-14 14:00:47 -06:00
Kurt
47cd845d05 Add placeholder donut picturebox, update layout 2025-12-14 13:31:50 -06:00
Kurt
5d0f1c9c37 Update SAV_Misc3.cs
Closes #4652

Co-Authored-By: Ryan Gabel <98432212+rjgabel@users.noreply.github.com>
2025-12-14 10:54:10 -06:00
Kurt
189b9bece8 Hide donut editor for base game saves 2025-12-14 10:45:23 -06:00
Kurt
07eadfec16 Add import/export donut
Hold control to set to clipboard for easier copypaste between slots/RAM windows
2025-12-14 02:03:13 -06:00
Kurt
ad550da3ae Update SAV_Trainer9a.cs 2025-12-14 01:16:00 -06:00
Kurt
437e0b8e23 Fix colorful screw collection button localization
Was an oopsie from a pull request, all good, I had hidden the button with the new TM button anyway.
Now it's all fixed :D
2025-12-14 00:41:00 -06:00
Kurt
69e519602b Create SizePower9a.cs
https: //x.com/Sibuna_Switch/status/2000090054371537099
Co-Authored-By: Lusamine <30205550+Lusamine@users.noreply.github.com>
2025-12-14 00:32:44 -06:00
Kurt
ef374f2d22 Add Donut editor
thanks to everyone who watched along while I implemented this
2025-12-14 00:14:43 -06:00
Kurt
2498b27363 Add Hyperspace Survey Points editor (DLC tab) 2025-12-14 00:14:08 -06:00
sora10pls
ed7b40e215 Finish up Mega Dimension static/gift/trade encs 2025-12-13 21:35:29 -05:00
Kurt
e89bf3a416 ZA Blank save indicate as -MD
Needed to change the blank block's type. Technically I could have made SaveRevision a readonly field rather than computed...
2025-12-12 13:14:32 -06:00
Kurt
aee2d1a556 Meowstic/Magearna dex edit/set
Also fixes tatsugiri form set when giving all
2025-12-12 12:54:56 -06:00
Dennis
c148da9ab4
Unban beast and safari (#4655)
can be obtained in the Hyperspace Battle Zone.
2025-12-12 08:32:14 -06:00
Kurt
70f5b2ddaa Update translations 2025-12-12 02:19:21 -06:00
Kurt
4c5efe5ae6 Update 25.12.12
Initial partial support for DLC.
Encounters and other various QoL features to follow in future commits.
2025-12-12 01:38:06 -06:00
Kurt
34f3624b64
Changes for Legends: Z-A (Mega Dimension) support (#4653)
Refer to pull request notes and the eventual changelog for a high-level summary.

Co-authored-by: Matt <17801814+sora10pls@users.noreply.github.com>
Co-authored-by: Lusamine <30205550+Lusamine@users.noreply.github.com>
Co-authored-by: SciresM <8676005+SciresM@users.noreply.github.com>
2025-12-12 01:30:35 -06:00
sora10pls
f2d33bf0cf WA9: Alpha Charizard date range 2025-12-09 09:23:46 -05:00
sora10pls
2d8145b96c Add latest distribution raid data 🐲🔫 2025-12-04 19:02:51 -05:00
Kurt
6603984f88 Update 25.12.02 2025-12-02 08:41:49 -06:00
sora10pls
86a19e4a16 Add support for Project M 2025-12-02 08:08:48 -05:00
sora10pls
d8caf74653 Fix uncatchable Tera Raid Battle pickling 2025-12-02 08:05:58 -05:00
Kurt
def9802375 Allow seed of mastery to plus any learned move
Using a seed of mastery on any currently known move is allowed, regardless of the natural
2025-12-01 23:06:47 -06:00
Kurt
01dc5aa331 Remove duplicate line returns
No functional change, just an OCD nitpick
2025-12-01 22:53:27 -06:00
sora10pls
3e1499bdf4 Add HOME Fidough event date range
Forgot to add this for an entire month, oops
2025-12-01 09:25:54 -05:00
Kurt
72008a8e60 Add IFixedTrainer interface tag
Allows SysBot.NET to detect it more easily
2025-12-01 00:11:19 -06:00
Kurt
cc6a26a757 Update 25.11.30 2025-11-30 23:33:16 -06:00
Pasquale Nardiello
dc1818d589
Added Appearence editor for ZA and annotated hair styles and eye cuts. (#4642) 2025-11-30 21:51:12 -06:00
Carbonara
7658ba8994
Add French translation for B2W2 flags and constants (#4644)
* Create const_bw_fr.txt

* Fix an incorrect name for the P2 Laboratory event

The Scientist of the P2 Laboratory event used the name Dudley (Black 2 and White 2 scientist) instead of the name Nathan (Black and White scientist).

Chinese languages already seem to be correct, while I cannot directly fix the Korean translation myself as unlike other languages, I do not have any wiki source to find the trainer name (if you have this information, please feel free to fix it).

* Create const_b2w2_fr.txt

* Fix the Petilil-Cottonee order

* Create flags_b2w2_fr.txt
2025-11-27 10:22:32 -08:00
sora10pls
4a10b8e087 ZA 1.0.3 save file loading
One new boolean save block added, probably related to Ranked Mega Stone issue?
2025-11-27 09:42:51 -05:00
sora10pls
c4ca51dbcd ZA: Unban Dream Ball, Chesnaughtite 2025-11-26 08:59:43 -05:00
Kurt
85cf53fe8e Gen4: Add Mic Test possible IV seeds 2025-11-25 00:19:58 -08:00
Kurt
a437fecab8 Reuse EntityGender magic ratio values 2025-11-24 17:21:49 -08:00
Kurt
2da0e303a6 Extract method to allow specific forced detection 2025-11-24 17:21:31 -08:00
Kurt
2f1f08af84 gcea -> bacd_r_a
https://discord.com/channels/343093766477053953/406851200928055297/1442622561124094213
2025-11-24 17:15:51 -08:00
Professor Dirty
d0cf74b063
Add files via upload (#4643) 2025-11-23 18:57:14 -08:00
9Bitdo
01b66416c4
Add LAIC 2026 Federico Camporesi's Whimiscott date (#4641) 2025-11-21 08:39:14 -06:00
Kurt
a1c9e3a615 Gen7: hidden ability+gen4 ball on gen2-5 starters
Was mistakenly copypasted from Gen6's rules.

Thank you manolin18 for bringing this to my attention!

https://projectpokemon.org/home/forums/topic/67210-pok%C3%A9mon-starter-and-pokeball/#findComment-297255
2025-11-20 20:56:54 -06:00
sora10pls
7b091ce931 Add latest distribution raid data 🫃
Also updates existing raid/outbreak data using latest pkNX changes
2025-11-20 19:04:06 -05:00
Kurt
e8aecc85a6 Update EncounterSlot9a.cs 2025-11-18 17:01:36 -06:00
Kurt
84e5382134 Update EncounterGenerator9X.cs
Nobody cares about SW/SH for a month eh
2025-11-18 00:25:02 -06:00
Kurt
148d71e7ea Update EncounterGift9a.cs 2025-11-17 23:26:51 -06:00
sora10pls
d1574959d5 Update GO encounters per latest PGET changes
Early stages of Safari Ball handling.
2025-11-17 19:21:01 -05:00
Kurt
c74a5ef085 Alpha PlusMove for evos: use enc species
For people who mess with plus moves
2025-11-17 16:32:06 -06:00
Kurt
931276bf39 Update 25.11.16 2025-11-16 15:19:45 -06:00
Kurt
c9dbed6d8a ZA: Update handling for ability-change rules 2025-11-16 15:07:18 -06:00
Kurt
b0dfe2f57f Required move count: ignore evo/relearn 2025-11-16 15:07:03 -06:00
Kurt
af3f7f770b Update location detection result 2025-11-16 15:06:35 -06:00
Kurt
4c377d75cf Allow colorful screw quantity to save
Really need to refactor the entire Inventory handling because it isn't very flexible
2025-11-16 11:58:13 -06:00
abcboy101
a3e1d88243
Update Switch badwords to v21.0.0 (#4640) 2025-11-16 09:59:42 -06:00
Kurt
5113e5e641 Minor clean
Adds IEncounter9a to WA9 for extra metadata fetch
2025-11-15 22:25:33 -06:00
Kurt
9f21f45f25 Sanity check ability index on transfer
Passing an entity with AbilityNumber of 7 no longer throws an exception on transfer logic
2025-11-15 22:24:15 -06:00
Kurt
d138755ed0 Hide nonsensical dropdowns for gender/shiny
Internal values that were loosely bundled. The Criteria tab in the encdb shouldn't show these.
2025-11-15 22:22:44 -06:00
Kurt
e45754c830 Track more move sources
No change in coloration, but will show them for past games such as Egg Moves that it could have learned.
2025-11-15 22:21:07 -06:00
Kurt
d5bf6e67d5 Extract bonus move interface from slot6ao/8b-under 2025-11-15 22:15:25 -06:00
902PM
4d0bfa46df
Update translations (#4638)
* Update lang_ja.txt

* Update flags_c_ja.txt

* Update flags_gs_ja.txt

* Update const_e_ja.txt

* Update const_frlg_ja.txt

* Update flags_e_ja.txt

* Update flags_frlg_ja.txt

* Update flags_rs_ja.txt

* Update const_dp_ja.txt

* Update const_hgss_ja.txt

* Update const_pt_ja.txt

* Update flags_dp_ja.txt

* Update flags_hgss_ja.txt

* Update flags_pt_ja.txt

* Update flags_e_ja.txt

* Update const_b2w2_ja.txt

* Update flags_b2w2_ja.txt

* Update flags_bw_ja.txt

* Update lang_ja.txt
2025-11-13 21:37:59 -06:00
Kurt
11da2bb317 Update LegendsZAVerifier.cs 2025-11-13 20:11:31 -06:00
Kurt
89e10f0640 Allow level 100 rare candy evo for SW/SH+BD/SP
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/34/#findComment-298102

ty JeongJeong
2025-11-10 18:43:18 -06:00
Professor Dirty
70a58f2a55
Update CHS translation (#4634) 2025-11-09 21:34:33 -06:00
Kurt
6a1c14af2f Revise some method signatures 2025-11-08 14:56:15 -06:00
Kurt
551c34b9ed Add latam spanish for enigma/transporter strings
#4632
2025-11-08 14:55:42 -06:00
Kurt
29629b865a Minor tweaks
MGDB shows WA9
re-add S/V PP/movetype (move 0 is 0 pp; in ZA it is 35)
2025-11-08 00:16:23 -06:00
Kurt
866d538c24 Improve randomization feel for prefill alpha IVs
Slightly slower due to random indexes, but negligible overall
2025-11-07 10:56:25 -06:00
Kurt
8b0c6c774a ZA: Revise A.Z. floette OT for spanish
uses non-breaking space instead of regular space
2025-11-07 10:34:57 -06:00
Kurt
27ca4ec76f Ignore shiny raid pidiv correlation
Was previously ignored, but the improvements to the enum return value for Z-A caused them to get flagged
2025-11-07 10:26:49 -06:00
Kurt
6e3d46142b WB8: revise date set for eggs
Closes #4571

same as PR with some rearranging/modifications (fork was archived so could not push to PR)

Egg was a direct redeem (not via HOME) so no egg date range

end result: don't set met date for IsEgg; logic flow rearranged

Co-Authored-By: HexByt3 <80122551+hexbyt3@users.noreply.github.com>
2025-11-07 10:06:23 -06:00
sora10pls
a6beda0e3b Update ItemStorage9ZA.cs 2025-11-07 10:56:39 -05:00
Kurt
5b9b9c2981 Disallow alpha mark on ZA alphas
Not set by default, no HOME connectivity to set it.
2025-11-07 03:07:26 -06:00
Kurt
bad73d8cbf Remove test code
burning the midnight oil
2025-11-07 02:46:35 -06:00
Kurt
c7427926cb Update 25.11.07 2025-11-07 02:41:29 -06:00
Kurt
9d719bdf06 Retain item values not exposed for edits
Fixes pouch getting nuked
2025-11-07 02:38:39 -06:00
Kurt
c218098f26 Add current context set ribbons only
Will clear anything picked up from other games.
Closes #4592
2025-11-07 01:15:33 -06:00
Kurt
19284fef79 Update 25.11.06
Fixes pcdata
Disabled Legal item sanitization on save (clearing unreleased mega stones)
2025-11-07 00:21:32 -06:00
Kurt
a6683e9e52 ZA: Fix item edit IsNewNotify
Behavior might be wrong in S/V but nobody reported? Either way, this fixes the behavior of old things seeming new again in Z-A.
2025-11-06 23:40:05 -06:00
sora10pls
2779f384eb Hey Sora! Get up on the Hydra's back! 2025-11-06 19:03:40 -05:00
sora10pls
89b5c12b3d ZA: Shine Bright like a Gemstone 2025-11-06 08:16:27 -05:00
Kurt
cca9781884 Add ConfigSave9a
Can set text speed to 3 for instant text.
2025-11-06 01:04:53 -06:00
Kurt
1e7fd1e918 Remove non-alpha slot if always-alpha 2025-11-05 22:39:40 -06:00
sora10pls
c1ba6f9a77 ZA: Unban Sport Ball, Delphoxite 2025-11-05 08:39:48 -05:00
Kurt
308364fb9e More tweaks/fixes in slot dumper
Fixed stale reference for slot merge
Revised AABB check to get WZ 8 matching
2025-11-05 00:55:14 -06:00
Kurt
8d604c6560 Fix level range hover indication 2025-11-05 00:54:28 -06:00
Kurt
2b2d5c2c0b ZA: Dex enhancements/fixes
Fix X/Y mega forms clearing the Y mega form
Fix Displayed form being clamped to max of 3 (Vivillon no longer an issue)
Add on-seen/capture bulk form granting
Add on-seen shiny bulk form granting
2025-11-04 18:46:26 -06:00
Kurt
5b9d7f1314 Update EncounterGift9a.cs 2025-11-04 14:26:48 -06:00
Kurt
31b48383c8 ZA: Update wild pkl from current
Solves WZ 6 & 10's secondary areas
Still has issues resolving the right area for overlapping outside areas, and potential crossovers
2025-11-04 13:28:41 -06:00
Kurt
512063239b Update Overworld8aRNG.cs 2025-11-04 13:27:53 -06:00
Kurt
3ea2ce6f38 Add EVs to batch dropdown list 2025-11-04 13:27:43 -06:00
abcboy101
724765e185
Remove es txt resource fallback logic (#4627)
No longer needed after commit 51a1caf628
2025-11-04 07:56:59 -06:00
abcboy101
ecbfe41f7f
Add LGPE/Gen 8/Gen 9 Wonder Card titles (#4625)
* Add LGPE/Gen 8/Gen 9 Wonder Card titles

* Fix CardTitleIndex
2025-11-03 19:49:30 -06:00
Kurt
51a1caf628 Duplicate es txt resources to es-419 2025-11-03 19:49:12 -06:00
sora10pls
4c9352dcbd Update MoveInfo.cs 2025-11-03 20:38:19 -05:00
Kurt
cac0ee3eaa Update PKMEditor.cs
Closes #4626
2025-11-03 15:24:21 -06:00
sora10pls
d5cc675d15 More ZA form considerations for Pumpkaboo/Gourgeist
Initial handling only really considered the form name changes for English, but in some localizations, more than just Average and Super got changed. Now include all four updated form strings for all localizations

FRA: Mini -> Petite, Maxi -> Grande
ITA: Mini -> Piccola
DEU: S -> Kleine, L -> Große
ES-ES: Pequeño -> Pequeña
2025-11-03 12:23:49 -05:00
Jonathan Herbert
6b1939deaf
Extend ZA Fashion Editor For SV Followup (#4624)
Fix Typos and Indentation Error
2025-11-02 22:00:08 -06:00
Jonathan Herbert
7e4d7773cc
Extend ZA Fashion Editor For SV (#4623) 2025-11-02 21:08:39 -06:00
Kurt
98fc80448a Show criteria in encounter db for tweaks 2025-11-02 18:43:23 -06:00
Kurt
a83ee19757 Minor clean 2025-11-02 17:54:23 -06:00
Kurt
0fc1e000e4 Add overworld playtime value 2025-11-02 17:22:07 -06:00
Kurt
b407d8f0a0 Show less-detailed scale eval in Z-A 2025-11-01 16:59:54 -05:00
Kurt
108bcf38d5 Update SAV_Trainer9a.cs 2025-11-01 10:46:19 -05:00
Kurt
5f718a9d4e Update PKMEditor.cs 2025-11-01 10:41:19 -05:00
Kurt
bde111af20 WA9: add audino, fix OT name/ID fetch
Since ZA was forked before S/V 2.0, they reverted the raw ID fix lol.
2025-11-01 10:23:01 -05:00
9Bitdo
268ce77173
Add Poké Center Fidough & Audino Birthday Gift's date (#4622) 2025-11-01 09:41:28 -05:00
Kurt
a6a532ff7a Update EncounterStatic9a.cs
fixes xy legend
2025-11-01 02:04:05 -05:00
Kurt
74a74c6749 Set alpha move in batch edit suggest 2025-10-31 22:21:25 -05:00
Kurt
46e9b6d2fc Don't modify screw count on GiveAll 2025-10-31 20:56:11 -05:00
Kurt
7013250688 Add Colorful Screw collector button-cheat
Trainer editor, press button to have them auto-collected to inventory.
Some modifiers available for those wanting to reset all or get a list of all for manual hunting :)
2025-10-31 20:39:50 -05:00
Kurt
d047f02410 Extract contest colors to static utility class
Fixes more hardcoded colors to SystemColors for easier darkmode handling
2025-10-31 20:26:51 -05:00
Kurt
b5c29b3de8 Minor tweaks
Allow dragdrop into menustrip/legal/vertical tabs to load file
ZA: Retain original criteria for cleanup application of IVs
Inline vertical tabs color choice
Simplify some expressions
2025-10-31 18:44:37 -05:00
Kurt
0e9d8db2b1 Fix plus move defer check 2025-10-31 00:51:01 -05:00
Kurt
bcd12478af Update EncounterSlot9a.cs 2025-10-31 00:47:32 -05:00
Kurt
649ba5f1f2 Fix alpha move flag check 2025-10-30 23:48:37 -05:00
Kurt
f4e6520afe Make some configurable color settings fixed
Better support for toggled color mode (Dark Mode)
Rewrites Marking sprite coloration so that Dark Mode doesn't show the marks as Black when active (instead show as the Text color).
2025-10-30 23:21:30 -05:00
Kurt
0b58c01866 Fix diff flagwork block type 2025-10-30 22:52:46 -05:00
Kurt
8933fb06d4 ZA Fashion: Less GUI lag when Set All Owned
Pretty much instant now. Requires a little bit of allocation but good enough.
Clone the save file so we don't mutate the original if the user opts to cancel.
2025-10-30 22:52:35 -05:00
Kurt
048f7cfe30 Revise PIDIV check return value to enum
Deduplicates slightly by indicating the true-"ignore" better.
Add deferral for Alpha Moves (flagged later via ZA Verifier, not needing a generic Partial error)
2025-10-30 22:50:38 -05:00
Lusamine
291d5be618 Surface LZA FieldItems block 2025-10-30 21:07:53 -05:00
Kurt
d5314a00f5 Allow game bug for trade evo's & plus moves 2025-10-29 17:40:13 -05:00
Kurt
702829bb20 Add more event block repo types
Allow bigger window
2025-10-29 16:34:10 -05:00
Kurt
4c2cc2f45f Add replace trainer name (Z)
profanity OT/nick => replace with this
not that it is currently being used, as none of the official events have bad OT names, and HOME can't transfer in stuff-yet-to-reset.
2025-10-29 16:33:49 -05:00
Kurt
e6a59740bb Manual ability ctrl-click suggest
Add control-click for the manual ability entry to auto-apply the corresponding ability based on personal info (try and detect birth ability)
2025-10-29 01:48:13 -05:00
Kurt
5bdfd4597c Fix non-English set imports of remapped forms
No need to do the English-specific remapping if the language isn't English
Closes #4613
2025-10-29 01:47:43 -05:00
Kurt
98255526d6 Update EvolutionUtil.cs 2025-10-28 23:55:48 -05:00
Kurt
70a6658835 Misc updates
ZA: Check ability number values (users were setting to 0 and it wasn't flagged)
ZA: Add Mable status for overall completion
ZA: Allow mutable slots of stored sub-event entities (such as gogoat/shuppet)
XY: Allow old man's slot to be modified (he's dead, who cares lol)
2025-10-28 23:47:05 -05:00
Kurt
bf9f585b77 Rename Work1=>CountTitle 2025-10-28 02:23:41 -05:00
Kurt
bdf3c09c2a Relabel some EventWork blocks, increase size 2025-10-27 23:33:09 -05:00
XxPhoenix1996xX
d93f76731b
Spanish localization update (#4608)
* Update const_e_es.txt

* Update flags_bw_es.txt

* Update MessageStrings_es.txt

* Update flags_b2w2_es.txt

* Update flags_frlg_es.txt

* Update flags_e_es.txt

* Update const_rs_es.txt

* Update const_dp_es.txt

* Update flags_hgss_es.txt

* Update flags_dp_es.txt

* Update flags_rs_es.txt

* Update flags_e_es.txt

* Update flags_frlg_es.txt

* Update flags_bw_es.txt

* Update flags_hgss_es.txt

* Update MessageStrings_es.txt
2025-10-27 22:25:38 -05:00
Easy World
34d154320e
Update Simplified Chinese translations (#4607)
Improved and expanded Simplified Chinese localization for battle set parsing, legality checks, and UI text. This includes more accurate terminology, better consistency, and full translation of new features for Gen 9a and related editors.
2025-10-27 22:25:15 -05:00
XxPhoenix1996xX
629714c3b2
Update lang_es.txt (#4606) 2025-10-27 19:53:34 -05:00
Kurt
404bd1036c Fix recognition of Gen9 eggs
Closes #4605
2025-10-27 19:53:10 -05:00
Kurt
b486247c1a Gen9a: remove unused Game Started entry
Trainer editor; block is unused
2025-10-27 15:34:36 -05:00
Omni-KingZeno
ec550a03fa
Add Alpha status box sort option for PLA/ZA (#4604)
Co-authored-by: Omni-KingZeno <200784099+Omni-KingZeno@users.noreply.github.com>
2025-10-27 15:30:34 -05:00
Kurt
01255dc83e Gen9a: Add "Set All Owned" button
slow, but works
hold alt to remove
hold shift to apply to all tabs
not holding shift applies only to current tab

allow form to be translated
2025-10-27 12:45:17 -05:00
Kurt
67b0217683 Fix met location duplication across sets
Kalos Gift starters when loaded to the GUI would mutate their met location to the wrong index (series 0) rather than retain the correct one (series 3). Wasn't apparent because the deduplication algorithm is different for Debug and Release builds.

Tested Release build, all unit tests now pass. Thanks pigeonsaint (discord) for reporting!
2025-10-27 12:21:08 -05:00
Manu
10d3ae0d54
Added support for .wa9 file type (#4602) 2025-10-27 11:57:03 -05:00
Kurt
29b4b6c38d Gen9a: fix dex seen gender bit set
genderless was overwriting male; ensure value is 0-2
add missing seen gender set on UpdateDex box/party slot set

Closes #4600
2025-10-27 11:36:05 -05:00
abcboy101
b5f5f35f2c
Update text resources from Z-A, support LATAM Spanish as a program language (#4599)
* Split zh text resources

* Reorganize language text resources

* Update language codes

Z-A uses the same abbreviations in all languages

* Update characteristics text from Z-A

* Update LATAM text resources from Z-A

* Support LATAM Spanish as a program language

* Handle duplicates
2025-10-27 11:03:48 -05:00
Kurt
bee3cfb657 Minor perf tweak for generating shiny slot 2025-10-27 00:28:26 -05:00
HexByt3
1b2512c16f
Fix Zygarde form handling in ShowdownParsing (#4577)
The logic for parsing Zygarde-50%-C and Zygarde-10%-C was inverted, causing 50%-C forms to be imported as 10%-C. Fixed by checking if the form string contains "10%" instead of checking if it's empty.
2025-10-27 00:11:45 -05:00
XieonGaming
45e8a76754
Revise Spanish descriptions for event 0248 - (#4591) 2025-10-27 00:11:13 -05:00
Kurt
4aefdb7627 Update MiscVerifier.cs 2025-10-27 00:09:46 -05:00
Kurt
5cd6f456f0 Minor tweaks
Fix ability index calc for generate & match
Fix message for mystery gift fateful encounter flag should be false
Add PA9 to GetBlank for anyone using the method via NuGet dll
2025-10-26 23:58:28 -05:00
Kurt
1c610c2054 full shiny cache, slot->pa9 obedience level
also alpha plus move retained on set all plus moves
2025-10-26 20:21:42 -05:00
Kurt
692d99c5cc Update 25.10.26 2025-10-26 19:07:50 -05:00
Kurt
fd1c538cc5
Changes for Legends: Z-A support (#4596)
Refer to pull request notes and the eventual changelog for a high-level summary.

Co-authored-by: Matt <17801814+sora10pls@users.noreply.github.com>
Co-authored-by: Lusamine <30205550+Lusamine@users.noreply.github.com>
Co-authored-by: SciresM <8676005+SciresM@users.noreply.github.com>
2025-10-26 19:01:44 -05:00
abcboy101
ae526a5bd5
Add Korean translation of README (#4583)
Closes #4582

Co-authored-by: scd02 <96989282+scd02@users.noreply.github.com>
2025-10-21 07:32:05 -05:00
Carbonara
fc6126a9e0
Update flags_bw_fr.txt (#4575) 2025-10-14 15:59:11 -05:00
Ka-n00b
0244b1bc6f
Fixed some CHT Event Constants formatting (#4574) 2025-10-13 22:46:20 -05:00
Ka-n00b
b34ec9de4c
Update Event Flags and translations (#4573)
* Update const_e_en.txt

* Update const_e_es.txt

* Update const_e_ja.txt

* Update const_e_zh-Hans.txt

* Update const_frlg_en.txt

* Update const_frlg_es.txt

* Update const_frlg_ja.txt

* Update const_frlg_zh-Hans.txt

* Update flags_e_en.txt

* Update flags_e_es.txt

* Update flags_e_ja.txt

* Update flags_e_zh-Hans.txt

* Update flags_e_zh-Hant.txt

* Update flags_rs_en.txt

* Update flags_rs_zh-Hans.txt

* Update const_dp_en.txt

* Update const_dp_es.txt

* Update const_dp_ja.txt

* Update const_dp_ko.txt

* Update const_dp_zh-Hans.txt

* Update const_dp_zh-Hant.txt

* Update const_hgss_en.txt

* Update const_hgss_es.txt

* Update const_hgss_ja.txt

* Update const_hgss_ko.txt

* Update const_hgss_zh-Hans.txt

* Update const_hgss_zh-Hant.txt

* Update const_pt_en.txt

* Update const_pt_es.txt

* Update const_pt_ja.txt

* Update const_dp_ko.txt

* Update const_pt_ko.txt

* Update const_pt_zh-Hans.txt

* Update const_pt_zh-Hant.txt

* Update flags_dp_en.txt

* Update flags_dp_es.txt

* Update flags_dp_ja.txt

* Update flags_dp_ko.txt

* Update flags_dp_zh-Hans.txt

* Update flags_dp_zh-Hant.txt

* Update flags_hgss_en.txt

* Update flags_hgss_es.txt

* Update flags_hgss_ja.txt

* Update flags_hgss_ko.txt

* Update const_hgss_ko.txt

* Update flags_hgss_zh-Hans.txt

* Update flags_hgss_zh-Hant.txt

* Update flags_pt_en.txt

* Update flags_pt_es.txt

* Update flags_pt_ja.txt

* Update flags_dp_ja.txt

* Update flags_pt_ko.txt

* Update flags_pt_zh-Hans.txt

* Update flags_pt_zh-Hant.txt

* Update const_b2w2_en.txt

* Update const_b2w2_es.txt

* Update const_b2w2_ja.txt

* Update const_b2w2_ko.txt

* Update const_b2w2_zh-Hans.txt

* Update const_b2w2_zh-Hant.txt

* Update const_bw_en.txt

* Update const_bw_es.txt

* Update const_bw_ja.txt

* Update const_bw_ko.txt

* Update const_bw_zh-Hans.txt

* Update const_bw_zh-Hant.txt

* Update flags_b2w2_en.txt

* Update flags_b2w2_es.txt

* Update flags_b2w2_ja.txt

* Update flags_b2w2_ko.txt

* Update flags_b2w2_zh-Hans.txt

* Update flags_b2w2_zh-Hant.txt

* Update flags_bw_en.txt

* Update flags_bw_es.txt

* Update flags_bw_fr.txt

* Update flags_b2w2_es.txt

* Update flags_bw_es.txt

* Update flags_bw_ja.txt

* Update flags_bw_ko.txt

* Update flags_bw_zh-Hans.txt

* Update flags_bw_zh-Hant.txt

* Update const_oras_zh-Hant.txt

* Update const_xy_ko.txt

* Update const_xy_zh-Hant.txt

* Update flags_oras_en.txt

* Update flags_oras_es.txt

* Update flags_oras_fr.txt

* Update flags_oras_ja.txt

* Update flags_oras_ko.txt

* Update flags_oras_zh-Hans.txt

* Update flags_oras_zh-Hant.txt

* Update flags_xy_en.txt

* Update flags_xy_ko.txt

* Update flags_xy_zh-Hant.txt

* Update const_sm_ko.txt

* Update const_usum_ko.txt

* Update const_usum_zh-Hant.txt

* Update flags_sm_ko.txt

* Update flags_sm_zh-Hant.txt

* Update flags_usum_ko.txt

* Update flags_usum_zh-Hant.txt

* Update flags_sm_zh-Hans.txt

* Update flags_sm_zh-Hant.txt

* Update flags_gs_zh-Hant.txt

* Update flags_c_zh-Hant.txt

* Update const_gs_zh-Hant.txt

* Update const_c_zh-Hant.txt

* Update flags_rs_zh-Hant.txt

* Update flags_gs_ko.txt

* Update flags_hgss_ko.txt

* Update MessageStrings_ko.txt

* Update MessageStrings_ja.txt

* Update const_b2w2_zh-Hans.txt

* Update const_b2w2_zh-Hant.txt

* Update const_b2w2_es.txt

* Update flags_gs_ko.txt

* Update flags_gs_en.txt

* Update flags_c_en.txt

* Update flags_c_fr.txt

* Update flags_c_ja.txt

* Update flags_c_zh-Hans.txt

* Update flags_c_zh-Hant.txt

* Update flags_gs_fr.txt

* Update flags_gs_ja.txt

* Update flags_gs_ko.txt

* Update flags_gs_zh-Hans.txt

* Update flags_gs_zh-Hant.txt

* Update flags_gs_es.txt

* Update flags_c_es.txt

* Update flags_gs_es.txt

* Update flags_c_es.txt

* Update const_e_ja.txt

* Update const_frlg_ja.txt

* Update flags_dp_ja.txt

* Update flags_pt_ja.txt

* Update flags_c_ja.txt

* Update flags_gs_ja.txt

* Update flags_gg_ko.txt

* Update flags_gg_zh-Hant.txt

* Update flags_gg_es.txt

* Update flags_gg_es.txt

* Update const_hgss_ko.txt

* Update const_xy_ko.txt

* Update flags_oras_ko.txt

* Update flags_c_es.txt

* Update flags_c_es.txt

* Update flags_gs_es.txt

* Update flags_gs_es.txt

* Update flags_gs_es.txt

* Update flags_c_es.txt

* Update const_c_zh-Hant.txt

* Update const_pt_ko.txt

* Update flags_usum_zh-Hant.txt

* Update flags_usum_zh-Hans.txt

* Update flags_usum_ko.txt

* Update flags_usum_ja.txt

* Update flags_usum_es.txt

* Update flags_usum_en.txt

* Update const_c_es.txt

* Update flags_bw_ja.txt

* Update flags_bw_zh-Hans.txt

* Update flags_bw_zh-Hant.txt

* Update flags_bw_zh-Hant.txt

* Update const_xy_zh-Hans.txt

* Update const_xy_zh-Hant.txt

* Update flags_gs_es.txt

* Update flags_c_es.txt

* Update const_c_es.txt

* Update const_c_es.txt

* Update const_gs_es.txt

* Update const_c_es.txt

* Update flags_gg_zh-Hant.txt

* Update flags_dp_ko.txt

* Update flags_pt_ko.txt

* Update const_gs_es.txt

* Update const_c_es.txt

* Update flags_b2w2_zh-Hans.txt

* Update flags_b2w2_zh-Hant.txt

* Update flags_dp_zh-Hans.txt

* Update flags_pt_zh-Hans.txt

* Update flags_pt_zh-Hant.txt

* Update flags_dp_zh-Hant.txt

* Update flags_dp_zh-Hans.txt

* Update flags_dp_zh-Hant.txt

* Update flags_pt_zh-Hant.txt

* Update flags_pt_zh-Hans.txt

* Update const_dp_ja.txt

* Update const_pt_ja.txt

* Update flags_bw_ko.txt

* Update flags_usum_zh-Hans.txt

* Update flags_usum_zh-Hant.txt

* Update MessageStrings_de.txt

* Update MessageStrings_it.txt

* Update MessageStrings_zh-Hant.txt

* Update MessageStrings_zh-Hans.txt

* Update MessageStrings_ko.txt

* Update MessageStrings_de.txt

* Update flags_c_es.txt

* Update flags_c_fr.txt

* Update flags_c_ja.txt

* Update flags_c_zh-Hans.txt

* Update flags_c_zh-Hant.txt

* Update flags_c_en.txt

* Update flags_gs_es.txt

* Update flags_gs_fr.txt

* Update flags_gs_ja.txt

* Update flags_gs_zh-Hans.txt

* Update flags_gs_zh-Hant.txt

* Update flags_gs_ko.txt

* Update flags_gs_en.txt

* Update flags_gs_es.txt

* Update flags_hgss_ko.txt

* Update flags_hgss_es.txt
2025-10-13 16:22:48 -05:00
sora10pls
cdf4aaecce Add latest distribution raid data... again! 2025-10-06 07:39:08 -04:00
Kurt
bff4a56f1f Add egg3/4 language restrictions, kor shaymin
Closes #4570
2025-10-05 11:23:30 -05:00
sora10pls
f113b01faf Add latest distribution raid data 🌙🤖
Thanks for not distributing these raids correctly, Game Freak!
2025-10-02 20:38:47 -04:00
Kurt
f10b6c5196 Minor tweaks 2025-09-27 18:33:15 -05:00
Kurt
73536187cf Update 25.09.25 2025-09-26 17:00:38 -05:00
9Bitdo
23e08dc73a
Add Shiny Miraidon / Koraidon Gift's date (#4567) 2025-09-26 04:37:58 -05:00
Kurt
e217979000 Misc zipreader tweaks
Closes #4566
signature changes, add some overloads, extract/simplify common logic

Co-Authored-By: Chris Dailey <nitz@users.noreply.github.com>
2025-09-25 17:27:14 -05:00
Kurt
8d0bd79708 Whitelist trade5bw for pid check
PID is forced by the encounter
Closes #4562
2025-09-24 23:35:07 -05:00
Kurt
83beeaa5d0 Add zipped save file r/w
Not happy that zipping the file is the solution for some homebrew apps, but it is what it is.

No need to select which file; it's always one file in the zip, and never multiple.
When exporting, if it originated from a zip, grab the original then update it with the revised contents.

Closes #4564

Co-Authored-By: Chris Dailey <nitz@users.noreply.github.com>
2025-09-24 23:25:15 -05:00
Kurt
d0f8c18426 Misc tweaks
extract trade restriction check logic to a separate class
Update translation for roamer3 level
2025-09-24 23:13:30 -05:00
Kurt
74870abc55 Fix item convert for gen2 showdown import
Add handling for wrong-EV format imports
2025-09-24 23:10:40 -05:00
Kurt
9cfe12bf32 Add fallback showdown parse line: happiness 2025-09-24 23:09:03 -05:00
Dave / Xieon
4b66f3780f
Added a couple of translations in French and Chinese (#4565)
* Update legality_fr.json
* Update MessageStrings_zh-Hans.txt
2025-09-24 22:28:25 -05:00
sora10pls
e8d3acb938 Update Shiny Chi-Yu Wonder Card ID 2025-09-18 20:04:08 -04:00
abcboy101
dc52331cf1
Fix PK2.ConvertToPK1 (#4561) 2025-09-14 21:40:04 -05:00
Kurt
e5705b078a Update MiscVerifier.cs
#4559
duh
2025-09-13 23:09:58 -05:00
Kurt
7e43c3d468 Gen5: fame legality check +25/-50
Closes #4559
2025-09-13 22:40:49 -05:00
Kurt
7d77c3568d Localize status type browser on hover
Toxic isn't translated, so just use "Toxic".
2025-09-13 13:35:39 -05:00
abcboy101
a75ff7b2b0
Correct Gen 5+ status conditions (#4558) 2025-09-13 12:52:17 -05:00
Kurt
efa1211c07 Keep plugin load result
Closes #4556

Co-Authored-By: Chris Dailey <602691+nitz@users.noreply.github.com>
2025-09-08 10:22:52 -05:00
Kurt
ac777ba3ec Swap gen1 trade nidoran jp/int species
Also flag * char for international encounters that aren't in-game-trades.

https://projectpokemon.org/home/forums/topic/67161-invalid-ot-from-generation-12-uses-unavailable-characters/#findComment-297053
2025-09-07 17:30:19 -05:00
Kurt
ec47d75327 Misc tweaks 2025-09-07 17:29:19 -05:00
Kurt
d774e48a56 Revise event flag block fetch
now works for redirected save files where a block is responsible
2025-09-06 15:28:49 -05:00
Kurt
63dfdab57e Fix settings editor select battle revolution ver
Selecting Battle Revolution (recently added GameVersion for BatRev rentals) isn't filtered out of the GameVersion list, so when a user selects it, it will result in an unhandled switch case. Add it, and ensure the blank save loads without errors.
2025-09-05 23:28:54 -05:00
Kurt
e1ca2ccdf8 Revise set all valid ribbons for Gen6 training rib 2025-09-05 21:05:07 -05:00
Kurt
d574ce32d1 Add slnx
Try again; delete .sln later.
2025-09-05 17:35:53 -05:00
Kurt
ee02b7c176 Update save file export extension filter get
specific extensions (like dsv/gci) would return a filter of (.gci) rather than (*.gci), leading to user error when they toggle back and forth (removing the extension).

Closes #4555

unrelated: allow folder list manual text entry to anchor to the right side (expands when form is expanded)
2025-09-05 14:54:26 -05:00
abcboy101
355262ba4d
Fix IsG2CrystalJPN (#4554) 2025-09-05 08:34:48 -05:00
Kurt
1a07618bbe Misc tweaks
Adds HP to gen3 roamer editor - closes #4553
Revises Gen3 Hall of Fame editor to allow edits (save/load methods were swapped)
Don't append Gift3 PID types if PID type is mismatched
2025-09-04 22:52:18 -05:00
sora10pls
47666d5469 Update Shiny Ting-Lu Wonder Card ID
Chi-Yu is likely 1548, but I'm not going to change it yet, because I know that if I do, then they'll break the current pattern.
2025-09-04 20:02:11 -04:00
Lusamine
6438a74940 Extend WCS Toedscool valid end date
Contrary to what the code cards stated as the end date, they could still be redeemed into August 31st in UTC+14.
Thanks to the user Pikachu from Discord from testing!
2025-09-01 16:30:35 -05:00
Carbonara
293107dbcf
Add more info for BW and ORAS flags (#4551)
* Provide more details for TransferMet

Defaulted Japanese, Korean and Chinese (Traditional and Simplified) to the English line due to not knowing well the language and not being easily fixable unlike other European languages (the line was wrong in any case so it needed a retranslation, and nearby lines are also untranslated).

Other languages than English don't seem to have a widespread term for the Crown Beasts, they use something like Legendary Beasts even for those ones, so using this term should be easier to understand.

* Give more details for some ORAS flag lines

- Specify where the Statuette is exhibited (XY and ORAS)
- Specify where the gifts and eggs are, and which Pokémon are in-use for the in-game trades
- Specify that the Diancite and Prison Bottle are part of events ()
- Specify where the Winstrate Family is located
- Add trainer location and title for rematches in English and Spanish (translated), Korean, Chinese Hant (untranslated, English lines). Not sure how to take care of it for the other Chinese translation, and the Japanese translation already mentions the location, so no need to alter it.
- Fix an issue in French where the old hot-springs visitor was partially using the English name instead of the French one

* Add more info for some bw flags
2025-09-01 09:34:17 -05:00
sora10pls
765b0b3680 Latest distribution outbreaks, Reg J ribbon legality 2025-08-31 20:05:54 -04:00
Kurt
ded9d54399 Misc tweaks
fixes legality report not showing localized
fixes pcjp5 seed->table generate
removes eternamax from go_home pkl
waiting for raids before hotfixing exe & pushing nuget
2025-08-31 17:12:20 -05:00
Carbonara
597bbbcf8d
Update the French translation (#4550)
* Update legality_fr.json

- Translate the added lines in French
- Fix Œuf (Egg) not having a capital letter for one of the lines

* Update encounter_fr.json

- A space should be present before a ":" character in French, fixed
- Translate Origin Seed (still using the English term Seed for consistency atm, and since this term is more widespread)

* Translate setparse_fr

* Update movesource_fr.json

- Move is supposed to be Capacité in French, not Attaque
- Added a space before ":"
- Changed the order when mentioning a special move to avoid confusion (would have displayed Capacité spéciale otherwise, which is the old term used for Abilities in French, unrelated to this file)

* Update flags_bw_fr.txt

- Translate remaining flags
- Add proper locations for where some of the flags are located
- Give more details for the trades

* Update flags_oras_fr

Translate all untranslated lines to French, specify the gender for legendary Pokémon only available in female, fix Famille Stratège not being accorded.
For the rematch, I added the trainer class and location for all of the lines in French (speaks more than just a name you will not memorise): let me know if you want to have this ported to other languages.

Other:
- Fix a typo where Fallarbor Town was written as Fallabor Town, Verdanturf Town as Vendanturf Town
- Fix an issue where Pikachu Cosplayeur was written as Pikachu Cosplay
- Fixed the origin message

* Update MessageStrings_fr.txt

- Translate new lines
- Fix some wordings
- Fix MsgIndexAbilityGame being duplicated from MsgIndexAbilityRange instead of being its own message

* Update lang_fr.txt

Translate some lines:
- Pass Powers, O-Powers, Gen 7 Throw Styles translated
- Misc lines translated or fixed

May or may not take care of other entries in the future, it depends on when I'm motivated and for what
2025-08-31 17:10:56 -05:00
Kurt
a0584ca5f5 Update MedalVerifier.cs 2025-08-31 01:21:24 -05:00
Kurt
9e8d8ccc62 Update 25.08.30 2025-08-31 00:01:14 -05:00
Kurt
5f5ec65c4a Add training bag effect party stat
ty Anubis
rearrange some of the comments for clarity

Co-Authored-By: Lusamine <30205550+Lusamine@users.noreply.github.com>
2025-08-31 00:01:14 -05:00
Easy World
82fdbbb777
update Chinese translation (#4547) 2025-08-28 22:26:40 -05:00
Kurt
c3b3b611aa Super Training: another look
revise criteria for ribbon
add legality check for training bag values
add localizations for distribution training regimens (never distributed)

Co-Authored-By: Lusamine <30205550+Lusamine@users.noreply.github.com>
2025-08-28 20:19:05 -05:00
Kurt
1056b04d0c Gen1: update sea slots for tentacool<->jynx remap
this area (sea surf) didn't get internal->national in my conversion script years ago... nice.

refer to dumper where they were manually fixed and re-dumped to the binlinker pkl format.
366e035034
2025-08-28 01:04:58 -05:00
Kurt
b9625c75b4 Update GUI translations
includes the splash screen disable setting that was added recently
2025-08-26 12:55:12 -05:00
Kurt
79b3bd4f74 Gen3: revise PCJP internal logic
deduplicates a little; renames the PID type label to something less confusing.
2025-08-26 12:54:53 -05:00
Kurt
ed419c49bd Gen5: Pokestar fame disallowed on ditto
transform in moves, disallow participation (can't copy the opponent!)
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/33/#comment-296835
2025-08-26 12:53:53 -05:00
Kurt
82744a12ef Update dependencies 2025-08-22 16:33:04 -05:00
Kurt
dd5d6a4e39 Minor tweaks
no functional change
2025-08-22 16:33:04 -05:00
Kurt
5beabb2020 Hide slot change publisher's list, add/remove 2025-08-22 16:33:04 -05:00
Kurt
3f32ee5814 Allow SlotView to wrap a multi-entity file
Not currently used, but can be in the future.
2025-08-22 16:33:04 -05:00
Kurt
5dce5e93e9 Add misc slot legality flag skipping
For BD/SP with partially generated mons, ignore the triangle annoyance.
2025-08-22 16:29:03 -05:00
Kurt
f0423610af Add localization for ShowdownSet parse fail popup 2025-08-22 16:28:03 -05:00
sora10pls
d8853b6de6 Update Shiny Chien-Pao Wonder Card ID 2025-08-21 20:05:10 -04:00
sora10pls
c1211fe84c Add latest distribution outbreak data 🌎 2025-08-17 20:05:16 -04:00
Kurt
ea85d5e6b0 Minor clean 2025-08-16 09:21:16 -05:00
Kurt
e25b2037e2 game->version 2025-08-16 09:21:16 -05:00
Kurt
89cb15b9cd Extract GetBlankSaveFile to static class 2025-08-16 09:13:07 -05:00
9Bitdo
2aa6552312
Add WCS 2025 Toedscool & Luca Ceribelli's Farigiraf date (#4544) 2025-08-15 14:49:40 -05:00
Kurt
c3873165af Add farfetch'd apostrophe 1/2->7
the other ver 1.2 bug that was fixed in 1.3
every other diff noted in https://github.com/kwsch/PKHeX/pull/4545#issuecomment-3192383636 is an inaccessible text entry. Farfetch'd is set by the game on capture, just not enterable via text entry.

Restore 0-9 for both int/jpn as Porygon2 exists. 01[]345... are un-enterable, but whatever. Should be a text entry check rather than a transporter check.
2025-08-15 14:25:45 -05:00
drabu96
3e8c355ef7
Fix pk1/pk2 char conversion to >=pk7 (#4545)
* Fix pk1/pk2 char conversion to >=pk7

Fixed conversion of a gen1/2 "․" to ASCII "." when converting to pk7 or newer.
Added a test that verifies the fix.

Addresses the issue: https://github.com/kwsch/PKHeX/issues/4543

* Update StringTests.cs
2025-08-15 13:30:07 -05:00
Kurt
f0c8b86728 Minor startup tweaks
Allow settings to skip Splash Screen (and just launch the main form without fuss, cuz why not?)
Handle scenario where PKHeX.Core.dll fails to bind during Settings fetch -- handle via static constructor instead of Program.Main() so that errors pipe to the Release error handlers.

Run update check in another thread, after Main is shown, so that offline users don't have to wait 3 extra seconds for it to timeout and show.

Revise the startup animation to just show the Main form rather than minimize->restore. The previous "hack" was designed so that if users clicked anywhere after launching the program (thus losing focus) the Main form would re-capture it. Activate() works fine now (maybe it didn't in the past?)

Removes "dark" startup arg; do via settings. Users really won't have a separate launch config like they might for HaX via .bat
2025-08-14 23:57:20 -05:00
Kurt
d658da44c6 Minor tweaks
Fixes conversion compatibility override being reverted when settings is reloaded by user (via GUI)
2025-08-13 22:05:43 -05:00
Kurt
93a381bfde Startup: load config before Main ctor
Allows specifying Dark mode in settings now.
Extracts reusable settings objects to PKHeX.Core (drawing/GUI stuff kept in WinForms).
Updating settings now refreshes backup paths/mgdb
2025-08-13 20:59:46 -05:00
Carbonara
80487a514d
French - Update the legality file (#4542)
Fix a lot of weird translations/inaccuracies, translate untranslated lines.

Notes:
- BallEggCherish & BallEggMaster were missing the mention of normal, which is incorrect (some eggs were distributed in a Cherish Ball in 2017)
- EncStaticPIDShiny was using the term shiny-locked to describe it as if the error could only apply to non-shiny Pokémon set to be shiny, but from the English formulation I understand it as implying the opposite too (e.g. a shiny only Pokémon set to be non-shiny could display the warning), so I renamed it
- FatefulGiftMissing was mentioning the Mystery Gift DB being edited which wasn't the case in the original, edited
- G2OTGender: mention Pokémon Crystal instead of just Crystal since I struggled to understand Crystal meant the game rather than the name
- No clue if the Mood and Spirit stats have an official French name, so a direct translation from the English term was used
- MemoryArgBadItem_H1 was specifying that the Pokémon cannot have held any item rather than a specific item, fixed

- I translated HT (Handling Trainer) as Der. Dres. (Dernier Dresseur, Last Trainer), takes more space but clearer than using DD, or just an abbreviation that wouldn't mean anything out of the box
- TransferMet: no clue what Crown is supposed to mean, I changed it to be more accurate (from what I understand, it's supposed to be for Relocator Pokémon, so legendary beasts and Celebi, so I specified the expected potential encounter locations for them). If this is not correct, feel free to change it or tell me what it's supposed to be.
2025-08-13 20:40:10 -05:00
Kurt
330a6f088c Ball: gen6 roselia allow apricorn inherit (no HA)
budew was correct, somehow the flag for roselia-apricorn wasn't set like the other splitbreed species mirroring.
2025-08-12 01:12:54 -05:00
Kurt
8633344187 Fix vertical alignment of IsEgg checkbox
off by 1 pixel compared to pkrs
rearrange some margins on cosmetic/stats tab to avoid some clipping
2025-08-12 01:11:56 -05:00
Kurt
47efdf8a90 PK5: add pokestar fame edit, update translations
Remove pk4 walking mood from extrabyte list
2025-08-10 23:09:27 -05:00
abcboy101
8f9fec0b13
PBR: Add Battle Pass, Gear, Trainer Info editors (#4540)
* Fix PBR checksums

* Fix PBR desyncs between Data/Container

- CurrentSlot.set reloads Data from Container, so copy other.Data to Data after
- When editing the OT name, use Data if the requested slot is the current slot

* Correct PBR party offset/size

* Add GameVersion.BATREV

* Add Gear Editor

* Add Battle Pass/Trainer Info Editor for PBR

* Minor tweaks

* Fix ResetGear/UpdatePresetIndexes
2025-08-10 22:43:03 -05:00
Kurt
3a4fe49182 PB7: Add Spirit/Mood names, editing, checks
Official Names from guidebook: https://discord.com/channels/497890797115670539/950895799401848852/1285451683945779303
Removing from party (except starter): reset to 100 -- not ALWAYS like the previous logic once did.
Flag any non-party/starter if not 100-100.
Add separate GUI controls (not to confuse with Gen4's Walking Mood)
2025-08-10 02:16:37 -05:00
Kurt
ff0f4727dd Extract logic from SaveUtil
BlankSaveFile -> creation of blank save files
SaveFileType -> listing of all savefile types

Blank save file arg passing is now clearer
Instead of SaveFile? return, use TryGet pattern with nullable annotations to indicate success
2025-08-09 21:55:55 -05:00
Kurt
d4bbb6dd02 Misc tweaks
Ball: all ball IDs are in a sequence +1'd. No need to have an array when we can just increment within the range. Ez removal of static constructor and allocation, and better iteration (and skips index 0!)
Disallow E/FR/LG item deposits of anything besides general pouch (R/S is like G/S/C, any pouch). Confirmed via testing in-game and matches Bulbapedia's testing.
Disallow Gen2 held item being an HM; no longer considered valid as a tradeback catch rate value. Oops that HMs were "allowed" for so long!
Encode Gen2 held items to bitflag array to not need to compute the merged array. Relocate duplicated logic to a single location in ItemConverter.
Fix gender-changing marill edge case comparing the wrong ratio
2025-08-08 23:43:22 -05:00
Kurt
af416dc71a Add gen4 mood to pkm editor (cosmetic) 2025-08-08 00:42:15 -05:00
Kurt
141aa97e2b Merge branch 'master' of https://github.com/kwsch/PKHeX 2025-08-08 00:19:34 -05:00
Kurt
fd3af56ec4 Legality: add date sanity check, shinyleaf/mood
date: If location specified, ensure valid date; if no location, ensure zeroed.
shinyleaf: check bad bits, check crown has all leafs.
mood: rename from pokeathlon, now sbyte. All values possible, only flag outside of party in HG/SS.

Revise HGSS slot setter to wipe mood to match game behavior (and thus not retain mood to be flagged by the legality check).
There's currently no editor for it, but maybe I can add it in a future commit.
2025-08-08 00:19:33 -05:00
Kurt
101aa17a8d Update translations for dex revisions
see previous commits
2025-08-08 00:08:57 -05:00
Kurt
b291378e78 Localizations: better thread safety init
No need for a dictionary, just allocate an array and index in via one of the supported languages.

Updates GameInfo to use the abstraction to prevent some duplicate work on a very-hot startup. Threads repeating the same work vs Thread n++ simply waiting for previous thread to finish init is the ~same amount of time, with less overall CPU usage (so this is a positive improvement).

EnterScope is "safer" than explicit `lock (x)` due to the using syntax releasing the lock even on exception (not expected, but might alleviate issues on developer-initiated feature upgrades).

Co-Authored-By: HexByt3 <80122551+hexbyt3@users.noreply.github.com>
2025-08-07 21:15:32 -05:00
Kurt
4ed1d12859 Widen batch editor GUI
Fiddled with the spacing to be more 4px spaced rather than inconsistent.
Dropdown for property select is now wider, and doesn't cut off long names like OT memory feeling.
2025-08-07 21:15:32 -05:00
sora10pls
cfdc571f9c Update Shiny Wo-Chien Wonder Card ID 2025-08-07 20:05:25 -04:00
Kurt
28e20c4ea3 Enhance gen6 dex interactions
Uses the rewritten Gen5 object as the base rather than the old zukan abstraction
Adds National Dex unlocked flag for editing

probably best to extract an interface as there's no need to have a shared abstract Zukan class across generations.
2025-08-05 09:55:58 -05:00
Kurt
ceff28210a Fix PBR init
Closes #4534
2025-08-05 09:52:03 -05:00
Kurt
031f7f4e6c Update Zukan5.cs 2025-08-04 02:56:57 -05:00
sora10pls
d78aff9e03 Add latest distribution outbreak data ❄️ 2025-08-03 20:15:14 -04:00
Kurt
112086d85b Update SAV_Pokedex5.cs
am i gen 4 or gen 5? why not gen4.5?
2025-08-03 14:34:34 -05:00
Kurt
d732520762 Rewrite Gen5 dex editor & backend
Closes #4533
2025-08-03 14:24:22 -05:00
Kurt
1d57facd22 Minor tweaks
extract max/min level to const
fix max species ID in filtered sources
2025-08-02 21:02:11 -05:00
Kurt
720ac7ead7 Misc tweaks
add localization for ` @ lv{0}` for verbose report
2025-08-02 01:40:18 -05:00
Kurt
0f106c9c82 oops
not actually exclusive
for eggs, assume that ability inheritance flips the bit
2025-07-31 23:12:17 -05:00
Kurt
8d99a7a56d Misc gen5 PID random number updates
Use 64bit RNG for PID creation, mimic how the game generates PIDs with the impossible value quirks
shiny lock the HA eevee in castelia
remove duplicate encounters (no longer needed due to form mutation API being mature)

ty @Lusamine for obtaining some samples and testing the PID generating algo
2025-07-31 22:40:56 -05:00
HexByt3
b1464a0941
Update StartupArguments.cs (#4532)
BallDataSource => VersionDataSource
2025-07-30 15:19:38 -05:00
Kurt
4c0ad92edb Allow bulkanalysis w/o save file
secret constructor that was for tpci, now available for all /s
2025-07-30 01:25:20 -05:00
Kurt
11f33985c4 Move files, xmldoc, simplify
External check can call AddLine. The intent is to not allow it to remove previous parse results.
2025-07-30 01:24:02 -05:00
Kurt
90cfa59102 Misc tweaks
Pass Analysis to external localizer
Cache index for bulk analysis flagged slots
2025-07-29 20:12:41 -05:00
Kurt
223840f943 Revert "Convert sln to slnx"
This reverts commit 5d2ebf4d0a.
2025-07-28 21:51:16 -05:00
Kurt
efefd78caa Update dependencies
pipeline will stay broken until nuget 6.15 releases (it's been 2 month
2025-07-28 21:51:10 -05:00
Kurt
5d2ebf4d0a Convert sln to slnx 2025-07-28 19:07:25 -05:00
Kurt
47092a2df0 Misc tweaks 2025-07-28 18:45:30 -05:00
Kurt
13154d70f8 Split missing/invalid ribbon results
Generating the message is repeat work, but the deferred message is still better in the long run.
1x -> 3x, but that's worst case.
2025-07-28 18:12:14 -05:00
Kurt
c331d97e89 Fix 2 localization mis-mapped
tested 100k, no more thrown Exception's
2025-07-28 02:11:26 -05:00
Kurt
fb814ac878 Add xmldoc 2025-07-28 00:14:33 -05:00
Kurt
904fd2020c Add external legality check functionality 2025-07-27 23:47:45 -05:00
Kurt
b3d3c9e562 Minor fixup on localization resources 2025-07-27 21:49:22 -05:00
Kurt
65420b0878 Actually test the bulk check result formatting 2025-07-27 21:20:34 -05:00
Kurt
d99ec943fe Fix stragglers from dual PR merge 2025-07-27 21:03:19 -05:00
Kurt
44486fdf85 Merge branch 'master' of https://github.com/kwsch/PKHeX 2025-07-27 21:00:38 -05:00
Kurt
f370c0cc39
Memory<byte> Refactoring (#4527)
`SaveFile` and `PKM` classes now use `Memory<byte>` instead of `byte[]` to store their primary backing array data.
2025-07-27 20:57:10 -05:00
Kurt
13a4d472bc
Deferred Humanization of LegalityAnalysis (#4531) 2025-07-27 20:54:58 -05:00
Easy World
a5cdb0e27c
Update Simplified Chinese translations (#4529)
* Update translation

* Update Simplified Chinese translations

Improves and corrects Simplified Chinese translations in legality check strings, program messages, and WinForms UI. Updates terminology for better localization accuracy and user clarity.

* revert legality check strings

Will be manually updated in future commit by kwsch -- avoiding merge conflicts for now

---------

Co-authored-by: Kurt <kwsch@users.noreply.github.com>
2025-07-27 16:57:16 -05:00
sora10pls
ba2c397cb9 Add placeholder Shiny Treasures of Ruin date ranges
Wonder Card IDs to be determined, 9996-9999 until released
2025-07-27 15:49:49 -04:00
sora10pls
a49f832f3a Add placeholder Shiny Treasures of Ruin date ranges
Wonder Card IDs to be determined, 9999 until released
2025-07-27 10:06:43 -04:00
abcboy101
fef5ff3edb
Translate DataGridViewColumn header text (#4528)
* Localization update

* Translate DataGridViewColumn header text

Temporary label hack in SAV_FolderList is no longer needed

* Localization update
2025-07-26 08:43:23 -05:00
Kurt
6fd644069c GUI: swap gen3 marking visual order
https://projectpokemon.org/home/forums/topic/66916-order-of-cosmetic-markings-in-gen-3/#comment-296256
2025-07-24 14:46:59 -05:00
Kurt
747d083975 Update EncounterEnumerator5.cs 2025-07-23 23:13:53 -05:00
Kurt
a02712e375 Add gen5 spin trade as valid link trade loc
ty Cappy for reporting, and @PP-theSLAYER & @Lusamine for testing.
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/32/#findComment-296214

gen4 spin trades in pt/hgss do not set the wrong value -- gen5 only bug.
2025-07-23 22:37:39 -05:00
Kurt
9d49458787 add a new 0xFFFFFFFF legality check for gen5 eggs
lol nice gamefreak
2025-07-23 22:37:39 -05:00
Fábio H. Attard
ed9d46cad9
Fix SAV1 fallback detection for Yellow version, when starter is not set yet (#4526) 2025-07-23 01:03:22 -05:00
sora10pls
025a812e2c Add latest distribution outbreak data 🍃 2025-07-22 20:15:54 -04:00
Kurt
a548614646 Safeguard moveset suggestions for Gen2->Gen1 enc
Removes all gen2 moves from a suggestion when requestor is a gen1 format mon (can't have gen2 moves). This is the only case where moves are less available (ignoring HOME's multi-format moving, which cannot transfer moves to less-available contexts anyway).

ty Asia81
https://projectpokemon.org/home/forums/topic/66877-pokemon-red-unknown-suggested-moves-for-poliwhirl/
2025-07-21 17:05:49 -05:00
Xzonn
577278b7b8
Update Chinese translations from official translations (#4525)
* Update Chinese translations from 52Poke Wiki

* Fix memory translations

* Update feelings
2025-07-21 15:30:43 -05:00
Kurt
42835e9aac Misc tweaks
Adds a debug hex file loader from clipboard
sav1 current box if empty -> set if box is desync'd
wb7: add comment note of "real" value
2025-07-16 23:02:57 -05:00
Kurt
0e9a3129b4 Misc stuff
Adds GameSync ID for Gen5
Add GiftRibbons to gen3 mainline saves
Add defined chars from Transporter update 7 (latest)
2025-07-13 01:00:43 -05:00
Kurt
b4edc389bb Rearrange gen3/4 deferral enum
null check necessary for unset deferral needing to ignore max
2025-07-12 01:29:37 -05:00
sora10pls
6a9af0a4a3 Add latest distribution raid data 🤜🤛 2025-07-10 20:05:14 -04:00
Kurt
cdb5770f5f Minor tweaks
Fixes cosmetic issue on gen4 seed example times
no functional change otherwise
2025-07-08 23:00:27 -05:00
Kurt
0b77aa5729 Add swsh string check
Replace debug assert in SWSH encounter finder
Add dir arg for pogo pickle reload for debug builds
re-add latest outbreaks
2025-07-06 18:48:49 -05:00
Kurt
16f30ebe2d Handle gen6 wc's with bad OTs
https://projectpokemon.org/home/forums/topic/41065-gen-7-compilation-of-events-that-change-ot-when-traded/
2025-07-06 17:16:11 -05:00
Kurt
a957569caa Update WC9.cs 2025-07-06 11:40:26 -05:00
Kurt
aee9171249 Remove length check
Nope, doesn't check length
2025-07-06 09:56:02 -05:00
Kurt
d0f73c96c1 Ignore h/w odds on wc9 2025-07-06 09:35:17 -05:00
Kurt
31c52b6cd2 Add HOME trade OT replacement functions 2025-07-06 09:21:13 -05:00
Kurt
157210de08 Minor tweaks
Add a mutable trainer info class that mirrors the SimpleTrainerInfo
2025-07-06 09:03:44 -05:00
Kurt
082f9cb340 Add Title to some import dialog prompts 2025-07-06 09:02:44 -05:00
Kurt
89ada33e24 Add ReplaceTrainerName api 2025-07-06 02:15:10 -05:00
Kurt
59047cda25 Update outbreak pickle
More pkNX logic fixes (just now)
2025-07-05 16:31:17 -05:00
Kurt
7864907f81
Add Misty Mark recognition & weather bleed for slots (#4519)
Closes #4036
Weather now handled upstream in pkNX, with bleed applied to individual spawn points, serialized to consumable legality binary.
Misty marks thanks to @Lusamine and her discord helpers -- also utilized https://github.com/kwsch/MistyMarkVisualize to help visualize and filter entity dumps => spawn-position.
2025-07-05 00:08:29 -05:00
Kurt
607901dda1 Misc tweaks 2025-07-04 16:06:26 -05:00
Kurt
fbde4f585d Small enhancement to encdb ui
Shift click type checkbox to un-check others
Search button disables itself if the search would return nothing
When using tabs as criteria, if hyper training is available, only require the specified imperfect IVs for the encounter

enc9: be a little nicer and allow a slight search lag by only considering actual encounterable attempts (passing slot check). Can infrequently obtain a 0-speed shiny Foongus via encDB with a few attempts.
2025-07-04 01:35:48 -05:00
Kurt
c19a4605d5 Misc tweaks
No functional change
2025-07-04 01:32:25 -05:00
sora10pls
a471f1bd8c Add latest distribution outbreak data 🐭 2025-07-03 20:06:27 -04:00
Kurt
bda5baab88 PB7: Permit GO in BelongsTo check
Closes #4518
2025-07-03 18:12:08 -05:00
Kurt
e9d299fc92 Honor more shiny requests in gen6+ encounters 2025-07-02 01:08:50 -05:00
Kurt
e69f6b05f8 Revise bdsp egg gen to follow correlation
no, detecting this correlation is not realtime, and never will be (99.99999% sure).
allow generating of shiny eggs by spoofing a link trade on them.
2025-07-01 01:07:31 -05:00
Kurt
3b4661d40d Revise dmax adv shiny PID generating to match game 2025-06-28 12:32:45 -05:00
Kurt
1258d96883 Add some CodeAnalysis attributes 2025-06-28 00:38:05 -05:00
Kurt
5e0ee30235 Split ShinyUtil.GetIsShiny into separate methods
Some past gen usages weren't passing the bitXor compare `8` and were thus using the Gen6+ 16 value. Let's be explicit.
2025-06-28 00:36:46 -05:00
sora10pls
fd5b9f7db9 Add handling for GO Pass: Ancients Recovered level ranges
Regi encounters have a range of possible levels when encountered from "Ancients Recovered Timed Research: Legendary Giants", allowing for as low as Lv. 1
2025-06-22 17:44:24 -04:00
9Bitdo
cd587828df
Add PJCS 2025 Ray Yamanaka's Amoonguss date (#4516) 2025-06-21 09:28:55 -05:00
Carbonara
15f61ef1ca
DP - Add Spear Pillar related flags (#4512)
* DP - Add Spear Pillar related flags
* HGSS - Add flags tied to the Slowpoke Well
2025-06-20 23:45:07 -05:00
9Bitdo
4153b0aab7
Add PJCS 2025 Hyuma Hara's Flutter Mane date (#4514) 2025-06-20 23:44:19 -05:00
Kurt
8c85a03d78 simplify mysterygift clone
move declaration to derived class, can return specific type now
don't use AbilityType directly, use the ability permission computed property for legality checks. probably can remove this explicit MG method in the future.
2025-06-19 23:36:59 -05:00
Kurt
870dbb1ce3 MysteryGift.Empty -> IsEmpty 2025-06-19 23:35:21 -05:00
Kurt
e3ecf7b593 Fix multifolder mgdb set
no usages currently would pass multiple folders for mgdb, but this fixes that potential bug behavior where it sets the array every folder (gradually repeating allocation work).
2025-06-19 23:33:09 -05:00
sora10pls
554321e5b7 Add latest distribution raid data ❄️ 2025-06-19 20:04:15 -04:00
Kurt
fd72b6ea46 Misc tweaks
no functional change
2025-06-18 16:23:31 -05:00
Kurt
ec4cfc807f in 2025-06-18 16:23:07 -05:00
Kurt
7ce73eb55a Revise enc5 pid/gender setpinga
Some properties weren't being honored; lift nature/ability/IV assignment out of monochrome and do in each enc type.
2025-06-18 16:22:14 -05:00
Kurt
e10d272cde Update SummaryPreviewer.cs 2025-06-16 20:45:08 -05:00
Easy World
30d51fc880
fix(zh-Hans): update legality check messages for type and evolution validation (#4511) 2025-06-15 02:50:58 -05:00
Kurt
c274ff6f3a Add more generator filtering criteria
Gen6+ slots now try to give shinies if possible & requested
Gen3/4 slots now try to respect IV requests if only a couple are requested
Removes PIDGenerator; no remaining uses (either all inlined or extracted to specialized generator classes). API usage wasn't recommended anyway, as it was incomplete and did not honor all correlations. We want to try to generate it right the first time.
2025-06-14 21:45:16 -05:00
Kurt
a57914cf27 Add MonochromeRNG (gen5) generating methods
Extracts a bunch of logic from PIDGenerator
2025-06-14 21:42:47 -05:00
Kurt
422a082f98 Split shadow team tests from shadow test
Use TheoryData to wrap instead of `object`
2025-06-14 21:41:01 -05:00
Kurt
416e519073 Misc tweaks
No functional change, just xmldoc and small inlining
2025-06-14 21:39:49 -05:00
Kurt
fae4340b39 Fix typo 2025-06-14 21:38:10 -05:00
9Bitdo
8cb059e6f6
Add NAIC 2025 Wolfe's Incineroar date (#4510) 2025-06-13 11:16:32 -05:00
Kurt
9e501ea527 Add more xmldoc, fix item9 again
really not a fan of the abstract class, probably better to rewrite everything another day to be less dumb

bug was due to the trickle-down then clearing; object references ended up being duplicated when Unobtainable item placeholders were removed from the pouch and things trickled.

don't bother removing unreleased item data if its quantity is 0.
2025-06-11 22:02:34 -05:00
Kurt
d072f14570 Update EncounterCriteria.cs 2025-06-10 18:53:29 -05:00
Kurt
56e06dcbc1 Update SAV_MysteryGiftDB.cs 2025-06-09 16:55:41 -05:00
Kurt
58ae75cc6c Update 25.06.09 2025-06-08 16:34:06 -05:00
Kurt
ccfa58e5f1 Split PathUtil from Util, add more xmldoc 2025-06-08 16:33:31 -05:00
Lusamine
5b70ca0397 Document SV block for moon phases 2025-06-08 14:21:06 -05:00
Kurt
6784d5f045 Merge branch 'master' of https://github.com/kwsch/PKHeX 2025-06-08 08:38:36 -05:00
Lusamine
0de74b44ba Add LA block for in-game time in minutes 2025-06-07 11:19:40 -05:00
Kurt
ba2245d7d8 Add more xmldoc
Updated PIDIV tests to use IV32 rather than IV sequence checks. Now marked as Obsolete to prevent myself from reusing PKM.IVs getter :)
2025-06-07 09:41:02 -05:00
9Bitdo
5c77f79d88
Add PTC 2025 홍주영's Porygon2's date (#4508) 2025-06-07 08:12:26 -05:00
sora10pls
f78e929428 Pokémon Scarlet & Violet are finally playable 2025-06-04 20:05:50 -04:00
Kurt
d2594d7867 Misc tweaks
nothing needed for 4.0.0, everything works as-is
2025-06-02 21:01:16 -05:00
Carbonara
c17fc13fd2
GSC - Give proper name for decoration and puzzle flags (#4505)
Also fixes some lines in the French translation.

For the decorations, the formatting is based on the one of the games, but with names not written in ALL CAPS. As I do not have English copies for the names, I've used the formatting present on this page, https://bulbapedia.bulbagarden.net/wiki/List_of_decorations_in_Generation_II, and I've been using the Nintendo Power formatting for the plants.

There was an error of labelling: an entry was named DECO_PLANT_4, but there are only 3 plants available. After checking, this entry is actually the Town Map, that was listed separately as PLAYERS_ROOM_POSTER. As such, the entry named PLAYERS_ROOM_POSTER is incorrect. I have no idea what this is, if it does anything or not (doesn't seem to change anything when enabled or disabled at a first glance), so this would need someone to check that. For now, I removed the entry from the list, but feel free to readd it or cancel that change if it is needed anyways or if you can figure what it is.

On a similar domain, DECO_STARMIE_DOLL is actually a Staryu Doll, though the in-game name on its own is enough to fix the issue.

The zh_hans translation had some lines not translated in the proper order, so I corrected those lines by putting them in the proper order. (checked by combining translation software, wiki page https://wiki.52poke.com/wiki/%E8%A3%85%E9%A5%B0%E7%89%A9%E5%93%81%EF%BC%88%E5%9F%8E%E9%83%BD%EF%BC%89, and manual search of the Pokémon names to ensure everything was correct).

The decoration flags don't necessarily seem to fully work: they work to unlock a decoration, they work to remove it if unlocked through PkHex, but just disabling the switch on a save file that got the decoration through normal play doesn't seem to always work from what I can see, may need to be investigated.

Documenting the changes made for the French translation also.

Some of the decorations have different names between French Gold/Silver/Crystal and French Stadium 2, documenting them here:

French Gold/Silver/Crystal:
- "Lit à Plumes"
- "Lit :Rose"
- "Tapis :Rouge"
- "Tapis :Bleu"
- "Tapis :Jaune"
- "Tapis :Vert"

French Stadium 2:
- "Lit en Plumes"
- "Lit Rose"
- "Tapis Rouge"
- "Tapis Bleu"
- "Tapis Jaune"
- "Tapis Vert"

For the French translation, I'm using "Lit à Plumes", while for the other entries I'm using the names from Stadium 2 (no need for ":" in the names, and the formatting is odd).

I'm also adding a space for "JouetPikachu Surf" to be "Jouet Pikachu Surf" (space lacking due to the lack of space in the original text - Jouet should also normally be Poupée, but was translated as Toy due to the lack of space, but I'm not renaming it to not be confusing).

Fixing other French mistakes while I'm at it.
- Bec Pointu was written as Bec Pointy in the GSC flags
- Made the flag edit warning message be shorter to be able to be fully displayed
For the Entralink lines:
- Translated w/o to sauf, and wrote No in lower letters
- Changed NOUVEAU to NOUV. to fit in the textbox
- Changed Meilleurs records: to Total meilleurs scores to be clearer and not use ":" due to being odd
- Fixed Verrouillé and Déverrouillé being misspelled
- Changed Le plus de participants to Record nbr. joueurs to fix in the textbox and be more concise
- Fixed the line Verify status which wasn't using the same formatting than another similar line, and had several typos

The translations for the Entralink may not be final, it depends on if I revisit the translation of PkHex to complete it in some categories later on or not.
2025-06-02 18:38:20 -05:00
sora10pls
bc8ea34474 Add latest distribution outbreak data 🦆🖥️ 2025-06-01 20:05:14 -04:00
Kurt
602b1b6371 Add more xmldoc 2025-06-01 11:08:07 -05:00
Kurt
675c017a56 Merge branch 'master' of https://github.com/kwsch/PKHeX 2025-05-31 22:51:57 -05:00
Kurt
bf9e53efa1 Misc tweaks
Add more xmldoc
Simplify some expressions
Reduce unnecessary logic
2025-05-31 22:51:55 -05:00
Kurt
75cf9b0934
Use more modern PluginLoader implementation (#4503)
* Use System.Runtime.Loader to load plugins

Can now unload plugins if need be. Load->Update->Unload->Load(new) ?
2025-05-31 21:03:12 -05:00
Kurt
93d9292d83 Minor clean 2025-05-30 17:41:54 -05:00
sora10pls
af92d43650 Add more bugs to Pokémon Scarlet & Violet 2025-05-29 20:06:34 -04:00
Kurt
87d55fc303 revise xd ID check to exclude PAL
ty John_0902 on discord
2025-05-29 10:16:51 -05:00
Kurt
fd766a5506 Filter db search dropdowns for current context
"why can't I find Yveltal in SV?" because it doesn't exist in the game
now matches the dropdowns of the main PKM editor
2025-05-29 10:04:06 -05:00
Kurt
c9b8a5b893 Misc tweaks
No functional change
2025-05-29 10:03:17 -05:00
abcboy101
53ddcfb0e4
Update Switch badwords to v20.1.0 (#4502) 2025-05-28 08:54:32 -05:00
Kurt
c4199b26ec Minor startup optimization (resource sizes)
-288 KB (-31%) across lvlmove/eggmove/evolve binaries
redesign the levelup bins:
- be moves_levels rather than the "official" -1 stop of the past era.
- gen1/2 reformatted from byte,byte[] to ^ to skip initialization work
redesign the eggmove bins:
- be simply moves[], rather than the "official" -1 stop of the past era; now is just a struct to keep the array readonly with no further allocation.
- same for gen2 skipping initialization byte[]->ushort[]
- for gen7/8 formtable indexed, just use the personal table indexing style of SV.
added a 16-bit version of BinLinkerAccessor as start/end offsets of <65KB files are always 16bit. Saves a fair bit of space in eggmoves/evo where there's often 0 entries for a species-form.

Obviously binlinker16 is an unofficial format, but there's no need to replicate official serialization formats if we instead use a universal & maintainable alternative. Plus they don't even use BinLinker across all their games.

Adds a debug BinLinkerWriter because I'm tired of digging up the zipping implementation :)
2025-05-25 16:27:05 -05:00
Kurt
1d9fc99413 Misc tweaks
Add Count to IPersonalTable
Revise EncounterOrigin for evo chain search to use Context instead of Version
Use ITrainerID*ReadOnly as pivot for Trainer ID verification skip
Add more xmldoc
Reconfigure pla dex task fetch to be nullable, match fbs field ordering for clarity
2025-05-24 22:59:13 -05:00
abcboy101
4963d11c14
PBR: Fix box names, add play time (#4501)
* Allow empty box names in PBR

Before copying Pokémon from a DS game, all of the box names are zeroed-out. Allow these values to be read/written normally.

* Preserve current slot when cloning PBR save

Certain editors like SAV_BoxLayout clone the save file before editing it. This preserves the selected slot in the clone instead of resetting it to 0.

* Add PBR play time accessors
2025-05-24 08:15:55 -05:00
Kurt
48954533b5 Make mysterygifts memory-backed objects 2025-05-24 00:10:57 -05:00
Kurt
be67b007d5 Misc tweaks 2025-05-24 00:10:02 -05:00
Kurt
2f77b9c2aa Add xmldoc 2025-05-23 20:44:06 -05:00
Kurt
1c03fc3e5b Minor clean 2025-05-23 20:07:03 -05:00
sora10pls
e8ce5c1317 Add latest distribution raid data 🦈🌍 2025-05-22 20:03:44 -04:00
Manu
e72995e898
Standardize IGenerateSeed32 API for gen9 raid encounters (#4497) 2025-05-20 22:53:32 -05:00
abcboy101
09f654fd34
Use correct badwords list (#4498) 2025-05-20 22:35:16 -05:00
Kurt
fb803c6e4d Revise region handling and game version mapping
Updated region handling logic in XK3.cs to treat PAL and NTSC_U as equivalent and adjusted remapping logic accordingly.
Added a check in PKMEditor.cs to map GameVersion.COLO and GameVersion.XD to GameVersion.CXD for consistency in filtered data sources.
2025-05-19 23:37:41 -05:00
Kurt
064e4293a0 Save Language first for gen3pkm
So that Nickname encoding can work as expected. Also fixes mainline int<->jpn changed edits

ty rainbowsunsetwaves on discord

Extend the fix to user-created eggs in Gen3 so that the OT name is converted.
2025-05-19 22:51:47 -05:00
Kurt
072dae8d14 Update InventoryItem9.cs
Closes #4496
2025-05-19 21:54:16 -05:00
Kurt
8c4abe0a50 Update window style flags in PokePreview
Not sure what caused the behavior change, but this ensures the window stops stealing focus, as well as being shown TopMost.
2025-05-19 11:26:10 -05:00
Kurt
0d5f7c758a Fix pla purchased flag regression
ty cerquami for checking
2025-05-18 22:53:17 -05:00
Kurt
533c870ca5 Revise stat abbreviations
Closes #4495

fix german's order (speed last)
2025-05-18 18:13:51 -05:00
Kurt
bf31d9119f Update 25.05.18 2025-05-18 02:37:40 -05:00
Kurt
47bc45d854 GetLevelLearnMove->TryGetLevelLearnMove
As alluded to in 7442e86d65
2025-05-18 01:36:05 -05:00
Kurt
5a3ebec12b Fix characteristic of all-0 IV mons when EC%6!=0
They'll always be HP.
ty Anubis for testing and SirToastyToes for reporting

Changing characteristic is a 1 in (5/6)*(32^6) chance, when no IVs are forced; aka ~1:900million
or just get a colo e-reader mon with 5/6 chance :)

Gen4/HOME(mobile): correct, unaffected
Gen5-Gen9/HOME(switch): bugged
2025-05-17 15:03:42 -05:00
Kurt
7442e86d65 level int -> byte
Might refactor the learnset level get to byte later as a TryGet so -1 is never returned.
2025-05-17 14:45:49 -05:00
Kurt
6b7938fea1 Add mousewheel event to edge Level/EXP
Scroll up Level once to increase level, scroll down EXP to be 1 exp from level up.

Apply the same mousewheel events to Friendship, IVs/EVs/AVs/GVs, with EVs being increments of 4.
I don't think it's worth overriding keypress arrow up and down to do the same.
2025-05-16 17:11:34 -05:00
Kurt
09f0462736 Refactor methods to use Try-pattern and improve code clarity
Refactored several methods across multiple files to use the Try-pattern for better null safety and clarity. Removed unused code, such as the SingleLevelRange record, and improved type handling in BatchEditing and BoxManipUtil. Adjusted constructors and properties for consistency in classes like CustomFolderPath. Minor updates to improve code readability and maintainability.
2025-05-16 16:12:48 -05:00
Kurt
83bc2bf653 Set egg form for gen5-9
Missed this in the PR
gen5 needs form for basculin
2025-05-14 01:35:11 -05:00
Kurt
fa9713a1ad Misc egg tweaks 2025-05-12 21:10:23 -05:00
Kurt
85f5950f28
Split EncounterEgg into derived classes (#4490)
Splits EncounterEgg into derived classes, allowing for fine-tuned control of each generation's egg generation & pattern matching.

Adds an interface to check if the encounter is a bred egg (useful for many scenarios when checking for move inheritance, in general).

Enhances the deferral rating for PIDIV matches in eggs based on global legality check settings.

Adds date/time indicators for Gen3/4 eggs and other Method 1 encounters.
2025-05-11 22:31:36 -05:00
Kurt
b2d70295e9 SV: fix deoxys TM flag check for altforms
ty thedominantspoon on discord
2025-05-11 19:24:07 -05:00
sora10pls
789a410272 Add latest distribution raid data 🪓 2025-05-08 20:02:42 -04:00
Kurt
dc0dbbe340 Update InventoryPouch.cs 2025-05-08 18:42:15 -05:00
Kurt
af8cb884e6 Update InventoryPouch9.cs 2025-05-08 17:31:45 -05:00
Kurt
d0a79acdb4 Update CommonEvent3Checker.cs 2025-05-08 11:22:38 -05:00
Kurt
275f5fb5df Misc tweaks
Seal some classes
Use derived pkm class for template->pk* moves (pk3/pa8)
Revise inventory9 to better handle empty slots
Cache legality for Summary report grid (does this make it faster?? seems to open instantly); add shift-quit to skip prompt
Fix handling for level range on encountercriteria passing level range for gen3/4 encounter slot method1/etc lead checks
2025-05-07 23:06:40 -05:00
hewenhan
61b38d1aa0
Fix: Disable CETCompat to prevent crash with deep InitialDirectory (#4481)
Setting CETCompat to false in the project file resolves ntdll.dll crashes observed on .NET 9 Preview when OpenFileDialog's InitialDirectory points to deep paths like MyDocuments. This allows the application to run without triggering CET violations in this specific scenario.

Temporary merge until .NET 10; CETCompat isn't necessarily required for this application so no harm disabling this feature.
2025-05-05 10:51:06 -05:00
Easy World
b656510939
Update zh-Hans localization (#4488)
* Update zh-Hans localization: improve Pokémon gender terms, AVs translation, and clarify the transfer handler legality message.

* Clarify zh-Hans translation for LTransferHTFlagRequired
2025-05-05 09:24:23 -05:00
santacrab2
d6c484295a
gen 4 nature (#4487) 2025-05-04 21:52:12 -05:00
Lusamine
e02564cc08 Remove Sandstorm from weathers for IoA Wailord
It isn't possible to spawn Wailord from any area with Sandstorm and
reach it without despawning it. All of them are too far or require a
path that is out of its spawn radius.
2025-05-04 15:42:47 -05:00
Kurt
a85f919630 ShowdownSet: handling for context-locked forms
Retconned forms like Totems and Cosplay Pikachu that only exist in a specific context now get recognized again
Current-Context has shifted from Gen6->Gen7->Gen8+, so a "get form list for species" won't return these forms.

Not a perfect implementation, but better for now.
2025-05-04 13:19:40 -05:00
Muhammad Kassar
a77e60d8d8
improve fr translation (#4486)
* improve fr translation

* trim down truncated stat names
2025-05-04 13:16:07 -05:00
Kurt
0e0d812d83 Add more xmldoc 2025-05-03 23:55:06 -05:00
Kurt
ae3bb75fe6 ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual 2025-05-03 23:54:11 -05:00
Kurt
ed67be3498 Keep Summary Previewer form above all other forms
Sometimes the z-order can get messed up when the PC/process is put to sleep by the operating system (or something else happens?) -- either way, just force it to the top since it should always be on top when shown.
2025-05-03 23:53:11 -05:00
Kurt
a7420cb3de Lift stat amp reinterpret to nature adjust
Allows retaining a stat parse amplification tracking result in visual order, unless requested in stored order. Results in slightly less logic being done on un-kept parses/IV parses.
Adds xmldoc
Extract Ability/Nature parse lines to add parse fail indicators on conflicting values.

```
Piplup
[Torrent] @ Oran Berry
Ability: Defiant
```

Will now flag the conflicting ability
2025-05-03 23:52:08 -05:00
Kurt
2514e66331 Fix stat leading zero 2025-05-02 11:04:06 -05:00
Kurt
77f1b637c9 Misc tweaks 2025-05-02 01:34:49 -05:00
Kurt
f730f7d19a
Feature: Localization of Battle Templates (Showdown Set) (#4482)
* Localization capability for each language, import & export
* Lines with stat names (IVs/EVs) can be configured to many representations (X/X/X/X/X/X, HABCDS, etc).
* Add nonstandard localizations
* Add token types for Showdown's new set format
* Add new program settings for hover & export styles. Allows users to select which presentation format they want for the hover previews, as well as the set export format.
* Revises preview hover GUI to use new settings
* Revises export events to use new settings
* Moves no longer indicate end of set
* Enhance robustness of stat parsing
* Expand all settings in settings editor on form load
* Extract clipboard -> sets operation to api for maintainability & reusability
2025-05-01 23:16:36 -05:00
abcboy101
63516fc718
Update badwords (#4483) 2025-04-30 17:04:59 -05:00
sora10pls
d4b2b2c75f Add latest distribution outbreak data 🟡 2025-04-29 20:06:04 -04:00
Gengar
8adb7e2dc9
Add Korean Ditto Serial Code Distribution Event (#4480) 2025-04-24 20:57:11 -05:00
Kurt
a6e2e08ecb Add pokepast.es url import
why not
2025-04-21 01:10:57 -05:00
Kurt
87d2f10c7f Misc tweaks
Add date validation for lgpe go park encounters (deferred, now finally remembered to implement?)
2025-04-21 00:57:23 -05:00
Kurt
154c370901 Merge branch 'master' of https://github.com/kwsch/PKHeX 2025-04-20 11:24:04 -05:00
Kurt
d6a18ddc10 Batch editing: accurately track untouched results
still feels goofy
2025-04-20 11:23:59 -05:00
abcboy101
d633202af3
Add accessors for PBR save language (#4475)
* Allow PBR saves with at least one valid half

* Add accessors for PBR save language
2025-04-20 11:17:16 -05:00
Kurt
972c432205 Minor tweaks
no functional change, just updating some style for readability
2025-04-20 11:08:10 -05:00
Kurt
b8eb980241 Enhance folder path tracking
Internal metadata, not really useful but maybe in the future.
2025-04-20 11:06:54 -05:00
Kurt
383037db43 More xmldoc updates 2025-04-20 11:06:08 -05:00
Kurt
15a7f622fc Update EncounterServerDate.cs
Co-Authored-By: 9Bitdo <157295620+9Bitdo@users.noreply.github.com>
2025-04-19 23:11:46 -05:00
Kurt
8c20444368 Lowercase bag sprite icons
Co-Authored-By: Nova <nova@zeusteam.dev>
2025-04-19 14:34:09 -05:00
Kurt
87eb8529ff Refactor Showdown team parsing methods
Renamed methods in ShowdownTeam.cs for clarity
Simplified IV validation logic in ShowdownSet.cs using AsSpan().ContainsAnyExcept, remove unnecessary xmldoc inherit
Updated references in Main.cs to reflect these changes.
2025-04-18 20:56:39 -05:00
Kurt
45d95d5742 Add showdown team import from url 2025-04-17 20:27:24 -05:00
Kurt
2d48c56200 Add seed parameter to gen3/4 GetRandom*
Rather than call Util.Rand32() inside the method, allow passing in an arbitrary seed.
2025-04-17 19:53:27 -05:00
sora10pls
73abf1a0ec Add latest distribution raid data 🎈 2025-04-17 20:02:22 -04:00
Kurt
d699b6db3e Update MethodPokeSpot.cs
Closes #4470

Co-Authored-By: santacrab2 <79347566+santacrab2@users.noreply.github.com>
2025-04-16 17:09:05 -05:00
Ka-n00b
56e2ec427e
Update Gen IV Event Constants (#4471) 2025-04-16 17:06:44 -05:00
Kurt
3a4cd9a0cd Update EncounterSlot3XD.cs
Closes #4468

Co-Authored-By: santacrab2 <79347566+santacrab2@users.noreply.github.com>
2025-04-15 23:10:15 -05:00
Kurt
ef2152cf3b Misc tweaks for cxd encounters/viewing
convert, adapt to save file on view (fixes viewing gen3 ot/nick'd encounters in cxd)
display original string in cxd format (useful for jpn->eng->ENG colo, for string matching? might need to revert)
hard-match version for colo gift (MATTLE) to not confuse 10ANIV
2025-04-15 02:50:25 -05:00
Kurt
ba4d054089 Allow Heal box on all formats
pp, hp, whatever
revise sorting behavior for custom descending sorts; have Favorite mark sort as marked first.
Add time of day sort for Gen2
2025-04-13 16:56:18 -05:00
Kurt
207135948a Remove some PP set fluff methods
just a wrapper to do the same thing... nah
2025-04-13 16:54:20 -05:00
Kurt
4da88ac063 Minor clean 2025-04-13 12:02:33 -05:00
Kurt
76f2705c9c Misc tweaks for enc->pk ctor language/version
lang->language
only pass version if relevant
2025-04-13 11:58:33 -05:00
Kurt
efa627f703 Make SimpleTrainerInfo immutable after init 2025-04-13 11:55:42 -05:00
Kurt
8a19968321 Minor clean 2025-04-13 11:55:20 -05:00
Kurt
1914c482c6 Add readonly interface for 3DS georegion 2025-04-13 11:52:51 -05:00
Kurt
be874dcc50 Extract TM lists to PersonalInfo
Fix gen3 allocating eggmoves 3x (instance) instead of just once (static)
2025-04-13 11:49:45 -05:00
Kurt
fbfa28a0cf Sunset PKX -> Latest
Relocate gender ratio info to EntityGender
farewell PKX; once a behemoth catch-all of >4000 lines, now no more. 😢
2025-04-13 11:44:28 -05:00
Kurt
57060cbea5 Rename PP arrays for consistency 2025-04-13 11:43:00 -05:00
Kurt
9218f97971 Rename item pouch legal lists, make public
No longer need to protect for immutability since they're now ReadOnlySpan.
2025-04-13 11:42:59 -05:00
Kurt
92cd8feaf1 Use entitycontext for xmldoc instead of lump 2025-04-13 11:34:17 -05:00
Mow
2f1a11faa7
Implement Gen 4 groups and group-derived values (#4467)
* Fix swarm and safari seed locations

* Implement Gen 4 group system

* Implement Gen 4 BattleTowerSeed

* Gen 4 Lotto number

* Creator -> Leader in line with in-game terminology

* Update Group4.cs
2025-04-10 20:43:23 -05:00
rganhoto
7219e39b0f
Pokedex - fix cannot unsee forms in Gen4 (#4466) 2025-04-10 13:11:44 -05:00
Carbonara
f020b3eb52
Update the French translation (#4465)
- Rework a bit the French translation on some lines; Translate some new lines; Fix some formatting (;, :, ! and ? are supposed to have a space in French before, though it is not done in Pokémon games prior to Gen 6 for space reasons); Fix some terms being incorrect or not being those expected (e.g. Pokémon Outil de lien refers to Poké Lien, super entrainement refers to SPV
- Translate all of the Funfest Missions in French. Official names and formatting are used. Changed B and W to N2 and B2 to be a bit more consistent for the initial of the game.
- Translate flags and constants for GSC in French, using official terms. Left the GSC decorations untranslated due to not wanting to figure for now which decoration is which, same thing than what is done in English.
2025-04-09 19:49:08 -05:00
Kurt
5ab6dbc0ac Add cancellation to savefile detection calls
5s timeout on detection, roughly
2025-04-06 22:25:37 -05:00
Lusamine
56ab067ad9 Standardize 'usable' in flags, correct spelling 2025-04-04 21:41:59 -05:00
Kurt
8700e11eb2 Fix flag get
flag1 would indicate true if flag2
2025-04-04 01:02:48 -05:00
Kurt
87b77f8a9d gen6: submission PP
Closes #4462
2025-03-30 22:39:19 -05:00
Kurt
a0be3f5f5f xy: Fix radar record read/write 2025-03-30 00:04:17 -05:00
sora10pls
7eda8ada4d Add latest distribution raid data 👻 2025-03-27 20:02:45 -04:00
Kurt
94b47d61da Add smeargle verifier branch
Add gen2 nosketch moves while we're here, and Stadium2's extra quirk one.
2025-03-27 18:43:37 -05:00
Kurt
542d6541c3 Extract stadium reminder moveset verifier step
Now flags as Stadium2 as source
2025-03-27 14:27:17 -05:00
Kurt
49061f0047 Add placeholder stadium2 pkl&api
private static readonly LearnsetStadium[] Stadium = LearnsetStadium.GetArray(BinLinkerAccessor.Get(Util.GetBinaryResource("reminder_stad2.pkl"), "s2"u8));

https://bluemoonfalls.com/pages/general/move-reminder
ty for the easier-to-digest parse; reinterpreted further into pkl
2025-03-27 00:53:12 -05:00
Kurt
83aff8b889 Add murkrow rebattle empty set
ty Unknown Warrior on discord for reporting

enhance heracross' note
2025-03-27 00:15:49 -05:00
Carbonara
7b8d51df37
Update lang_fr.txt (#4461) 2025-03-26 12:48:21 -05:00
Lusamine
d170908a3d Standardize apostrophes in Encounters8 comments
Use the straight apostrophe which is more easily typed, which allows for searching by location name.
2025-03-25 11:38:17 -05:00
Ka-n00b
1098481c85
Update B2W2 Event Flags (#4460)
* Update flags_b2w2_en.txt

* Update flags_b2w2_es.txt

* Update flags_b2w2_ja.txt

* Update flags_b2w2_ko.txt

* Update flags_b2w2_zh-Hans.txt

* Update flags_b2w2_zh-Hant.txt
2025-03-25 07:43:22 -05:00
Kurt
b303ea90ef AbilityVerifier: gen5 ability patch for genies
and giratina
extract to a single location
abilityverifier is still needing a rewrite to clean up all the branched logic, but it's still "functional"
2025-03-24 10:29:22 -05:00
Kurt
ca03311d21 PA8: Allow un-updated mid-scale alpha floats
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/31/#findComment-294285

While we're here, fix PB7->PKH to retain the ReceivedTime values. Not that it matters, because those PKH values go nowhere.
2025-03-23 13:25:54 -05:00
Kurt
5376d44de8 Add $suggest for friendship properties 2025-03-23 12:16:52 -05:00
Kurt
4c15e7fd88 Gen4: permit 0 pid for bred eggs
masuda method rolls forward the egg seed, therefore 0 is reachable
2025-03-17 22:03:04 -05:00
Kurt
9b82cdfc80 Misc tweaks
CXD: indicate shadow index on hover, pokespot area
CXD: flag eggs
CXD: enc->pk try to match shininess when requesting all IVs
make some signatures dealing with level use as byte
2025-03-15 23:15:55 -05:00
Kurt
7f33741abb PP verifier: relax check based on typeof(PKM) 2025-03-13 21:53:27 -05:00
Kurt
ee3665cbe8 Gen2: Fix spanish Aerodactyl Trade nickname
NORMA is the OT, not the nickname
2025-03-13 21:50:21 -05:00
sora10pls
cb6d4ef70f Add latest distribution raid data 💧🦆🕺 2025-03-13 20:03:26 -04:00
Kurt
32887b5659 Revise LockFinder to accept u32, not pidiv 2025-03-12 21:24:38 -05:00
Kurt
e609081892 cxd: fix enc->pk3 with criteria IVs set
not sure why this unroll was in there... now works as intended.
2025-03-12 00:57:11 -05:00
Kurt
ca81fcd250 CXD: check naming screen->TID/SID frame pattern
Adapted from e5255b1313/PokemonCoRNGLibrary/Util/LCGExtensions.cs (L15)
2025-03-10 22:25:53 -05:00
Kurt
f4cfc39173 Gen9+: Show gold Battle Memory ribbon if 7+
https://bulbapedia.bulbagarden.net/wiki/List_of_Ribbons_in_the_games#Battle_Memory_Ribbons
> As of Scarlet and Violet, Pokémon that obtained seven out of the eight possible Ribbons will also display this version, owing to the fact that the World Ability Ribbon hadn't been officially obtainable for 8 years as of the game's release
2025-03-10 00:32:15 -05:00
Kurt
c77ea8fd4c PokeSpot: add IV animation pattern check
Realign slot match check to what was documented: https://www.smogon.com/forums/threads/past-gen-rng-research.61090/post-4755981
Most RNG tools assume that both are available to spawn, as that would be an annoying setup.

IV animation pattern: 075f1340c6/PokemonXDRNGLibrary/Generators/PokeSpotGenerator.cs (L48-L56)
2025-03-10 00:11:29 -05:00
Kurt
5bf9865f3f Add pokespot reverse/forward methods
Also adds partial IV specifying for cxd generating
2025-03-09 20:09:17 -05:00
Kurt
2fbe2feb52 Fix gen4 pcd sentinel set
Closes #4407
Bug: 0th PCD slot not being provided would set PGT slot0 to "inactive", rather than setting pcd slot0.
The modeled GUI is still very clunky (editing DP just ignores the flag, and assumes if data exists it is active).

fix a typo while we're on the topic of Gen4 :) -- ty 0blivion0athkeeper on discord
2025-03-08 11:13:35 -06:00
sora10pls
fed4a6e900 Add latest raid distribution data 🔥🐊🎤 2025-03-06 19:02:10 -05:00
Kurt
0de0bd4c0b Add setting to allow colliding gen3/4 egg pidivs
For those who think it is funny to RNG abuse egg PID/IVs to match a recognizable algorithm type. Opt-in setting because 99.9% of the time it's going to be a hack.
#4347
#3894
#3092
2025-03-06 09:37:26 -06:00
Kurt
4d702d261d Add ivs->channel seed recovery
Still lack IVs/nature/shiny request for the BACD/M2 variants, maybe in the future those can be added.
2025-03-05 22:45:41 -06:00
Kurt
8b5c07b07d Update Main.cs 2025-03-05 21:31:47 -06:00
Kurt
a74d882077 Loosen SV 2.0.0/3.0.0 size checks to ranges
Too many permutations of optional blocks to want to think about.
Since we already rely on hash validity, a range check should be more than sufficient to eager-check and prevent size clashes.
2025-03-05 00:13:11 -06:00
Kurt
3f305c9135 Handler check: bypass for blank saves
Revise "past gen" message since it's not past-gen only (now applies to cross-context like SV->BDSP)
2025-03-03 00:27:26 -06:00
Kurt
1ad7a6b5fb Autosize splash screen on startup
Higher scale was truncating, let it size up
2025-03-02 22:46:13 -06:00
Kurt
a084291532 Gen3 rse swarm: fix gen on rs, match
don't complain on random discords, actually report issues k thx
2025-03-02 22:45:51 -06:00
Kurt
356db93e9f Settings: version select typeable combobox
Was annoying cycling through all these versions that start with the same character. Works well enough to just do it

bv5: ignore checksum integrity, the checksum is over the decrypted data (not while encrypted).
2025-03-02 01:11:29 -06:00
Kurt
4adf450b08 Add gen4 mg timer indicator 2025-03-02 01:08:04 -06:00
Kurt
c47e595cd3 Enhance Stadium1 box read/initialization
Detect the current box buffer, initialize boxes if not already, and hide (delete) slots that aren't present (ignore ghost slots).
Compress storage before export.
Closes #4454
2025-03-01 20:07:26 -06:00
Kurt
78ccc92dea Update SecretBaseManager3.cs
Closes #4456
2025-02-28 00:10:39 -06:00
sora10pls
bf7441602e Add latest distribution raid/outbreak data 🐈🔴🟢🔵 2025-02-27 19:04:32 -05:00
abcboy101
6668350b68
Add Mainland China Melmetal gift (#4455) 2025-02-27 00:49:57 -06:00
Kurt
4bdd673fd0 Add label for LGPE arrived datetime
Allow editing of memories while an egg (SW/SH traded eggs with memories...)
2025-02-25 20:38:13 -06:00
Kurt
dfda71dddd Rename default label names 2025-02-25 20:37:34 -06:00
Kurt
dbff2695d7 More main editor appearance pixel tweaks 2025-02-24 22:38:21 -06:00
Kurt
6c8f312d17 Update PKMEditor.cs 2025-02-24 21:39:34 -06:00
Kurt
8626619b60 move combobox ordering oops 2025-02-24 21:31:16 -06:00
Kurt
7acc86ca61 Add API method for finding a legal shiny SID
if returns false, try a different TID
2025-02-24 18:17:45 -06:00
Kurt
7bfbc948c3 Enhance main form scale sizing 2025-02-24 18:17:27 -06:00
Kurt
9fb11b6041 Rework Cosmetic pane buttons into panel 2025-02-24 08:30:42 -06:00
Easy World
cb18310cee
Update zh-Hans localization strings for date validation and export legality (#4451) 2025-02-24 00:02:44 -06:00
Kurt
53d47e5c64 Update localizations 2025-02-23 23:34:11 -06:00
Kurt
87fb241a98 Update 25.02.23
fix bvid5 checksum check
Gen7b: add ReceivedDate/Time editor to OT/Misc tab, enable Ribbon button
2025-02-23 23:16:43 -06:00
Kurt
c8ec63992b Rework entity import settings param passing
Closes #4418

Introduce a readonly record struct to contain all the settings selected. Add simple static get for none/all to disable/force the updates.

Split the UpdateRecord behavior out of UpdatePKM (adapt to save file) so that it behaves similarly to UpdateDex.
Only AdaptToSaveFile when opening a file in the PKM Editor.
2025-02-23 15:50:17 -06:00
sora10pls
8f86e3fec0 GO: Handle Road to Unova Timed Research encounters
First time Level 20 Mythicals have been encounterable in Research, need to account for the minimum IV difference should any more occur in the future.
2025-02-23 15:49:29 -05:00
Kurt
65df18ae66 Extract entity/template game presence filter 2025-02-23 11:25:47 -06:00
Kurt
acfdd9bab3 misc tweaks
reduce allocation lol
2025-02-23 11:19:50 -06:00
Kurt
7279af79b2 Extract pk5->pk6 PID mutation logic 2025-02-23 11:19:33 -06:00
Kurt
a1ea915b52 Extract CuteCharm4 api 2025-02-23 11:19:03 -06:00
Kurt
8ac937b83c Fix Coronet Feebas SlotNumber tags
#4450
0deb19d1e8
2025-02-23 11:18:04 -06:00
Kurt
8cb14379a6 Resize Gen3 RTC editor buttons
Allows for long-winded languages like German to better label button text :)

Closes #4449
2025-02-21 20:07:48 -06:00
Kurt
5716ec25e3 Misc tweaks
Add some ease of use interaction to Trainer Database objects
Throw exception on bad picturebox early, instead of writing the file and aborting
2025-02-21 20:07:42 -06:00
Manu
e93425e137
Handle HOME Keldeo & Meltan; Revise WB7 (#4446)
* WC8: Handle HOME Keldeo
* WB7: Handle HOME Meltan fixed ability, scalars, AND HeightAbsolute and WeightAbsolute
* WB7: Fix PID get/set offset
* PB7: add ribbon get/set (Meltan has Souvenir ribbon and is retained on transfer to LGP/E)
* Legality: Verify Size Absolute for WB7 Home gifts, only check absolute scale when PB7
2025-02-21 20:00:28 -06:00
9Bitdo
96d779f9b5
Add Marco's Jumpluff date (#4448) 2025-02-21 19:43:51 -06:00
Kurt
3f3d43dca5 Gen1: Add Japanese tour mews 2025-02-19 19:19:39 -06:00
ShadowMario3
757f84db9e
Update EncounterGift1.cs (#4447)
Add more OTs for Gen 1 GB Mews.
2025-02-18 13:07:32 -06:00
sora10pls
f069b950ff Add latest distribution raid data 💘🍬 2025-02-13 19:04:23 -05:00
Taylor Rodríguez
b2f962f442
Fix LGPE local time validation (#4445)
The function `IsTimeValid` checks if the received hour, minute, and
second of the Pokémon is below 24, 60, and 60 respectively. For some
reason the current code includes a "+1" for each check, which results in
some legitimate Pokémon being marked as having an invalid timestamp.

Fixed edge cases:
- Received on the 23rd hour.
- Received on the 59th minute.
- Received on the 59th second.
2025-02-13 07:46:49 +00:00
Kurt
806548a456 Update MiscVerifier.cs 2025-02-13 05:08:30 +00:00
Kurt
93179c0281 Add LGPE received datetime r/w/lc 2025-02-13 04:53:37 +00:00
sora10pls
1fd1c0f67c Add preliminary handling for Keldeo & Meltan events
Shiny Meltan marks the first (and likely only) time where a LGPE event is server date restricted, so we need to extend those checks to include WB7s.

Also revise Manaphy & Enamorus start dates due to server date assignment logic changing at some point. In the past, it was always assigned according to UTC, but it is now determined by local server time, making January 27 possible in early time zones (Mountain, Pacific, etc.)
2025-02-12 14:58:08 -05:00
9Bitdo
03627b641e
Add Pokémon Day 2025 Flying Tera Type Eevee's date (#4440)
* Add Pokémon Day 2025 Flying Tera Type Eevee's date

* Refactor: add date bias for generate

---------

Co-authored-by: Kurt <kwsch@users.noreply.github.com>
2025-02-07 22:16:39 -06:00
BlackShark
c784edbe1b
Added setting to export with box and slot index (#4443) 2025-02-07 00:21:10 -06:00
Professor Dirty
4a706bcae7
Addition and correction of CHS translation (#4439) 2025-02-07 00:20:39 -06:00
sora10pls
7c25229236 Add latest distribution outbreak data 🐦🐦🐦 2025-02-06 19:06:15 -05:00
Ka-n00b
a1914105bf
Update Event Flags/Constants (#4437)
Adds some new and fixes grammar and formatting of existing ones. Additionally, renamed 2 Sword/Shield block names.
2025-02-03 23:38:13 -06:00
Manu
cb7cfef5fa
Fix CHT OriginalTrainerName setter for crafted WCs (#4438) 2025-02-03 22:18:56 -06:00
Manu
8d5866f118
Handle Arbok Mainland China wondercard (#4436)
* Add Arbok wondercard to Mainland China Gifts

* Always pick CHS OT/nickname for Mainland China gifts
2025-02-03 22:17:58 -06:00
Kurt
f538d2c4be Skip random level check if criteria not in range 2025-02-02 23:18:15 -06:00
Kurt
9dedd33694 Duplicate locations: across lists, not each array
BD/SP has Pokémon HOME as location 30018 and 40086; need to differentiate.
Revise the unit test to share the unique-check set across all location lists of that context

Ignore "empty" location values (dashes, empty). For deduplicating, ignore empty indexes as well.
2025-02-01 19:23:11 -06:00
Kurt
e04ad2c6b2 Misc tweaks
Fixes shiny rayquaza
some lowering int->byte
Fix event3 random restricted seed fetch for gender
Fix gen8a encdb filters excluding static encounters, now will be properly excluded
2025-02-01 10:42:26 -06:00
Professor Dirty
f36963d6e6
CHS: Update gen1-3 translations of some items/flags (#4430) 2025-02-01 09:41:02 -06:00
Manu
c456e8e212
Handle generation and legality for HOME Manaphy and Enamorus (#4432)
* Home Enamorus actually uses Cherish ball

* Handle Enamorus fixed scalars

* Handle Manaphy fixed scalars
2025-01-31 22:30:35 -06:00
Kurt
8e5de20881
Add Form to EncounterCriteria, apply from seed interface (#4433)
When generating from templates with randomly correlated forms, the EncounterCriteria will now seek for a match for that feature if specified. Granted this is just a cosmetic seek for Generation 4 Unown -- Gen3 Unown is locked to a specific form for each encounter slot.

Also adds an interface to templates that expose a shortcut to SetPINGA's randomness, for those who know what seed they want to generate from. This is currently only implemented into encounters from Gen8 (Raids/8b Roamers), Gen9 (Tera Raids), and Gen3 events (Channel, as others can be found via IVs quickly -- Channel's reversal from IVs isn't performant).
2025-01-31 21:08:27 -06:00
Kurt
538374b33f FRLG: fix gift magikarp location check
The Route 4 (Pokémon Center) location (099) is only used for the Town Map tags; the gift yields Route 4 (104) only.

https://projectpokemon.org/home/forums/topic/66281-frlg-magikarp-salesman-oddity/
2025-01-29 23:10:26 -06:00
Kurt
46bb7e3d3b Add trainer ID checks for cxd/rs
Templates: Add more interfaces for trainer ID properties
Closes #4431
2025-01-29 22:34:45 -06:00
sora10pls
f3f89c1f4b Update BDSP Met4 to match SWSH/LA 2025-01-29 20:33:13 -05:00
Kurt
4fa83b905f Better clamp corrupt sav2 mail boxsize reads 2025-01-29 02:02:43 -06:00
sora10pls
4089b8197a Add date ranges for Shiny Manaphy/Enamorus events 2025-01-28 08:39:00 -05:00
Kurt
7d59e3416a Minor tweaks
== null
to
is null
2025-01-27 16:37:37 -06:00
Kurt
2592dfec19 Revise RSBox slot display order
Revises prepended box name to better indicate left/right
Closes #4429
2025-01-27 11:59:36 -06:00
Professor Dirty
f63ad4372e
Additional event flags CHS translation (#4427) 2025-01-26 11:28:50 -06:00
abcboy101
6c3eec3314
Support Gen5/3DS/Switch word filters (#4423)
* Support Gen5/3DS/Switch word filters

- Separate 3DS/Switch word filters
- Add/implement Gen5 word filter
- Adjust behavior of DisableWordFilterPastGen

* Implement halfwidth/fullwidth conversion

Only applies to alphanumeric and kana
2025-01-26 10:38:21 -06:00
Professor Dirty
b119302d67
Additional CHS translation (#4421) 2025-01-23 23:43:51 -06:00
9Bitdo
53195d0233
Add Pokémon Lucario & The Mystery of Mew Movie Gift KOR 아론's Lucario Date (#4422)
* Add Pokémon Lucario & The Mystery of Mew Movie Gift KOR 아론's Lucario Date
2025-01-23 23:43:13 -06:00
sora10pls
659e2ad099 Add latest distribution raid data 😡🎤🐧🤖 2025-01-23 19:03:37 -05:00
Kurt
0316404f46 Fix ageto antishiny recognition
Must have been omitted in the refactor

while we're here, revise the other gift3's to be records for the autogen'd ToString
2025-01-21 22:40:45 -06:00
Kurt
82d74e6950 Add TIDseed for emerald frame indicator 2025-01-18 22:40:31 -06:00
Kurt
5fbf867613 Show seed-frame-time for rsefrlg slot/static encs
Easy indication for painting-skips or changed-seeds.
2025-01-18 19:50:02 -06:00
sora10pls
c7881b5c4a GO: Permit Master Ball for Max Battles
When Max Battles first launched, the Master Ball was not available in the Poké Ball tray in the bonus challenge. This did not apply to the tutorial Wooloo or Skwovet/Wooloo from Special Research.

0.333.0, which released on September 27 2024, added a property named `canUseMasterBallPostBattle` for Max Battles. Players noted being able to use a Master Ball on Gigantamax Charizard when it debuted in the game, meaning this is the likely culprit.

Since all past Max Battles have other, less restrictive encounter methods where the Master Ball is permitted, this does not cause any potential invalid legality overlaps. Gigantamax Pokémon also can't be transferred, so they don't need to be accounted for.
2025-01-18 12:59:32 -05:00
santacrab2
74a52d946b
Encounter to PA8 generation tweak (#4419)
* use pa8's current level for determining Mastered flag for Move Shop Records to ensure all flags are checked on encounters who learn moves close to their level mins

* use met level instead of current level to reduce unnecessary calculations
2025-01-17 22:08:54 -06:00
Kurt
c1d2babc5c Set all purchased on MoveShop all 2025-01-17 22:08:38 -06:00
Kurt
2976480792 Minor tweaks
File.WriteAllBytes now accepts span on .NET 9 yay
2025-01-16 22:16:40 -06:00
Kurt
b9df2d65c6 Improve performance of adding sprite accents
Can get the native memory span via MemoryMarshal.CreateSpan; this skips allocating a 15KB buffer as well as copying 30KB for each mutation

am paranoid about mutating the original image, so that can stay for now
2025-01-16 21:34:53 -06:00
Kurt
0549cfda4a Rework SCBlock to Memory<byte> 2025-01-16 20:55:07 -06:00
sora10pls
2442165907 Add latest distribution raid/outbreak data 🌴🦩 2025-01-16 19:05:01 -05:00
Kurt
eced651024 Pokewalker: Handle 539 stroll, revise signatures
Was missing the worst case scenario; ran a bruteforce script to find a 539 after adding the final logic. We try to do the least amount of cpu instructions so this is probably the better arrangement anyway.

Revise the method signatures to exclude the scratchspace span reuse (not-obvious behavior). Just stackalloc a new span.

Add seed indication to legality formatting. #4416
2025-01-15 01:26:49 -06:00
Kurt
e9092ca702 Update MetaFilter.cs 2025-01-14 21:50:04 -06:00
Kurt
5230cba9f6 Misc tweak for alpha mark check 2025-01-13 23:47:57 -06:00
Kurt
9f0812cb8b Pokewalker: Don't check slots for stroll seeds
Closes #4416
Majority of the changelog here is additional/revised comments/xmldoc or "unused code". Only `GetFirstSeed`'s behavior has changed on line 154 (new). Since there is no longer the need to refer to Species and Course, remove from the method signatures & usages.

Refer to the discussion in the ^ mentioned issue.

Co-Authored-By: HappyLappy1 <86489014+happylappy1@users.noreply.github.com>
Co-Authored-By: NickPlayeZ <80699972+nickplayez@users.noreply.github.com>
2025-01-13 23:24:18 -06:00
Kurt
ef60ee622d Fix colo espeon pidiv double-check
forgot about the fakeID before IVs
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/30/#findComment-293040
ty jacksonf !
2025-01-13 22:25:32 -06:00
Kurt
7d2c564744 Improve comment for lang restricted walker courses
Refer to Comment for context: https://github.com/kwsch/PKHeX/issues/4416#issuecomment-2588858294
2025-01-13 22:04:34 -06:00
Kurt
943223fe09 Handle snowy crossover deferrals better 2025-01-12 22:14:43 -06:00
Lusamine
09fce189d3 Filter uncatchable SV fixed encounters by AI action 2025-01-12 19:49:44 -06:00
Kurt
e99105cd78 Update hgss safari slot permutations
https://github.com/kwsch/PKHeX/issues/4416

for permutation code change, see dumper repo:
cdfafa8a31
2025-01-12 17:23:36 -06:00
Kurt
67156dd0df Misc tweaks
Fix gender filtering for gender-locked tera raids
Fix gender filtering for gen3 pcny
Make StringInstruction a record, expose Comparer in ctor
2025-01-12 11:22:30 -06:00
Kurt
b79551411b Fix batchmod compare for $rand 2025-01-11 23:20:14 -06:00
Kurt
26e06f80fb Revise minior handling
Fixed symbol isn't actually form random (previous commit reverted)
2025-01-11 17:51:04 -06:00
Kurt
9f60ff9eb7 Add settings to bypass hotkey requirement
Legality context menu requires holding control when opening the menu -- with this option enabled, don't need to do the hotkey.
2025-01-11 17:36:03 -06:00
Kurt
c17774f57c Revert "Handle fixed tera Minior from su2"
This reverts commit 20245dd512.
2025-01-11 17:35:53 -06:00
Kurt
20245dd512 Handle fixed tera Minior from su2
Thanks XD_Lele for bringing this up -- form-random handling wasn't added for this encounter type
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/page/30/#findComment-293014
2025-01-10 22:08:39 -06:00
Kurt
20887a1b2d Gen4: allow battle use items to be held (oops)
Closes #4415
2025-01-10 12:27:37 -06:00
sora10pls
c44f6d286c Add latest distribution outbreak data 🐍 2025-01-09 19:55:13 -05:00
Professor Dirty
866f0269af
Append CHS translation (#4413) 2025-01-08 16:51:54 -06:00
Kurt
172ba0d119 Update ITrainerInfo.cs
Closes #4414
2025-01-08 16:48:44 -06:00
Kurt
46c808d118 Enhance fail-retry PID generation for raids
try with criteria -> try with critera-w/o-IVs, try without any criteria
2025-01-08 01:02:00 -06:00
Kurt
93c7b765c3 Minor tweaks
Handle ARNG egg manaphy for gen4 trainer, force hatch
add xmldoc/comments
expand expressions for version/language selection, disallow languages correctly
2025-01-08 00:36:02 -06:00
Kurt
c60efc7d14 Revise Manaphy PIDIV checks for antishiny+egg
Handles all permutations correctly
set correct version value for blank DP save (not valid saved ver, need to fall back to D)
2025-01-07 23:45:56 -06:00
Kurt
e1773e83b9 Allow B2W2 Funfest Stunky/Glameow from both games
https: //github.com/kwsch/PKHeX.EncounterSlotDumper/pull/2
Co-Authored-By: Parnassius <11491745+parnassius@users.noreply.github.com>
2025-01-06 21:36:15 -06:00
Kurt
ab215d0721
Enhance EncounterCriteria and template->pkm adherance (#4411)
Change the EncounterCriteria object to a readonly record struct, and introduce extra properties to indicate how flexible we are with random results.
2025-01-04 22:45:30 -06:00
Kurt
37e4b06a9e Misc tweaks
Make nick/trainer'd encounters use RoM instead of string[] to truly make them readonly records, and skip 1 dereference on access
add some xmldoc
fix open file suggesting main from another folder
2025-01-04 21:43:55 -06:00
Kurt
c1f0c9e7ae Update dependencies
for .NET 9.0 there's a few
2025-01-04 01:25:14 -06:00
Kurt
b94ab9acb4 Minor clean
No functional change
2025-01-04 01:24:29 -06:00
Kurt
520c849287 Fix Gen1 party+clear
Closes #4410

Also fix trash bytes persistence (only overwrite nickname with default if it visually differs from what is present in the textbox).
Add trash edits to Gen3 HoF
2025-01-03 21:33:22 -06:00
Kurt
674a778bf9 Fix form centering to non-primary screen
The first screen is configured with origin coordinates 0,0; any other screens that are arranged to the left or below will have coordinates in the negative. Negative coordinates are valid; don't sanitize.
2025-01-03 13:35:54 -06:00
Kurt
a1a15fe5b2 Fix opening Gen3 HoF editor
Closes #4409
(icon reference needed to point to the static resource)

Add SAV3 property to check for misconfigured (small) sizes, to disallow editing extdata blocks that don't exist (no exception thrown for the small saves, can't open the GUI)
Rearrange GUI, handle exception when copypasting bad PID and saving, add sprite, add Clear button instead of auto-clearing on species:0, add shiny checkbox (readonly)
2025-01-03 13:32:24 -06:00
Kurt
fdcd71a3b6 Minor clean
Enhance the API for SV's item pouch/items, if anyone bothers to use it
2025-01-02 20:59:37 -06:00
Kurt
d7ecbfe667 Move PLA verifier into MiscVerifier
Only checks PLA stats in PA8 format; no need to type check this in the main method. Might be a good idea to extract format-specific verifiers into classes.

Extract size checks to top level method. A wasted type check isn't too bad compared to all the duplicate logic.
2025-01-02 20:33:15 -06:00
Kurt
9ef46b5f8c Correctly filter dummied moves in BDSP and SV
Less noise in the user drop-downs for moves that aren't available in the game. Unlike SW/SH that has all moves from the past, there's no need to confuse the user with impossible options (unless they turn on HaX, which is unchanged).

Add performant path for dummied move list fetch for Gen8a+
Skip duplicate work for Relearn list fetch (always same as Moves, except for Gen7 relearn)
2025-01-02 20:29:58 -06:00
Kurt
abc1e785d8 Remove fullness/enjoyment from PKM, use interface
No need to carry this baggage further since SV ditched it. PLA and BDSP only have it because there's a gap in the structure that *we assume* is unused (rather than reserved), and is worth checking for legality (unused bytes shouldn't be used).
2025-01-02 20:19:29 -06:00
Kurt
30fdfb29e7 Add context to TrashByte editor
rather than just Generation, context is also important to help select glyphs for different games (Gen7b differing from Gen7?)
2025-01-02 20:17:17 -06:00
Kurt
cb967ef283 Minor adjustments
Inline some gameversion calls, replace `is IAlpha` with immutable interface instead
Add interface to get the mastery object for a given a learnset source & species-form (rather than calling it statically)
Shorten the BallUseLegality expression
Defer ec%100 context to PKX (easy to miss in a future update if any others added down the road)
Rewrite Alpha mark check logic to require entry in to HOME (tracker present) as that's the method of obtaining the mark
Make SaveUtil not allocate, and let the compiler optimize size checks.
2025-01-02 20:15:53 -06:00
Kurt
a6beb0293c Don't bother passing evos for hypertrain check
if it exists in PLA, then it can exist in SWSH/BDSP/SV (as of latest DLC!). Only need to check if it has visited (tracker present).
2025-01-02 20:04:04 -06:00
Kurt
8ec1c8cc81 Extract some magic numbers
If Legends: ZA adds more megas (likely), would have to refactor this monster. Rename the mega check, as SV doesn't have megas, but if ZA is Gen9(a?) we need to segregate somehow.
2025-01-02 19:57:38 -06:00
Kurt
92d49e858f Extract SV Runtime Language get/set
Useful for get to check if things are misaligned in a save file. If future games continue doing this, then we can reuse the enum.
2025-01-02 19:54:46 -06:00
Kurt
a41f32c66b Hide PKX.Personal behind methods
No need to have the entire master personal table visible within the assembly when it is only used for gender fetching. Add 2 methods that return the info that the previous consumers were wanting from the previous logic.
2025-01-02 19:53:29 -06:00
Kurt
88c7256841 Extract common technical record applications
Get a neat API for it to provide an "option" of what all you want set.
Ends up deduplicating some logic in batch editor as well.
2025-01-02 19:51:03 -06:00
Kurt
f77899ab73 Extract entity extension list fetch
No need to have a static member in PKM for something that is only used outside of PKM.cs (save files, GUI, aka file naming)
2025-01-02 19:48:56 -06:00
Kurt
243c4102dc Reduce some allocations in SpeciesName
implement the net9 shortcut for species name lookup from span
don't fetch japanese/spanish names twice or build dictionaries for them
2024-12-31 13:15:02 -06:00
Kurt
e2086d2a0d Misc tweaks
Allow pkm batch editor to take readonlyspan property names
concrete types over `default` for clarity
encounterverifier: use const values for egg levels for clarity
batchediting: fetch all properties only once
etrade4: reduce object size/init by having Contest as a property
2024-12-31 12:53:51 -06:00
Professor Dirty
e4f63848ad
Add new translations to CHS and remove obsolete paragraphs (#4406) 2024-12-24 10:58:40 -08:00
Kurt
83df635e04 Add more DPPt underground stats, update names
Closes #4405
2024-12-23 19:31:01 -08:00
sora10pls
e6a8df366d Add latest distribution raid/outbreak data 🐉 2024-12-19 19:03:51 -05:00
Kurt
341106d48f Gift leniency: wish m2>1/4 & frlg togepi m1>4
Closes #4404
Future improvements can add value ranges to restrict based on PIDs (e.g. M1 Wish must be below X, M4 must be above Y, or if it's a range, likewise. Or, if it's a certain %24 result for shuffling considerations).

Co-Authored-By: notblisy <50887637+notblisy@users.noreply.github.com>
2024-12-18 18:35:02 -06:00
R-YaTian
c198ab08e0
Gen 6: Fix Pokemon Link data saving, enable modify BP and PM (#4401)
* Gen6: Fix Pokemon Link data saving, enable modify BP and PM
2024-12-16 20:35:48 -06:00
Pasquale Nardiello
8e42776610
Fixed HallFame3 logic and added GUI for manipulation (#4395)
* Fixed HallFame3 logic and added GUI for manipulation
2024-12-16 20:28:42 -06:00
Pasquale Nardiello
a75e09780c
Added Initial edito for Secret Bases in RSE (#4386)
* Started making Secret base 3 editor

* Completed Initial RSE secret bases manipulation.
2024-12-16 20:23:50 -06:00
Kurt
872c79f124 Add gen1 hall of fame editor
Closes #4403

Co-Authored-By: ShadowMario3 <36941677+ShadowMario3@users.noreply.github.com>
2024-12-16 19:37:58 -06:00
Kurt
013cf4cf93 Fix cloning of gen1-3 saves in subeditors
Clone'd saves no longer retain same byte[] refs as original

evident in GUI editors and canceled changes somehow being saved
(gen3 isn't required, because it's unpacked into the blocks, but just be consistent)
2024-12-16 19:37:03 -06:00
sora10pls
e83ae026d6 Add latest distribution raid/outbreak data 😡🐵♟️🦒 2024-12-12 19:04:20 -05:00
Kurt
c6307b40ef Misc tweaks
SAV3: extract struct slice fetch
Metadata: allow setting `Memory<byte>` instead of `byte[]`
Stats: deduplicate gen1/2 mask set
remove some unnecessary spaces (lol)
2024-12-09 01:02:14 -06:00
Kurt
6397cf7723 Revise Gen7b FlagCount
Closes #4396

Co-Authored-By: Fábio H. Attard <fattard@users.noreply.github.com>
2024-12-07 08:20:03 -06:00
Professor Dirty
933c9d03cd
Added CHS translation and some corrections to the detection prompts (#4402) 2024-12-06 12:14:34 -06:00
Ka-n00b
0a27880ce6
Update Event Flags (#4398) 2024-12-06 12:13:59 -06:00
sora10pls
a2c03fc0a9 Add latest distribution raid/outbreak data 🐰🔵🟤 2024-12-05 19:04:03 -05:00
sora10pls
ff452bf5a6 Add latest distribution raid/outbreak data 🐦‍⬛🐸🥚 2024-11-28 19:15:36 -05:00
Professor Dirty
7fdd5abc52
Added CHS translation and corrected typos (#4393) 2024-11-27 17:03:06 -05:00
9Bitdo
a4b1da542c
Add Korea(KOR) Operation Get Mythical Keldeo, Zarude & Deoxys's date (#4394) 2024-11-27 17:02:35 -05:00
NimbusFox
f2dfeb20af
Fixing StatusCondition to report sleep correctly (#4397) 2024-11-27 16:45:56 -05:00
9Bitdo
a60e76f16d
Add Operation Get Mythical Keldeo, Zarude & Deoxys's date (#4392) 2024-11-21 21:43:17 -06:00
Kurt
231b3ffe57 IEncounterable: Add down-level interface 2024-11-18 21:58:09 -06:00
Kurt
8c37d9b4b5 Slot3/4: Filter met level on generate 2024-11-18 21:19:53 -06:00
Kurt
c30a335404 Update EncounterCriteria.cs 2024-11-18 20:46:50 -06:00
Kurt
594d133fae Update WA8.cs 2024-11-18 20:40:03 -06:00
Kurt
0b686d428b Change EncounterCriteria to use Level Range 2024-11-18 20:33:33 -06:00
Kurt
52351bf0e7 Generator: Gen3/4 slots w/ IVs & not-min level
Previously would only generate with minimum levels when IVs are provided
Now that it generates not-min levels, need to set the correct level.

Might be possible in the future to have IV-spec also respect a Level request, but for now it's only forceMin which nobody currently uses (no usage of the setter).
2024-11-18 18:56:18 -06:00
Kurt
5c338f5f74 Misc tweaks
Extract better signatures for BallApplicator.ApplyBallLegalRandom
Slot1: swap interface order (consistency with other classes, no impact)
IEncounterTemplate vs IEncounterable interface usage
2024-11-18 14:12:57 -06:00
Kurt
61526fad8d Add missing IV interface tag to trades 2024-11-17 21:15:07 -06:00
Kurt
4af95f63b3 Update BallApplicator.cs 2024-11-17 20:59:54 -06:00
Kurt
1a5d06d0d0 Update BallVerifier.cs 2024-11-17 18:52:42 -06:00
Kurt
076bbbbd77 Update BallApplicator.cs 2024-11-17 15:12:09 -06:00
Kurt
ceb669c112
Update to .NET 9, c# 13 (#4390) 2024-11-17 13:13:58 -06:00
Kurt
adb67922c5 Set dex skin active flag on import 2024-11-17 12:47:38 -06:00
Kurt
8b071073d8 Improve BallApplicator performance
Previous logic would check a new LegalityAnalysis for each ball attempted, and would additionally allocate a new PKM for testing the balls.

With the refactoring to BallVerifier, we can just pass in the template and ball and get a truth, skipping the entire re-check. There might be a deficiency when the current ball invalidates the encounter (GO) but I don't think that's worth considering at this time.
2024-11-17 12:08:22 -06:00
Kurt
b5e3e17987 Update BallVerifier.cs
No longer need to assign ball, can be used to check if ball can be assigned.
2024-11-17 08:59:48 -06:00
Kurt
89e705c6ae Extract ball verification result 2024-11-15 23:39:33 -06:00
Kurt
55b75e8061 Merge branch 'master' of https://github.com/kwsch/PKHeX 2024-11-15 23:07:11 -06:00
9Bitdo
46739891e7
Add Patrick's Pelipper date (#4389) 2024-11-15 10:25:05 -06:00
sora10pls
d028cb0aed Add latest distribution raid data 🐢🌲 2024-11-14 19:03:08 -05:00
Lusamine
1959eb34a5 Further refine memory 29 restrictions in SWSH 2024-11-12 21:10:21 -06:00
Lusamine
bf0e0e2a4b Further refine memory 29 restrictions in SWSH 2024-11-12 18:43:15 -06:00
Kurt
cccce007b8 Update EncounterSlot7GO.cs 2024-11-12 12:08:11 -06:00
Kurt
5955319883 Add expected PP check for stored-healed slots 2024-11-12 10:33:34 -06:00
Kurt
b60c6e5f14 Update 24.11.11 2024-11-11 23:25:02 -06:00
Cynthia Coan
f2a6abbb6c
validate nickname characters within gen4 (#4382)
pkmnclassic has recently had some pokemon traded that cause game
crashes when viewing the pokemon's information, or when trying to
remove the pokemon for these boxes. most of these pokemon were reported
legal by pkhex however. this fixes the biggest use of these we've seen
actively traded (some of the other checks require more validation, as
they seemed to be buggy, we intend to validate those & send more PRs if
needed).

this check effectively covers "NULL Bytes" within the trainers name,
or the pokemon's nickname. We have attached an example pk4 that was
traded through our service that exhibits this issue, a couple notes:

- Generation 5+ seem to not be affected and replace character names with
  '?'
- Not all screens crash inside of Generation 4, the big ones our users
  noticed were viewing the pokemons information, and removal from the
  boxes.
- I also got a crash in pokemon ranch, but my testing setup was pretty
  hacky, and I'm not confident it wasn't something else, but we know
  it's potentially possible.

- We check for the terminator character '\uffff' which the pkhex string
  converter inserts implicitly when encountering an invalid character,
  but the actual underlying character when performing a hex dump is
  `\0`.
2024-11-11 22:28:01 -06:00
Kurt
fced599119 Fix HGSS box saving (empty->not)
HGSS tracks each box if it is changed, to skip writing it on in-game save. PKHeX (and other editors) weren't updating these bitflags, so when empty boxes had Pokémon added and saved once in-game, the save file would corrupt as it was copying the checksum but not the box's data.

Saving twice in-game will cause the checksums to get updated even without the flags being set. I don't think we understand it fully, but this hacky workaround of noting all boxes as "changed" will cause the first-save to properly copy all contents of the boxes to the new primary save section. Rather than tracking which boxes we are reading/writing slots for, or fixing/comparing against a backup -- we need to account for API usage where people might write directly into the save (pcdata.bin/boxdata.bin too). Best to play it safe and let the game fix it.

Most wouldn't have noticed this issue as the box they're modifying is usually their most-recently-changed in-game box.

Closes #4368
2024-11-11 22:25:34 -06:00
Kurt
6153d6c851 Restrict gen3 PCNY OTs further
last 4 sets were only on the D memcard
add notes regarding Dragon runs using B memcard (staff)
https://pokemonhistorian.com/pcny-project/pcny-preservation-records/
2024-11-11 15:17:01 -06:00
Kurt
b0a2c7911e Fix Met tab startup binding
`ListControl._dataManager` is null until some event fires, which never happens until the Visible state is normally triggered by flipping to that tab.

The met tab Version/Location can skip Visibility changes on startup, resulting in never being initialized by the GUI framework, thus ignoring any calls to change SelectedValue. Really weird, but setting the BindingContext here forces it to create a `_dataManager` when setting a new `DataSource`.

Closes #4384
2024-11-11 15:11:47 -06:00
Kurt
5d4976303d Add manual handling for Mainland China gifts
Closes #4375
ty manu for clarifying transfer restrictions on discord
2024-11-11 10:05:17 -06:00
Kurt
52437d1712 Gen3 PCNY: remove OT gender checks
Same as PCJP, uses the recipient's save file gender. Not random.
2024-11-11 08:16:58 -06:00
Eelen
4414b34fe7
Update lang_zh.txt (#4387) 2024-11-11 00:00:34 -06:00
Kurt
8b09d9467d Misc fixes 2024-11-10 19:22:59 -06:00
Kurt
de57e197ad Fix gen3 reversal exploration for 50% of seeds
line 340 - variable reuse!

In looking at the emerald disassembly, when Pressure/Hustle/Vital Spirit fails, it reduces the max level of the slot by 1 so that the max level doesn't randomly appear. Essentially 50% flat for the max to appear, for varied slots, not 50% + (1/range). Thus we require a separate branch of logic to check for this scenario.

f8119bedd4/src/wild_encounter.c (L298)

DPPt (and assumedly HGSS) do not decrease the random range on failure.

ty TFS for bringing this to my attention on discord
2024-11-10 19:11:30 -06:00
Kurt
d1a51797c7 Fix encdb search pane scroll, pkmdb overlap 2024-11-10 18:08:58 -06:00
Kurt
947bbc7274 Fix Sirfetch'd deferral, add Tera deferral
Marill and Azurill need deferral cases as they change primary type
(normal-fairy => fairy || water-fairy => water)
2024-11-10 15:32:57 -06:00
Kurt
e4e3d929a0 Handle Gift method2 eggs better
Set PID correctly
Defer on fateful (wynaut egg clash, hot springs)
2024-11-10 15:16:19 -06:00
Kurt
c8726c4e2d Fix T2/Channel jirachi enc->pk3 generating 2024-11-10 15:00:31 -06:00
Kurt
1ffbd46e3c Extract some logic, deduplicate 2024-11-10 13:25:30 -06:00
Kurt
3c574a45f1 Revise enc1/2 -> pk1/2 nickname set
Skip the IsNicknamed evaluation, saving at least 1 string allocation on ctor. Remember the IsNicknamed state when we set false.
GUI: provide the selected language rather than recalculate
2024-11-10 09:32:33 -06:00
Kurt
51464fb4af Update PK9.cs 2024-11-09 22:34:33 -06:00
Kurt
d77d6b7ce6 Fix MethodJ enc->pkm nature check
silly differences; MethodJ should have been /A3E not %25
Make GetNature public, and have the MethodK esv modulo be uint instead of long for better clarity.
2024-11-09 21:37:03 -06:00
Pasquale Nardiello
38756b44c7
Added Lilycove museum manipulation (#4385)
* Added method to extract pokémon data from lilycove museum and experimental way to set paintings active and change their subject in order to obtain 2nd trainer star and crystal decoration
2024-11-09 18:58:23 -06:00
Squid
62b58f78e7
Labels for Gen 5 Trainer Records (plus renamed flag) (#4362) 2024-11-09 09:08:57 -06:00
Professor Dirty
4bad2e3cdf
Add CHS Event Flag translations (#4383) 2024-11-06 10:59:20 -06:00
sora10pls
50ff039c1b Adjust HOME Tandemaus gift date range
Like with Shiny Zeraora in the past, the only limitation is the distribution of the gift itself. Once added to your Gift Box, it can then be claimed at any time, giving it no end date.
2024-11-01 09:30:04 -04:00
9Bitdo
26fd23a338
Add PokéCenter Birthday Tandemaus date (#4379)
submitting the date based on

- hard copy serial code issued out at Pokemon center in japan on 1st Nov 2024 1000hr (JST)(operating hous : 1000hr), hence countries in the UTC-11 would be able to redeem it on 31th oct 2024 1400hr earliest

- Serial code expiry date is from 1st Nov 2024 - 31th Jan 2026 (JST) or 1st Feb 2026 (UTC+11 )
2024-11-01 09:28:15 -04:00
sora10pls
56bd4e6486 Add latest distribution raid data 🐊💥 2024-10-31 20:02:09 -04:00
santacrab2
2cd9632c4a
MailBox fix (#4377)
* Catches Out Of Bounds error on ListBox SelectedIndex for when the B_PartyDown_Click event is triggered and the current Selection is in LB_PCBOX.
2024-10-28 18:21:44 -05:00
sora10pls
e2d73eb866 Add latest distribution raid/outbreak data 👻🎃🍭 2024-10-27 20:05:52 -04:00
Easy World
ff24892ec2
Update and revise zh-Hans translations (#4376) 2024-10-27 00:09:11 -05:00
abcboy101
e7be55242d
Support SWSH gender/fashion item editing (#4374)
* Support SWSH gender/fashion item editing

* Update translations
2024-10-25 17:00:54 -05:00
BlackShark
693cb3a70b
Renamed DLC Editor button (#4372) 2024-10-24 11:14:25 -05:00
BlackShark
d2cc13da3d
Updated badwords.txt (#4373) 2024-10-24 11:14:01 -05:00
Lusamine
dfd23f18b3 Add more LGPE crossovers from Route 11 & Route 21
Route 11 -> Vermilion City
Route 21 -> Pallet Town
2024-10-24 00:19:27 -05:00
Lusamine
9adedf429e Remove inaccessible tree encounters from LA pkl
Trees of type "gimmick_tree10" don't have leaves and can't be activated
2024-10-22 11:58:19 -05:00
Lusamine
451fd0e0a0
Handle crossover encounters in LGPE (#4371)
* Handle crossovers in LGPE

Ty @sora10pls, @LegoFigure11, @Skadiv, and @PP-theSLAYER for research!
2024-10-22 08:13:41 -05:00
abcboy101
d19acdb6ec
Support G6/7 player gender editing (#4370) 2024-10-22 08:06:39 -05:00
Lusamine
34f0c715ef Correct Vermilion City typo in comments 2024-10-21 22:41:38 -05:00
sora10pls
ec32302a5c Add latest distribution outbreak data 🐒🐿️ 2024-10-17 20:05:26 -04:00
Kurt
48abd8d080 Revise WC8+ ribbon set logic
For making fake WC*'s, revise logic to use the ribbon span instead of the entire range of bytes.

Fix Partner Ribbon not showing up in Ribbon Editor affix list.

Closes #4369 (superset, I wanted to change the underlying usage)

Co-Authored-By: Manu <52102823+Manu098vm@users.noreply.github.com>
2024-10-16 18:44:54 -05:00
sora10pls
325649b19b HOME: Add Meloetta/Tandemaus legal date ranges 2024-10-16 18:42:01 -04:00
Kurt
bc94f7b9ec Update EncounterGift3.cs 2024-10-16 15:42:41 -05:00
Kurt
be9767f3e6 Add settings tab to pkmdb/encdb
Quicker toggling compared to main window settings changing
same object
2024-10-16 12:49:12 -05:00
Kurt
b068027ba8 Add 32bit FnvHash methods
With all the abstractions since SW/SH's release, the string hash method used by some SW/SH blocks is the 32bit method, not the 64bit one used by PLA/SV.

`FnvHash.HashFnv1a_32("ZukanData_Pokemon")` = `0x4716c404` as noted in the block accessor list.

Expose in the API as 32bit for documentation purposes. Good to know if we ever go back to bruteforce like is being done for SV file paths.
2024-10-16 00:15:30 -05:00
Kurt
b389cd3acd Rework Gen3 Event PIDIV detection & checks
Detect base algorithm (regular, regular-anti, anti, shiny) and check for sub-patterns that differ per encounter (such as OT gender).
Implement PIDIV generators within the Gift3* template, remove old branches
Add Table Weight detection (still need to test) for PCJP 5anniv
Add PCJP GCEA campaign 1-6 templates
Differentiate Mystry Mew seeds; disallow Mews that were deleted b4 cart cloning
Need to double check some gen3 events (esp hadou titans), but is 99% there

Thanks Sabresite, for your continued support
2024-10-16 00:01:10 -05:00
Lusamine
71c9c43eef Add alternate met location for LA static Unown O 2024-10-13 13:18:15 -05:00
Lusamine
a98e5137d3 Add remaining LGPE static gift event flags 2024-10-13 13:12:16 -05:00
Kurt
4f6ac58622 Add KeyData extdata, revise Link chunk set 2024-10-09 21:46:56 -05:00
Kurt
b9768c14b5 Update WB8.cs
Closes #4352
2024-10-08 21:43:44 -05:00
abcboy101
34c4d5c90f
Properly detect if G3 Mail is Japanese (#4363)
International author names are padded with spaces to at least 6 characters.
On display, it's treated as Japanese only if the name is 5 characters or less.
2024-10-07 13:27:26 -05:00
Kurt
53b9c27ec9 Add Battle Test metadata properties
Set the magic, set the flags to whatever, and refresh the checksum -> NPC recognizes you have DLC battle test ready and lets you challenge it immediately when starting a conversation with him.
Of course, it crashes immediately because we don't know what the 0x000 - 0x5C1 region data should be, but hey, this is progress for unlocking unreleased content.
2024-10-06 15:32:46 -05:00
Kurt
ec24b8b8d5 Enhance Gen5 download content I/O
Initially just C-Gear, can do better!
Adds i/o for Pokedex skins, Musicals, Battle Videos, Movies & PWT (B2W2), and a research-only tab for Battle Test (never distributed by GameFreak, so doesn't work).
2024-10-06 01:10:32 -05:00
Lusamine
dfac093e43 Add LGPE event flags for Porygon and Lapras 2024-10-06 00:15:29 -05:00
sora10pls
52c32292d2 Add latest distribution raid data 🪨🐒 2024-10-03 20:02:41 -04:00
santacrab2
efdc308bfa
resize pkm database Table Layout Panel to unhide search button. (#4364) 2024-10-03 16:14:05 -05:00
Kurt
1dc94152c3 Fix array reference
GC strings are 22 bytes long, but GBA strings are 10 & 7 bytes long. If a 7-char OT name is generated in C/XD and transferred, it would try iterating to the 8th char which is OOB for dest.
Iterate over the smaller array, which is always dest (in this method).
2024-10-03 14:44:22 -05:00
abcboy101
7632230edf
Support G2 mail in all languages (#4359)
* Support G2 mail in all languages

- Handle Japanese/Korean mail
- Handle both NPC and user-entered mail (#3470)
- Display European mail based on mail language (#4027)

* Fix shift

* Add support for Stad2 mail
2024-10-01 00:02:37 -05:00
Kurt
4aacffb99b Move Color15Bit 2024-10-01 00:00:34 -05:00
Kurt
e5c3c43bda EncounterCriteria: sbyte IVs
Narrow fields to reduce allocation < 16 bytes (maybe make this a struct?)
Add method to get combined IVs (gen3/4 format) for RNG purposes
use ^ method in RNG methods
Change GetExpectedLevel to be generic rather than interface, better codegen
Change Gender to enum for clarity

breaks ALM and potentially other plugins, just need to recompile if using IVs
2024-10-01 00:00:19 -05:00
Kurt
9479e8cb5f Better catch program init exceptions (plugins)
Discard plugins that fail to load, rather than aborting the entire plugin load operation
Add friendly message for unzipping fail (no PKHeX.Core.dll self-extracted).
2024-09-30 23:56:09 -05:00
Kurt
b187633ec6 Fix Misc7 wormhole shiny property r/w
Redirect reader to r/w the Misc block property rather than the buffer directly
2024-09-30 23:54:54 -05:00
Kurt
ee00a21f90 Minor clean 2024-09-30 23:54:03 -05:00
Kurt
a970f05a62 Too many tiles: return excess rather than clamped 2024-09-30 23:53:02 -05:00
Kurt
5b5418cd3f Update EncounterText.cs 2024-09-23 15:27:27 -05:00
Kurt
327585d1b4 Fix gender sanitization
g is `byte`, ternary returns `byte`, not `byte?`
be explicit
2024-09-23 08:21:02 -05:00
Kurt
128fcdff7a Loosen restriction on party set (quantity)
Empty array being set is a legitimate write; properly clears the party data.
2024-09-23 00:48:57 -05:00
Kurt
1c7bc9d455 Add settings to export/show record properties
For legality analysis exports to clipboard, and hovering slots in encounter db, can show all properties if desired. Was previously a debug mode only thing, but why not.
2024-09-23 00:47:43 -05:00
Lusamine
6317b7f552 Add latest distribution raid data 🍃🐍 2024-09-19 19:29:26 -05:00
Kurt
4594b0d879 Update default version fetch for Gift3 rsbox
Gen3 save file without trainer in database fetching a Gen3 gift -> Gen3/RS -> version fails to determine
just return the RS fallback; add EFL
2024-09-18 16:52:12 -05:00
Kurt
e5c4bef4b6 Add computed BaseEXP to SV personal info
51fe9491ec
2024-09-16 09:12:21 -05:00
sora10pls
1e86f80a66 GO: Add handling for Max Battles 2024-09-09 20:49:39 -04:00
Kurt
d6ca1ebee1 Add SafeTerminate string write option
Inconsistently used in Gen5 (clear, clearFF, clearSafe); if I ever get around to checking trash bytes in those formats it'll be necessary.
2024-09-05 23:58:48 -05:00
Kurt
617914b257 Allow some nidoran/volbeat to mismatch correlation
https://github.com/kwsch/PKHeX/issues/4356#issuecomment-2333031493
fix flow so it is called in format3+
Closes #4356 ty @abcboy101

add another check for EC=0 eggs from Gen4 -- game uses the PID value as the "is egg available" flag, so can't obtain a 0-pid egg from gen4!

remove python paths from gitignore, so I can call one of the test folders `Eggs`
2024-09-05 23:55:46 -05:00
sora10pls
95ffbca973 Add latest distribution raid data 🔥🐯🤼‍♂️ 2024-09-05 20:02:19 -04:00
Kurt
86d1e5ce15 Add nidoran/volbeat-illumise gen3/4 gender check
Closes #4356
adds check
revises egg->pk* logic to generate a valid pid for this case
2024-09-05 00:50:08 -05:00
abcboy101
3707ee5eb1
Standardize language codes and improve locale handling (#4353)
Use standard BCP 47 language codes
Move Culture utils into WinFormsUtil
Detect system language on first launch
2024-09-04 18:51:35 -05:00
abcboy101
42c1dee4e1
Document JP Colosseum Bonus Disc gift flags (#4350) 2024-08-28 19:04:44 -05:00
Kurt
0e4d30fd21
Rewrite C-Gear Skin conversion code (#4351)
#4337

On form open, sav->cgb->png
On file import, data->cgb->png
On image import, img->cgb->png
On form save, cgb->sav

Original data is retained when opening/importing file, rather than rebuilding png->cgb every time on form save.

Changes:
- can now adapt logic easily for pokedex skins
- importing image with too many colors/tiles will instead use color/tile 0 instead of throwing an exception, then will alert after importing.
- importing image with wrong dimensions/format will alert rather than display exception message
2024-08-28 19:04:25 -05:00
Kurt
ba7646e7a2 Fix TopMost appearance for Group Slots viewer
SAV editor gui:
Used by stadium to show registered teams, the form would misbehave when showing hover previews if you change focus between main window and the popup form.
Copy the `form.Owner=ParentForm` behavior from the popup box viewer, which behaves correctly.
2024-08-25 10:16:38 -05:00
Egzon
a75bb2c758
Spanish translations (#4348)
* Translated and revised constants and flags from gen 5 to gen 8 to Spanish

* Translated and revised some of the gen 3 and gen 4 constants and flags to Spanish

* Translated and revised the program's text strings to Spanish
2024-08-23 17:34:10 -05:00
sora10pls
984db07397 Add latest distribution raid/outbreak data 🐲🐌 2024-08-22 20:05:07 -04:00
Lusamine
19882f1c6c Add two additional LA Enamorus met locations
Easy enough to catch it in Bolderoll Slope by throwing a ball and
running that way, and there are multiple easier ways to get the generic
Crimson Mirelands location

Closes #4343
2024-08-21 01:02:49 -05:00
Kurt
76302d0803 Reorder IV bits
Closes #4346
make IV properties public for either/or usage.
2024-08-19 21:45:18 -05:00
Kurt
04466ac209 Extract some AffixedRibbon logic
const now reused across entire sln
pkm editor GUI no longer crashes on hover of out of range affixed ribbon
pkm editor GUI now indicates the quantity of available affixed ribbons on hover (does not indicate if 1 and already affixed).
2024-08-19 21:11:08 -05:00
Kurt
27b552db13 Retain existing shiny flags on complete dex
Closes #4344
2024-08-17 23:04:37 -05:00
Kurt
9d06a2bc2d Add GameVersion.EFL to learn source switch
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/?do=findComment&comment=291016
Dumb logic but it works.
2024-08-17 22:08:28 -05:00
sora10pls
8fdcb634f0 GO: Add handling for WCS 2024 timed research encs
f2296ccedb update pickles because Necrozma encounter types had to be incremented by one
2024-08-17 13:58:04 -04:00
Kurt
8dae16102d Fix pokerus to allow evolved untransferrables
PLA sneasel -> transfer, catch virus -> PLA, evolve => was flagged. Need to check the encounter species-form, not the current species-form.
List all the species-form that are of consideration.
Wyrdeer, Kleavor, Ursaluna, Sneasler, Overqwil, Basculegion
2024-08-17 08:23:33 -05:00
Kurt
c524c37013 Misc tweaks
Remove GameVersion from IShadow3, lessening usage of fake gameversion IDs
fix sav3 with absurdly high record values (int.MaxValue -> uint.MaxValue)
2024-08-16 21:19:08 -05:00
9Bitdo
4330106d7d
Add WCS 2024 Steenee & トモヤ Sylveon date (#4342) 2024-08-16 20:29:50 -05:00
Kurt
a02e4db098 Update SAV_Misc5.cs
Fixes comboboxes from showing empty initial selections
2024-08-08 20:23:03 -05:00
Kurt
ad17f5f231 Fix Gen4/5 ribboncount get
oops offset
2024-08-08 20:22:38 -05:00
sora10pls
87762fe7f1 Add latest distribution outbreak data 🏆 2024-08-08 20:05:07 -04:00
Hernán Indíbil de la Cruz Calvo
68eb8a58d5
Added more Battle Subway fields (#4334)
* Added current set fields BW(2) subway

* Added battle subway data

* Fixed checking wrong chkbox

* Added NPC met flag
2024-08-07 18:56:39 -05:00
Kurt
97aa9805a9 Add GS Ball manual enable button
Adds a "misc" editor for Gen2 saves (only crystal for now), with a single button to enable the event (button enabled if the event is not yet enabled, lol)

746a06f1de/engine/menus/save.asm (L164-L175)

Closes #4335
2024-08-07 18:55:19 -05:00
Kurt
c9f3894076 Remove nature from FRLG Unown method loop
Only checks for Form.
c6a2f50491/src/wild_encounter.c (L238)

Closes #4332

make the private FrameCheckDetails<T> actually <T> instead of specific, to match MethodJ/K.
2024-08-07 17:03:27 -05:00
WonderSquid
1b3544ce78
Records.cs (#4330)
Fixed gen 6 index (inserted Cosmic Flips + renumbered)
2024-07-27 17:43:05 +02:00
Kurt
8578912360 Update 24.07.27 2024-07-26 15:38:03 -05:00
9Bitdo
f501c8a6b1
Add Roy's Fuecoco date (#4329)
Add Roy's Fuecoco date
2024-07-26 15:22:52 -05:00
sora10pls
fcbf521767 Add latest distribution raid/outbreak data 🐟🍣 2024-07-25 20:03:38 -04:00
Kurt
a84f440746 Update EvolutionGroupHOME.cs 2024-07-23 20:33:21 -05:00
Kurt
bf1dcf0737 Clamp level range to slot for 3/4=>5+ transfers 2024-07-22 16:09:37 -05:00
Kurt
ea4ff5714c Add targeted generator methods for CXD pidiv
If IVs are specified, will try to search for all seeds that originate them.
Same as was done for Method H/J/K of mainline games, this allows sets to be more-quickly generated if the criteria is provided.
2024-07-21 21:13:53 -05:00
Kurt
f35a8d8496 cxd blank sav: set default region for encoding 2024-07-21 01:50:08 -05:00
Kurt
d9504cd947 Add missing ribbon cases
Relevant for a new wc9 that has one of these marks...
2024-07-21 01:49:46 -05:00
9Bitdo
5702955723
Add Japan's Pokéss Summer Festival Eevee date (#4326) 2024-07-19 18:14:00 -05:00
sora10pls
6930afd2bd Add handling for GO Fest Necrozma encounters 2024-07-15 07:33:00 -04:00
Kurt
36d5e771b7 Update BoxLayout6.cs
Closes #4322
2024-07-14 17:17:10 -05:00
Ibis Liven
fc509200b7
Update SaveHandlerFooterRTC.cs (#4321)
add edge case for FlashGBX
2024-07-14 10:25:33 -05:00
Kurt
2205332326 Update EncounterSlot8b.cs 2024-07-13 16:20:56 -05:00
Kurt
3a131f61aa Add duking glyph remap check for Colo Plusle
Only applies to Spanish; other languages don't use special accent-mark chars.
make logic flow easier to understand for this if-duking case.
2024-07-13 10:34:55 -05:00
sora10pls
50930b0cc5 Add latest distribution raid/outbreak data 🐭🏄‍♂️ 2024-07-11 20:07:47 -04:00
902PM
e38481f7b0 localization (#4314)
* Update text_rsefrlg_00000_ja.txt

* Update text_gsc_00000_ja.txt

* Update flags_e_ja.txt

* Update flags_rs_ja.txt
2024-07-09 22:04:09 -05:00
Kurt
6e880f8e14 Add Emerald|FireRed|LeafGreen version group
Add new enum value to end to not break plugins using lumped values.

I think GameVersion needs to be reworked to remove all lumped values, and to instead implement a per-format enum (joy...)
2024-07-09 22:04:08 -05:00
Kurt
6568940d0b Rename WC3 to EncounterGift3, add PCNY
Adds PCJP class for whenever I get around to encoding that.
2024-07-07 01:07:02 -05:00
Kurt
298a0e6291 Allow HGSS hatch location Global Terminal
https://projectpokemon.org/home/forums/topic/65420-hgss-encounter-met-location-error-global-terminal/

only allow cross-version hatch locations if the egg location indicates it was traded
2024-07-06 12:15:44 -05:00
Kurt
27407aae60 Update XK3.cs 2024-07-06 02:45:14 -05:00
Kurt
e9a3192a95 Decouple WC3 enc from Mystery Gift, add jpn events 2024-07-06 02:36:33 -05:00
Kurt
52e22086e9 Extract IMetLevel for level!=met encounters
No longer marks Ranch gifts as Fishy for leveled @ threshold.
Encounter range check now ignores the MetLevel if it can't exist that low.
2024-07-06 01:40:12 -05:00
Kurt
99ebc47866 Extract TrainerName const to static class
Central location for where these values are defined.
2024-07-06 01:35:50 -05:00
Kurt
1dcda6d2e8 Fix sav clone metadata inclusion
Need to not include it when cloning saves into the sav constructor, need to mirror it in metadata instead.
2024-07-04 22:27:53 -05:00
Kurt
21271c2931 Fix jp oras work script filename 2024-07-04 18:43:13 -05:00
Kurt
3f49a01ae8 Sanity check save file personal info
https://projectpokemon.org/home/forums/topic/65409-2-exception-logs-found-on-latest-pkhex/?do=findComment&comment=290422
2024-07-04 18:42:27 -05:00
Kurt
66e8bf2645 Update 24.07.03
Update wc9 pkl
update dependencies (qrcoder got some perf improvements)
deduplicate HOME tracker message in SV by consolidating the methods
2024-07-02 23:51:10 -05:00
Arley Pádua
6de68ac626
feat: allow external consumers to specify AES implementation (#4311)
Allow external consumers to specify AES/MD5 implementation

HOME: Replace direct usage of transforms with built-in wrapper methods for easier API replacement.
BDSP: Replace incremental hash with one-liner for easier API replacement. Handle dirtying manually.
2024-07-02 09:12:03 -05:00
Kurt
298c83bc09 Misc tweaks
Fix dexnav move being allowed when relearn cleared and not learnable in dest game (duskull destiny bond gen9)
Move farfetch'd check to top of not-nicknamed, add deferral for GO match
2024-06-30 19:14:21 -05:00
Kurt
11b1eeb6c8 Allow HOME wrong apostrophe on farfetch'd
Stupid.
Move ash greninja check up with the CanHaveAnyLanguage as it's rectified in Gen8+.
2024-06-30 00:07:27 -05:00
Kurt
3824c2e9f7 SAV3: interact with other extdata sectors
Uninitialized sectors are now no longer updated for checksums
Checksums will be updated if not uninitialized (replacing battle video)
Not that the game really checks the checksum (lol)
2024-06-29 23:59:11 -05:00
Kurt
baac00c130 Invert export flags, fix footer inclusion gen1-3
Closes #4307
2024-06-29 12:55:13 -05:00
Kurt
dc5f1a0b17 Allow nicknaming ot-receive WC7; also WC6-link
Closes #4308
2024-06-29 11:38:30 -05:00
Kurt
37ea71eaa7 Fix pk4->rk4 convert copy 2024-06-29 11:21:37 -05:00
Kurt
00ce859584 Split pgf and wc5full
Importing had a bug where it would retain the metadata when setting to save file, rather than trimming down to the gift.
Split them into separate classes like gen6+

Remove erroneous WB7 size; 0x108 is only valid for a subsection, and won't work with the class since it needs to pull from the localization data. Nobody should be affected by that change.
2024-06-29 11:20:09 -05:00
Kurt
73c5e258b7 Move Breeding.CanGameGenerateEggs to generator
Already have a reference to it, no need to re-pivot off version.
Remove old method for checking if a Daycare exists; not all daycares give eggs, and not all egg-producing games have daycares.
2024-06-29 10:58:30 -05:00
Kurt
07a08fa328 Add some xmldoc 2024-06-29 10:55:01 -05:00
Kurt
8c21541305 Add missing SV->Gen8 transfer tracker check
SWSH format SV origin mon would report Gen9 but skipped the tracker check, no more.
2024-06-29 10:42:19 -05:00
Kurt
a84e59c0fe Use ReadOnlySpan for nest location fetch
skips allocating that big byte[][] entirely
No more static byte[][] references in PKHeX.Core !
2024-06-29 10:41:11 -05:00
Kurt
496c66bd46 Rename box storage slot flag fetch
`IsSlotX` is a little ambiguous; `IsBoxSlotX` much better.
2024-06-29 10:39:59 -05:00
sora10pls
bcb4e8374b Add latest distribution raid data 🦎🐲💞 2024-06-27 20:02:35 -04:00
sora10pls
d1416baf6c Revise SWSH dummied moves bitflag array
Prior implementation only accounted for moves with the description: "This move can't be used. It's recommended that this move is forgotten. Once forgotten, this move can't be remembered."

New implementation now checks for the CanUseMove flag, as is done with BDSP/LA/SV. This catches some oddities, like Kinesis becoming usable in Ver. 1.2.0 (Isle of Armor, Kadabra/Alakazam), as well as some signature moves for unavailable Pokémon, like Judgment and Seed Flare.
2024-06-24 12:19:23 -04:00
BlackShark
9ca0a46715
Fixed some SecretBase3 issues (#4305)
* Fixed some SecretBase3 issues

* Allow settings SpriteItems, use ushort for TID16
2024-06-21 21:27:33 -05:00
Eelen
2ab07201bd
Update CHS Translations (#4301)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2024-06-15 21:31:59 -05:00
Kurt
6b51250f88 Misc tweaks
PK5-PKMEditor no longer sets Hidden Ability flag for Rotom under HaX -- only set if unique hidden ability.
Differentiate move source tagging for HOME, DexNav, and GB-Evolution
fix some comma spacing
2024-06-15 18:38:05 -05:00
Kurt
10f7ef9d3e Add missing fallthru for HT Language on no memory
Needed to catch the EncounterStatic9 gifts (like Starter).
Update es9->pk9 to use the property instead of duplicating that logic
2024-06-15 01:13:51 -05:00
Kurt
f6bcc8a216 Minor clean
StringBuilder.Append has some fancy interpolation handling in the latest NET8 so it's more clean than multiple append calls.
2024-06-15 00:57:44 -05:00
Kurt
0ffb256052 Add slot source legality checks
Useful for save files with misplaced data (you really have to be using the program weirdly to get these flagged).
Stuff like Eggs deposited in Daycare, non-fuseable species in the Fused slots, etc.

Allow HaX to view any slot, & Delete if Set is allowed
2024-06-15 00:14:49 -05:00
Kurt
0499caa52e Update BK4.cs
Closes #4300
2024-06-14 09:10:08 -05:00
Kurt
b68fb27ccf Update StringConverter5.cs
Closes #4299
2024-06-14 00:06:40 -05:00
Kurt
f2ca406240 Don't set time of day for gift2->pk2 2024-06-13 22:04:54 -05:00
sora10pls
fff4e8a6e6 Add latest distribution raid data 🐗🔜🥓🤤 2024-06-13 20:04:01 -04:00
abcboy101
9817b29483
Add Berry Powder, Pokéblock3, Decoration3, and Record4 editors (#4298)
* Add Berry Powder to Misc3 editor

* Add Pokéblock Case editor for Gen3

* Pokéblock/Poffin localization

* Add Decoration editor for Gen3

* Localization

* Add Record editor for Gen4
2024-06-13 00:01:49 -05:00
Kurt
be79360778 Flag slotnumber instead of Tile
Oops
pk8: check for SV for met location loss
2024-06-11 08:25:20 -05:00
Kurt
4f23efd939 Minor tweaks
Rival_Trash -> RivalTrash
BinaryCodedDecimal better method names
Gift dupe checker differentiate by species
2024-06-10 21:43:27 -05:00
Kurt
992deea183 Fix B2W2 key unlocked get/set
Closes #4295
2024-06-09 08:54:02 -05:00
Kurt
67fc0d0885 Misc tweaks
Add Cap Pikachu const value updating for USUM, also set Magearna const for USUM (not only SM)
Remove `NotDistributed` property from WC3 as only CXD events used it, no longer WC3 type.
Delete ArrayUtil; move remaining methods into the StorageUtil class that used them for compressing/inserting bulk into boxes.
Add shiny wormhole flag
Extract byte array strings into spans, faster tests and less overhead.
2024-06-09 08:52:32 -05:00
Kurt
84d42807e0 Simplify discards on gui event methods 2024-06-08 01:34:32 -05:00
Kurt
06b8ec03ae Allow startup to detect memory card single-sav
Closes #4296
2024-06-07 09:42:52 -05:00
9Bitdo
2c687e48fb
Add Nils's Porygon2 date (#4297) 2024-06-07 09:40:39 -05:00
sora10pls
2f9ab3cee0 Add latest distribution outbreak data 🦸‍♂️🌊 2024-06-06 20:07:27 -04:00
Lusamine
d065c7d0d0 Clean up "Gift" from SV MG date comments
The Project Snorlax card comment used "Gift" because using "Snorlax"
twice sounds strange, and then that kept getting propagated to cards
after it. Brings comments back in line to before.
2024-06-06 10:09:19 -05:00
9Bitdo
676819ce4c
add sophia's gyarados.wc9 to mgdb and update Kaito Arii's Talonflame comment (#4294) 2024-06-06 08:23:27 -05:00
Kurt
114ba4c991 Handle BDSP eggs not having any trash 2024-06-05 23:53:01 -05:00
Kurt
de6c1baaf2 Minor tweaks
PK9: Fix writing box bin of self-owned SV eggs clearing nickname trash, also clear any hint of trading completely
SAV1: ensure current box is index:0 if uninitialized
2024-06-05 23:46:03 -05:00
Easy World
d5e5eec082
Update translation (#4291) 2024-06-05 08:24:04 -05:00
Kurt
637a0720a4 Extract manifest reader
Expose string cache dictionary for direct use
Allow plugins to reuse the EmbeddedResourceCache class for their own assembly's resources
2024-06-05 00:50:18 -05:00
Kurt
0bbba81fca Update 24.06.03
Stream translation text when loading. No need to allocate a few thousand strings and an array.
2024-06-03 23:32:45 -05:00
902PM
3ccdba4048
Changes (#4287)
1.Unified with in-game text.(text_cxd_00000_ja.txt)
2.Add Poké Seer's text for clarity.(text_gsc_00000_ja.txt)
3.Fixed some text.(text_rsefrlg_00000_ja.txt)
2024-06-03 19:38:48 -05:00
Kurt
093fca37ec Add enum to GUI translatable list
Improve perf of GUI scrape by only making forms once, don't fully set up Main
Add method to get a list of translated Enum names
Adds translatability for size markings
Closes #4266
2024-06-03 19:37:57 -05:00
Kurt
99a514b0bc Update translatables 2024-06-02 23:19:33 -05:00
Kurt
2d0d0236e3 Handle more RTC footer types
BizHawk Gambatte stores a 0x16 byte footer, but the correlation previously used isn't always true.
Just implement a generic detector that allows for a reasonably sized footer for the Gen1-3 formats.
2024-06-02 12:22:27 -05:00
abcboy101
5014b0c891
Add country/region editor to Geonet editor (#4286)
Adds individual country/region-level editing to the Geonet/Unity Tower editors
2024-06-01 17:56:20 -05:00
9Bitdo
acd2d49cdc
Add Kaito Arii's Talonflame date (#4285)
* Add Kaito Arii's Talonflame Date
2024-06-01 17:44:55 -05:00
Kurt
7d05e12c7f Misc tweaks
Fix pk6/7 pkmeditor export, now retaining status condition instead of wiping it
Abstract b2w2 key system to a save block; better documentation on its odd mechanics
Allow gen1-3 filename language/ver detect to work if the filename is ` `->`_` (discord attachments changing spaces to underscores); also revise canadian French emerald filename pattern
others: negligible perf/using standard library functions
2024-06-01 17:44:32 -05:00
abcboy101
e6cf61330a
Add Seal, Accessory, and Backdrop editors (#4284)
- Adds editors for the Seal, Accessory, and Backdrop inventories in Gen4
- Adds a line in the Gen4 Mystery Gift editor to display which Goods, Seal, Accessory, Backdrop, Pokétch app, or Route Map is contained in a gift
- Changes the HG/SS Apricorn editor to display localized item names instead of hard-coded English strings
2024-05-30 23:42:03 -05:00
sora10pls
8935b95eb4 Add latest distribution raid data ☠️ 2024-05-30 20:02:34 -04:00
Kurt
650f23ab82 Misc tweaks
Rewrites super training editor to not need reflection and special handling for translation
Fixes trash check for traded egg
Fixes Scientist trash applying to Ultra Beasts -- much better comparison
2024-05-30 10:40:08 -05:00
Kurt
b7298a7361 Auto-clean save file names of duplicate suffixes
`main (17)` now backs up as `main [...].bak` instead of `main (17) [...]`
Adds a debug-callable method to clean the main backup folder via:
`CleanBackups(Main.BackupPath, true);`
2024-05-27 19:23:56 -05:00
Kurt
c10c2a0dc2
Add Trash Byte verification for Switch formats (#4283)
* Extract logic, finish impl for switch era
* Extract trash checks to static class
* Reduce some allocations in OT name compares
2024-05-27 18:21:11 -05:00
Kurt
ca2bd3baf4 Minor clean
No functional change
2024-05-27 10:33:25 -05:00
abcboy101
a7421a7ceb
Localization (#4281)
* Display Medal type in SAV_Misc5
* Add CHT localization for Medal names
* Add localization for Medal type names
* Update Chinese localization for DPPt Underground
* Add additional G1/G2/G3 item name localizations
* Fix some typos in the Japanese names
* Add Korean names for G3 items
* Correct e-Reader Berry name format localizations
* Italian games use the format `BACCAENIGMA` (capitalized normally as `Baccaenigma`), so the entire item name needs to be titlecased, not just the Berry name.
* Add missing Gen2/3 location name localizations
* Minor corrections to existing languages to match in-game
* Minor tweaks
2024-05-24 18:51:05 -05:00
Kurt
c493580b55 Fix type display dropdown in gen12 2024-05-24 12:22:36 -05:00
Kurt
0be5581638 Enhance type icon downscale
No need to override the interpolation settings, looks way better.
2024-05-24 12:01:06 -05:00
Kurt
a79581e965 Add EncounterTime interface for Gen2 encounters 2024-05-24 11:55:46 -05:00
Kurt
225e2e3ce5 Add type icon to move dropdown 2024-05-24 11:52:47 -05:00
BlackShark
68ea86cc72
Fixed SecretBase3Team (#4280)
* Fixed SecretBase3Team

* Fixed more offsets

* Fixed Species
2024-05-23 21:00:11 -05:00
sora10pls
d4f8c2a5e5 Add latest distribution raid data 🧲🤖 2024-05-23 20:04:05 -04:00
Kurt
cd6eadcd8d Fix daycare slot read
Closes #4279
adds writeback for daycare in gen2 as well
2024-05-23 13:31:23 -05:00
Kurt
93ae048246 Fix sav1 daycare write
was copying wrong data in, clean things up.
#4279
2024-05-23 03:54:06 -05:00
abcboy101
4cb1048139
Implement GBA/GC string conversion (#4278)
* Swap GC nickname fields

The first nickname field is used for the displayed nickname, such as for EFIGS PKM whose names get truncated in Japanese games.
The second nickname field stores the actual nickname, which is restored when a PKM is traded back to the GBA games.

* Add StringFont3GC

* Add GC/GBA string conversion tables/logic

* Refactor GC region conversion

* Minor tweaks

Skip copying nickname/OT on generic conversion method; derived impl will remap the trash for us.
Extract the DisplayNickname truncation function and deduplicate. Use buffers directly when updating
Make CurrentRegion/OriginalRegion properties GCRegion instead of int/byte

Extract local func for font check skip for clarity
2024-05-22 12:22:41 -05:00
Kurt
96bf5a6891 Misc tweaks 2024-05-19 23:29:27 -05:00
9Bitdo
1735fd142d
Add Dot's Quaxly Date (#4277) 2024-05-18 19:16:52 -05:00
Kurt
49ffac5143 Add gen8+ special chars placeholder
use remapped chars from 7->8 as the list
2024-05-18 18:10:49 -05:00
Kurt
431b35a287 Remove unnecessary sugar 2024-05-18 17:51:50 -05:00
abcboy101
94e408d445
Use straight apostrophe for Gen3/4 (#4275)
Colosseum/XD/PBR/Ranch/Gen5 all treat the apostrophe/right single quote as a straight apostrophe (U+0027).

This primarily affects unnicknamed Colo/XD Farfetch'd, which need to use `'` instead of `’` to be traded to GBA correctly.
2024-05-18 10:04:25 -05:00
Kurt
3811f8d114 Misc tweaks 2024-05-18 00:40:29 -05:00
Kurt
899ee63434 Update dependencies 2024-05-17 22:55:11 -05:00
Kurt
0674d72fae Add ITrashIntrospection, impl on PKM classes
new api allows for checking for trash byte metadata, makes it much easier to write a verifier now
2024-05-17 15:58:49 -05:00
Kurt
f5c6510b82 7->8 Handle garbage string trash
no terminator? no problem
2024-05-17 13:18:48 -05:00
Kurt
da51291364 PK1/2 edit: Use save language when detecting lang
GBPKM allow spa/ita lang as fallback (so that nicknamed species just use fallback)
SK2 GetNonNickname reuse the passed language ID instead of recomputing
2024-05-17 13:18:25 -05:00
abcboy101
417231a67c
Update how symbols are handled for Bank -> HOME transfers (#4276)
This maps the remaining (legal) symbols in the private use area that are modified on transfer from Bank -> HOME. If any of these replacements are made, any leading or trailing halfwidth spaces are trimmed. This can result in nicknames/OT names that are the empty string or consist entirely of fullwidth spaces, even though these can't normally entered.
2024-05-17 11:56:55 -05:00
Kurt
4d6ce53bce Don't bother cloning sav for Gen1-3 dex edits
No need to clone the save file and allocate 2 bool arrays. Yay old code, why not do it better now? :)
2024-05-15 23:50:25 -05:00
Kurt
99857540ae Add version hints to filter name checks
Extract result valid check to less-wide style, add extra sanity checks for Gen3 typed saves.
2024-05-15 23:29:27 -05:00
Kurt
a4a0337162 SAV1/2: Force zeroed slots to be FF
Deleting a slot shouldn't leave a level 0 nameless speciesless mon
2024-05-13 19:47:23 -05:00
Kurt
ca85fba061 Force writeback on addition to uninitialized data
Adding an entity to an uninitialized save's box data will force all data to be flushed unless the final destination was (initially zeroed and has no slots to set).
2024-05-13 19:40:55 -05:00
Kurt
0836d3c670 Mirror handler check to bulk checks 2024-05-13 18:16:26 -05:00
Kurt
bbfec1fb29 GB: Don't read ghost slots
Read count from list, instead of using the full capacity.
Malformed lists (truncated via count) with ghost slots should have those ghost slots ignored.
2024-05-13 18:14:40 -05:00
Kurt
215f892f11 SAV1: Don't reference prior-save boxdata
If the boxes are not initialized, skip reading of box data
If the boxes are empty when saving, don't write if {dest 0} or {boxes uninitialized} to retain old data.
If the boxes have any slots when saving, set the flag that boxdata is good.

Remove flag from SAV2, game is different from SAV1. Only use the boxdata, just mirror to CurrentBox data as Stadium only looks at boxdata.
2024-05-13 17:41:23 -05:00
Kurt
3dc84d6a39 Revise ActiveTrainer checks if unset (unit tests)
No longer need to disable correct-handler-state check for unit tests
Adds indication for HT not matching gender (if active trainer is set)
2024-05-13 17:38:16 -05:00
Kurt
0f7d6e1b6a Handler check for WC7 ash pika reimplemented
The handler state checks to check against current trainer are new, forgot about this edge case.
Reduce allocation of temp strings on wc7->pk7, replicate the HT miss for ash pika wc7->pk7
2024-05-13 12:35:47 -05:00
间辞
89e22337a4
Add Sophia's Gyarados Date (#4272)
* Update EncounterServerDate.cs

* Add files via upload
2024-05-13 09:01:02 -05:00
Eelen
f74efceec3
Update lang_zh.txt (#4271) 2024-05-13 09:00:38 -05:00
Kurt
8b68a07dbd SAV1: Use current box span for source of boxdata
Maybe only a GSC thing where Stadium 2 doesn't know better?
2024-05-13 00:16:29 -05:00
Kurt
5af96eab95 Extract switch-entity HT update logic, fix
Closes #4227
SlotWrite no longer revises the data incorrectly

if ot&gender mismatch, clear memories and set new values
can retain wrong values by having ot&gender shared between games
2024-05-12 23:40:50 -05:00
902PM
28add13282
Fix some typos in Japanese localization (#4264)
* Update LegalityCheckStrings_ja.txt

* Update lang_ja.txt

* Update lang_ja.txt

* Update MessageStrings_ja.txt

* Update const_hgss_ja.txt

* Update const_rs_ja.txt

* Update const_e_ja.txt

* Update text_ItemsG3_ja.txt

* Update text_ItemsG2_ja.txt

* Update text_ItemsG1_ja.txt

* Update text_Character_ja.txt

* Update lang_ja.txt

* Update lang_ja.txt

* Update lang_ja.txt

* Update MessageStrings_ja.txt

* Update LegalityCheckStrings_ja.txt

* Update flags_dp_ja.txt

* Update flags_pt_ja.txt
2024-05-12 16:02:59 -05:00
Kurt
ed8d583c08 Add hover tooltip for special char insert preview 2024-05-12 14:51:25 -05:00
Kurt
7b6abc0520 Reduce alloc for ot/nick by raw trash reads
Legality check now catches buffer overflow mons.
Now that I have each type exposing a trash length & charcount, should be easy to have some reusable trash byte measuring methods (see the old branch)
2024-05-12 14:46:58 -05:00
Kurt
08ed482555 Revise gender symbol remapping
Handles Nidoran's shenanigans as well as more clear method names
- Add normalization for PK7->PK8 (no more 0xE... usage for the gender symbols... maybe more chars?)
- Not sure if Gen3 gamecube encoding needs sanitizing. Who is transferring Nidoran to CXD? :)

Requires some silly usage of Language passing as arguments. Future improvements can be made to revise the half/full encoding determination when setting a string. Probably has issues since we're just doing a naive check without considering nicknames w/ special chars.

Closes #4174
2024-05-12 10:47:55 -05:00
Kurt
a33884895f Remove IsNative property
Not something worth retaining if we can check directly.
2024-05-11 16:20:12 -05:00
Kurt
326e790e4b Handle and ' behaviors for 4->5->6
Gen5 does not use the slanted apostrophe for anything. 4->5 converts to ' for both strings.
Gen6 fixes all to be slanted. Even nicknames.
Importing to HOME (PK7, GO) resets nicknames, and the default name is not slanted. Nicknames/OTs are unaltered; again, only the "default" species name is wrong.

Closes #4066
2024-05-11 11:35:55 -05:00
Kurt
d2e8d722e7 Add setting for pkmeditor selected tab colors 2024-05-10 23:17:22 -05:00
Kurt
410be93358 Add opt-in boxdata dragdrop toggle
Can drag from the Box tab into another window or folder to copy the entire box's contents.
Closes #3933
2024-05-10 22:36:46 -05:00
Kurt
dcb23c7981 Allow adding/hiding extra properties in Report grid
#3933 3.
top level settings for Report

1. was already implemented with the file namer settings on dump

not sure how I want to do dragdrop-multi, but I did recently add a record type for `ConcatenatedEntitySet`...
2024-05-10 19:32:28 -05:00
Kurt
e0172b601c Allow randomizing IV32
Really shouldn't use it.
2024-05-10 19:30:01 -05:00
Kurt
969f733c2c Fix pkmeditor auto-set Nickname flag
Event was stolen via impossible-chars change, so just revert & call that method so only 1 method is hooked to the event.
2024-05-10 19:26:48 -05:00
Kurt
bd4ea9656d Misc tweaks
Fix block editor GUI not showing named blocks (`Equals` is easily repointed...)
No functional change for the others, just reusing/cleaning irks.
2024-05-10 01:22:26 -05:00
sora10pls
58a6d453b8 Add latest distribution raid data 🦭 2024-05-09 20:04:38 -04:00
sora10pls
bcb7135940 Add handling for Wonder Ticket Poipole
See 1ec0f91e08
2024-05-06 13:20:51 -04:00
Easy World
e9b7ccac17
Update Chinese Translation (#4262)
* Update Chinese Translation

* Translation tweak
2024-05-06 06:05:01 -05:00
Kurt
5af6678784 Update 24.05.05
Fix devutil text update
swap tox & poison icons, move picturebox to the right
add some Legality settings util functions
2024-05-05 23:24:32 -05:00
9Bitdo
19951f10e1
Add 신여명's Flutter Mane distribution date (#4261)
* Add 신여명's Flutter Mane distribution date
2024-05-05 23:15:32 -05:00
Kurt
e72ff1a899 Misc tweaks
No noticeable change besides correctly indicating N's Pokemon in hover text (instead of Dream Radar)
2024-05-05 15:33:30 -05:00
902PM
004795b329
Changes (#4257)
* Create const_sm_ja.txt

* Update const_pt_ja.txt

* Create const_oras_ja

* Update flags_oras_ja.txt

* Update const_rs_ja.txt

* Update flags_rs_ja.txt

* Update flags_e_ja.txt

* Create const_b2w2_ja.txt

* Create const_bw_ja.txt

* Update flags_bw_ja.txt

* Update flags_b2w2_ja.txt

* Update lang_ja.txt
2024-05-04 15:10:43 -05:00
Kurt
2d61c833bc MysteryGift drop: use extension-less method if w/o
New Korean WC9 is restrict:0 so just make it behave like restrict:3 -- verifies all possible versions that way.
2024-05-04 14:48:19 -05:00
sora10pls
6855c842ca Permit Ranked Ribbon on Legendaries for Regulation G 2024-05-02 20:28:57 -04:00
Kurt
ad0c9b147d SAV6AO: Fix dexnav get/set
Was looking in the wrong segment of save data. Now points to the correct span (pokedex).

Extract a few more save blocks and interactions.
Basically try to get rid of any remaining `SAV.Data` references. Still a few left, but aren't broken.

Closes #4259
2024-05-01 22:58:47 -05:00
Kurt
0f4024952e Minor clean
indexof -> contains
trailing space
some variable reuse
pcdata/boxdata less janky handling
2024-05-01 00:49:43 -05:00
Eelen
40353c7922
Update CHS Translations (#4256)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2024-04-28 22:47:32 -06:00
Kurt
78fde1367d Add more per-context settings 2024-04-28 18:19:55 -05:00
Kurt
9231f37be4 Move outbreak iteration after slots
essentially wild slots... can have wild marks like slots
need to check before static encounters (cannot have marks)
2024-04-28 16:44:28 -05:00
902PM
eb273d05a4
Japanese language translation updates (#4255)
Revises gen3/4 japanese strings as well as some program messages.
2024-04-28 10:27:11 -06:00
Kurt
e9de611735 Fix sav2 clone
Regression from the version-detect enhancement where we weren't expecting specific versions to be passed, only the lumped ones.
2024-04-28 11:25:46 -05:00
Kurt
e7f79be41d Minor clean 2024-04-28 00:35:26 -05:00
Kurt
bb6e45db60 Refactor out legality settings, add more settings
Extract PP verifier
Add disabling of wordfilter for formats < 6
Add context-specific bypasses for nickname/handler checks
2024-04-26 14:33:03 -05:00
Kurt
94f93d41c5 Update EvolutionGroup4.cs
Handles arceus-9 in future generations (reads as "not present in current game" via personal info if not stripped).
2024-04-26 08:35:54 -05:00
Kurt
a296403595 BeginInvoke for Folder datagrid
auto-picks the correct thread for the control/form

batch editor progressbar is useless cuz the editor is so fast for 99.99% of uses (only really matters for processing a folder on slow drives), yay speed.
2024-04-26 01:34:47 -05:00
Kurt
3358038172 Add ball deferral & met level leniency for gen3/4
Fixes some issues reported via discord
2024-04-26 01:33:19 -05:00
Kurt
4e56a2b756 Fix zhs/zht table swap
simplified is the first set, not the second
Closes #4254
2024-04-26 01:27:53 -05:00
sora10pls
9788f007a3 Add latest distribution outbreak data 🐟🚗🦁💩 2024-04-25 20:06:07 -04:00
Kurt
c0e44d6375 PA8: add missing marking interface 2024-04-25 10:24:38 -05:00
Kurt
2296d34df3 Gen1/2: Fix party read
Oopsie false->true ez
Closes #4253
2024-04-24 19:24:49 -05:00
Kurt
9ff94455b9 Add setting to retain met date on 4->5 transfer 2024-04-24 02:11:14 -05:00
Kurt
093264bc24 Add IStringConverter, impl on PKM & SaveFile
use in trash editor to translate strings, from PKM instead of SAV.
Closes #4222
2024-04-23 00:57:17 -05:00
Kurt
394f5ed707 Clamp minimum size of PKM editor popups 2024-04-22 20:47:59 -05:00
Kurt
1b294f7c97
Refactor: Split Gen1/2 string & pokelist conversion methods (#4251)
* Split Gen1/2 string & pokelist conversion methods

* Refactor pokelist to direct read/write, skip on save if blanked

* Add settings editor for SaveLanguage overrides
2024-04-22 14:42:22 -06:00
902PM
3087519c62
Translated to Japanese. (#4249)
* Update lang_ja.txt

* Update flags_hgss_ja.txt

* Create const_hgss_ja.txt

* Create const_dp_ja.txt

* Create const_pt_ja.txt

* Update lang_ja.txt
2024-04-22 14:40:03 -06:00
Kurt
f93b616ca8 SV: Fix EV yield bit ordering
Closes https://github.com/kwsch/PKHeX/issues/4252

see dcf6b0043b
2024-04-22 13:33:12 -05:00
Kurt
54525da20b Add tradeback wipe of Gen2 initial moves 2024-04-20 14:04:54 -05:00
Kurt
e1b964ad64 Use img instead of color
Still too big
2024-04-17 01:56:09 -05:00
Kurt
f630ad8271 Add setting to hide status condition view
Likely will be moved to the bottom right corner and shown with a language-less sprite. Feature preview on main :)
2024-04-17 00:44:31 -05:00
Kurt
35bf97eaf1 Add status indication & switcher popup
Shows on all tabs to make it clear.
Obviously, status condition is only saved in formats that save status condition in their slot's resting format. Gen3 box mons won't store Burn, etc.
2024-04-17 00:35:42 -05:00
Kurt
b60648627a Add setting to show Gender in Gen1
Default false, current behavior.
Previous commits (years ago) would show Gender, but only current-format properties are shown now.

Fix some form scaling settings that haven't been reported, match other controls.
PKM property copy now checks if src==dest property type; for Gen6<-Gen7 markings, converting ushort -> byte can cause the conversion to fail if more than 8 bits are used. Just ignore copying the marking value, by only copying properties that have the same name AND type.
2024-04-16 21:01:14 -05:00
Kurt
2e62e41ab1 Add gen5 trainer record edits
Refer to src (Record5) for indexes; documented a few.
Closes #4245
2024-04-13 09:06:16 -05:00
ネイ
9caab05aab
Change gen4-gen7 Japanese location names (#4246)
* Fix gen4 Japanese translations

* Add Japanese descriptions where location names are different between games
2024-04-11 22:32:29 -06:00
sora10pls
ce666680f7 PK2: Expose Fast Ship as accessible met location 2024-04-11 10:04:28 -04:00
9Bitdo
8f65874ba4
Add Marco's Iron Hands distribution date (#4238) 2024-04-10 22:01:19 -04:00
ネイ
5794ffd78a
Update text_rsefrlg_00000_ja.txt (#4242)
Translate the remaining untranslated location, from text dump.
2024-04-09 19:31:54 -06:00
ネイ
4972ab469c
Update text_cxd_00000_ja.txt (#4240)
Translate the remaining untranslated location names with reference to text dump
2024-04-09 07:28:46 -06:00
Lusamine
a6868834c8 Add alternate met location for LA static Unown N
Closes #4237
2024-04-06 15:53:59 -05:00
Kurt
0552dcd37c Crystal: Add e-speed-less dragons den dratini
VC has no move relearner so can't unlearn espeed & relearn the level up move to look the same.
Rearrange moves to match how the game gives them.
2024-04-06 09:06:45 -05:00
Kurt
9c1538f503 Disallow HOME move share w/ battleversion & <=Gen7
HOME's SWSH moveset is disqualified from having <=Gen7 moveset permissions if the battle version was applied in SW/SH.
2024-04-05 14:49:46 -05:00
sora10pls
f0b127e13e Add latest distribution raid data 🦕🔮 2024-04-04 20:02:00 -04:00
Kurt
d222d4bb2e CXD location on hover
CXD is a stored version and does not contain others
2024-04-01 17:28:05 -05:00
Kurt
5f4cf2af29 Better guard against bad gender requests
Can infinite loop gen3/4 generators by requesting from an invalid gender (eg Male Blissey) in Gen5+
2024-03-30 17:44:21 -05:00
Kurt
a2f36760e6 EncounterCriteria: ignore genderless gender values
Shedinja (genderless) was filtering when trying to generate gendered nincada
Also fix method2 reattempt & comments while we're here (comments were copypasted from Unown's method)
2024-03-30 11:28:15 -05:00
Kurt
a71d74b1ea Split gen3 memorycard into subfolder 2024-03-30 01:17:18 -05:00
Kurt
8b039ae786 Rework card 0184 deferral 2024-03-30 01:13:52 -05:00
Kurt
8f60ad546c Extract DEntry reading logic 2024-03-28 23:07:13 -05:00
sora10pls
9888479021 Add latest distribution outbreak data 👶 2024-03-28 20:07:13 -04:00
Kurt
d8689e4074 Update EncounterTrade2.cs 2024-03-28 00:41:23 -05:00
Kurt
69f7413387 Revise Gen8 HOME gift 0/0 scale rolling 2024-03-25 23:12:59 -05:00
Kurt
af228b0fec Re-add gsc rock smash
ede3296c31

also sneak in SV Ride legend having trash bytes/HT data
2024-03-25 22:28:51 -05:00
Kurt
fed6b41950 Update EncounterTrade2.cs 2024-03-25 19:27:23 -05:00
Kurt
45444578ab Handle Gen8 ability tradebacks
HOME 3.1.0 change (Teal Mask)
https://www.smogon.com/forums/threads/pokemon-home-update-adds-ability-tradebacks-to-swsh.3727789/

delete all the handling for 3.0.0 tradeback gating since it's no longer correct.
2024-03-25 17:18:44 -05:00
Kurt
bf476f4de5 Update ChannelJirachi.cs 2024-03-25 12:52:59 -05:00
Kurt
c95d56bfd6 Update 24.03.26 2024-03-25 00:46:21 -05:00
Kurt
2b4ecee899 Rename EggEncounter to IsEgg 2024-03-25 00:39:30 -05:00
Kurt
024bd85cc3 Minor tweaks
make PL6 use memory instead of byte[]
make GP1 use memory instead of byte[]
move IEncounterable properties higher to IEncounterTemplate
2024-03-24 20:12:33 -05:00
Kurt
905c80521e Trade2: hard match on nickname, not OT
Webster (RANDY) Spearow in Italian uses the same OT, with different nickname.
2024-03-24 14:46:23 -05:00
Kurt
c6f12515c6 Flag invalid mystry mew seeds, like channel 2024-03-23 21:35:46 -05:00
Kurt
acdb9f12b3 Defragment PersonalInfo backing arrays
resource fetch grabs the big byte[], just reference off that instead of creating another copy object.
2024-03-23 21:34:11 -05:00
Kurt
2ff2815dbe Add Channel Jirachi menu pattern verification
Sitting on my todo-list for far too long
see pokefinder for similar implementation
PKHeX has a forward & reverse implementation available for 100% documentation purposes :)

The seed indicated in hover previews now matches that of the true Origin seed for the encounter (pre menu & accept dialog), similar to Method H/J/K encounters.

Co-Authored-By: Admiral-Fish <24730718+Admiral-Fish@users.noreply.github.com>
2024-03-22 16:12:56 -05:00
902PM
dbed62b314
Update flags_usum_ja.txt (#4231) 2024-03-22 08:04:47 -06:00
902PM
04dda339e5
Update const_usum_ja.txt (#4232) 2024-03-22 08:04:40 -06:00
902PM
1df1101236
Create const_xy_ja.txt (#4233) 2024-03-22 08:04:33 -06:00
902PM
9c9400050f
Update flags_sm_ja.txt (#4234) 2024-03-22 08:04:28 -06:00
902PM
331e08ff2e
Update flags_xy_ja.txt (#4235) 2024-03-22 08:04:11 -06:00
Kurt
84363eb8eb Next seed if activation fails
ty santacrab for pointing this out
2024-03-21 21:22:56 -05:00
sora10pls
1faef958a7 Add latest distribution raid data 🍄👊 2024-03-21 20:48:31 -04:00
9Bitdo
f0f341f6f2
Add Liko's Sprigatito distribution date (#4228) 2024-03-21 07:57:02 -06:00
abcboy101
21a1d5fe6f
Add PBR Poké Coupons accessor (#4230) 2024-03-21 07:56:28 -06:00
Kurt
c1b98025f7 Fix gen3 HoF clamp, reject invalid sector IDs
Closes #4229
2024-03-21 08:55:48 -05:00
Kurt
205e9b433d Set ability bit, precheck for encounter activation 2024-03-21 00:18:16 -05:00
Kurt
f27326cb2e Update PK5.cs
don't count sinnoh champ in battle ribbon sum
2024-03-20 23:31:17 -05:00
Kurt
1291a40e21 sav4 dex 2024-03-20 23:28:19 -05:00
Kurt
f7900ed8b4 Enhance Text trash editor
#4222
SuggestAppend species dropdown
Indicate trash-to-be-applied when hovering on button
Fix ClickOT in pkmEditor applying English on Japanese Gen3 saves
2024-03-20 09:44:20 -05:00
Kurt
95eb008125 Misc tweaks
no functional change
2024-03-20 09:42:28 -05:00
Eelen
cc7f96951f
Update lang_zh.txt (#4225) 2024-03-19 23:42:36 -06:00
Kurt
59be36294f Re-encrypt forest data when saving
Affected saves should just open and save with the current release and it'll re-encrypt.
2024-03-20 00:42:05 -05:00
Kurt
dca76f50f7 Catch more uninitialized file call stack paths
Closes #4223
2024-03-18 19:05:22 -05:00
abcboy101
8a3a338c0b
Fix encodings for Gen 3/4/5 transfers (#4220)
Adjust Gen 3/4 encodings to be consistent with Gen 5 Unicode encodings
Gen 3 quotation marks are displayed differently based on the game language
Implement how Pal Park handles invalid characters and corrupts certain accented characters
Implement how Poké Transfer handles invalid characters
Use U+25BA BLACK RIGHT-POINTING POINTER, since this character is used as the pointer in menus/etc., rather than as a bullet or generic shape
2024-03-17 22:34:13 -06:00
Kurt
2b6f153831 Don't remove <=g7 move if swsh context 2024-03-17 17:30:04 -05:00
Kurt
9e7046564c Revise naive ability deferral logic for gen9
Closes #4221

HA->1->2 requires a 2-step check
2024-03-17 14:44:54 -05:00
Kurt
ea63d90ac4 Settings: remember last selected tab if reopening 2024-03-17 14:43:25 -05:00
Kurt
ee426e29c5 FolderList: minor clean
deduplicate constructor, obvious functions
2024-03-17 14:13:24 -05:00
Kurt
2259551ace Event Diff: move version check closer to init
Sanity Check method checks the objects that expose the flagwork, which no longer is always a save file.
2024-03-17 13:55:27 -05:00
Kurt
bc7118b493 Add specialized PIDIV generator for gen4 slots
extract gen3's to a static class
2024-03-17 13:54:36 -05:00
Kurt
3dd60890fb Setting: change max recently loaded sav file count 2024-03-17 10:40:36 -05:00
Kurt
c3c7fac86f stadium: grgs psyduck all languages 2024-03-16 20:21:44 -05:00
Kurt
9352301b65 Add special generator for Gen3 unown
Removes incorrect slots ( see 2bf963009f )
Allow Unown of any form through (unspecified in Possible generator)

Adds SlotRange get method for Gen3/4 methods.
2024-03-15 21:21:34 -05:00
Kurt
ba0c4c90d2 Indicate if the exception is from a Plugin
All these silly users reporting issues to the main PKHeX forums when it is outdated plugins breaking; give them a better hint.
2024-03-15 20:54:20 -05:00
Kurt
d43f0dadc4 Fix SAV4 daycare seed set (save export)
oops, just extract the end-data offset to be more obvious
2024-03-14 03:24:03 -05:00
Kurt
d630160665 Fix import of bv4/5 teams
Need to decrypt state
Handle pk5.Nature on import/export, unsure what the other flags are for.
2024-03-14 03:11:11 -05:00
Kurt
9c6f030861 Fix gen4 mystery gift misalignment
indicate the card type in the preview pane
2024-03-14 02:32:20 -05:00
Kurt
2f08a35a5c Re-add Mt Silver Cave headbutt tile types
https://projectpokemon.org/home/forums/topic/64867-pkhex-marks-as-illegal-some-pok%C3%A9mon/#comment-288729
2024-03-14 01:20:56 -05:00
Sakura
b7f31f335f
Add YOASOBI Pawmot Date (#4218) 2024-03-13 09:51:25 -05:00
Kurt
10e108939b Move fallback pressure check to near end 2024-03-13 00:18:58 -05:00
Kurt
1241e6eff6 Double check some bounds checks
still have some issues with gen4 mystery gifts and some groundtile flagging (to be resolved later)
2024-03-13 00:08:10 -05:00
Kurt
7ac5da37b3 Fix check order for Pressure/Hustle/Vital Spirit
what a silly set of conditions for it to matter -- we need to permit matching of boosted slots, then enforce that the boosting is valid for the slot, and disallow any other lead. If it couldn't be boosted, then ignore the slot.
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/?do=findComment&comment=288731
2024-03-12 23:33:47 -05:00
Kurt
5b89b279d1 Update LearnGroupHOME.cs 2024-03-12 08:33:33 -05:00
Eelen
4c950760a3
Update CHS Translations (#4216)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2024-03-12 07:33:54 -05:00
Kurt
a62e169258 Use precompiled rand for honeytree rand level
Add note that No Guard works the same as Illuminate and Arena Trap.
2024-03-12 00:46:52 -05:00
Kurt
819f6009bf Fix some gen4 fish slot check inaccuracies
Pt doesn't do Sticky Hold/Suction Cups right for fishing (not just D/P)
HGSS has a follower boost that is now implemented
lol at my GetSuperRod comparing range wrong for slot4

For HGSS follower boost, don't bother indicating differently even though it's not entirely transparent. We just assume the most permissive case (+50) even though people looking to replicate in-game might not have >= 250 friendship.
2024-03-12 00:03:09 -05:00
Jonathan Herbert
4435f032a5
Rename Obedience_Level To Match C# Naming Standard (#4215)
Change from Obedience_Level  to ObedienceLevel since it was missed when other properties we changed to match C# naming standard.
2024-03-11 20:39:31 -06:00
Jonathan Herbert
8a29320724
Minor Changes To EpochDateTime (#4214)
- Fix Epoch0000DateTime.DisplayValue  having seconds in the output even though they aren't stored
- Introduce RawYear and RawMonth in EpochDateTime to reduce duplicated logic
2024-03-11 14:07:59 -06:00
Kurt
7122c5c3f5 Fix database search returning empty w/ unfiltered
Closes #4211
Also fix box dump not creating folder and Surprise Trade block optionally existing in SV saves.
2024-03-11 13:00:08 -05:00
Kurt
353c00ef67 Fix HOME-sharing rules allowing initial egg moves
Were originally permitted because Generation == 0; now we properly indicate the origin of all Initial/Encounter/Shared moves and use that to remove anything not from the environment. Invalid/Sketch moves are still permitted to be Gen0 and skipped because their verification is a different branch.

ty notflyy (discord)
2024-03-11 12:50:38 -05:00
Kurt
b6739e5547 Fix Mystery Gift edit form save
Closes #4213
Also fixes XY/AO saves not having the button available when they should
2024-03-11 12:47:10 -05:00
Kurt
5d2c20d449 Fix DexNav max level boost
Closes #4204
flute actually applies in this case
2024-03-11 12:45:43 -05:00
Eelen
66ac64bc85
Update CHS Translations (#4212) 2024-03-11 08:35:19 -05:00
Kurt
9127f6548a Handle fixed-moveset missing move cases
Stadium Eevee has less moves than if populated by initial moves; need to use the encounter moves for moveset mons.

Co-Authored-By: ShadowMario3 <36941677+ShadowMario3@users.noreply.github.com>
2024-03-10 23:22:37 -05:00
Kurt
8e32ab5008 Misc tweaks
Gen1 VC mew OT name set
Gen1 Japanese stadium language get
Tab control edge case index redraw
Home Tracker & EC control ordering now grouped by flowlayout panel
2024-03-10 21:28:50 -05:00
Kurt
c432c56ca5 Update 24.03.10 2024-03-10 18:38:47 -05:00
Jonathan Herbert
c651c6f6cd
Deduplicate Gen 6-8 PlayTime Logic (#4208)
Slightly overengineered but a fun experiment to de-duplicate some logic.
2024-03-10 08:50:32 -06:00
BlackShark
25c7b3cc8c
Expose Gen 2 Palette to Block Editor (#4209) 2024-03-10 08:36:25 -06:00
Kurt
564025ae72 Fix ruins of alph unown form check 2024-03-09 23:10:01 -06:00
Kurt
a143c9a2ab Update SAVEditor.cs 2024-03-09 20:28:50 -06:00
Kurt
30d85c95a0 Handle radar slot bypassing / chain shiny 2024-03-09 18:13:09 -06:00
Kurt
90cf6cff84 Fix slot replace indexing for slotNum
see dumper, was giving SlotNumber as absolute index, not the replaced index (keep the original SlotNumber value now)
60668dfc9b
2024-03-09 14:16:26 -06:00
Kurt
6d8504bf82 Misc tweaks for slot4 level check 2024-03-09 13:50:15 -06:00
Jonathan Herbert
78fba23eae
Replace Epoch1900DateTimeValue Duplicate Logic Instances (#4207)
* Replace LastSaved8a With Epoch1900DateTimeValue

Since LastSaved8a basically implemented the same logic, replace it and update Epoch1900DateTimeValue.DisplayValue to not display seconds if they aren't present

* Replace PlayTime8 With PlayTime7b
2024-03-09 00:41:26 -06:00
Kurt
d86469266b Enhance bv5 checksum documentation
add for gen4 so there's no more mystery on that

change span to memory for direct injecting to a bv obj
2024-03-08 21:31:17 -06:00
Kurt
994c063537 Misc tweaks 2024-03-08 21:30:21 -06:00
Jonathan Herbert
0693825f96
Correct SM and USUM GameTime7 Offsets (#4205) 2024-03-08 01:02:15 -06:00
Kurt
6dc785da0c Add gen5 battle video reads 2024-03-08 00:53:35 -06:00
Kurt
df7c25d43f Rename battle video classes, allow bv4 recognition 2024-03-07 00:34:21 -06:00
Kurt
802974a42c Add BV4 reading for SAV4
D/P don't store battle videos, so the SAV4 get will always return null for those games. Still can return null if the extdata blocks aren't present.
2024-03-06 21:02:33 -06:00
Kurt
3e25ed9d32 Show sv surprise trade slots in misc tab 2024-03-05 22:04:13 -06:00
sora10pls
d841a7a4bb Add latest distribution raid data 🐢💧 2024-03-05 19:03:13 -05:00
Kurt
efe0f5e0cf Update WB7Records.cs 2024-03-05 10:04:04 -06:00
Kurt
f32a1ddc7a Misc fixes
event flag editor gen5-7
rs/frlg/dp/hgss enc->pkm version choice
pb7 party stats loading
daycare slot now shows when present
remove unnecessary `GameVersion.Unknown`, use Invalid instead. Might be worth removing Invalid in favor of changing `Any=0` to `None=0`.
2024-03-05 09:42:20 -06:00
Kurt
4e87fd7eca Misc fixes
ty matt & foohyfooh
2024-03-04 23:46:11 -06:00
Kurt
59ad4d749f Add handling for lgpe lumped in Contains/Gen check
ty Oval Lenin (discord)

Co-Authored-By: Ivan <15915901+ivanlonel@users.noreply.github.com>
2024-03-03 23:40:55 -06:00
Jonathan Herbert
40cea0c858
Add A Few Time Editors For LGPE, SwSh and SV (#4201)
- In SAV_Trainer7GG, add Adventure Begin and Last Saved
- In SAV_Trainer8, re-enable Last Saved
- In SAV_Trainer9, add Last Saved
2024-03-03 23:35:11 -06:00
Kurt
fa80dac2ac
Refactoring: Rework saveblock to be Memory<byte> based (#4200) 2024-03-03 23:13:16 -06:00
Lusamine
2b63c4b013 Fix Project Snorlax Mystery Gift date
End date for the promo is Feb 29th, but the code can be redeemed until March 31st.
This also adjusts some of the event comments to be more descriptive.
2024-03-02 19:57:42 -06:00
ptrstr
4f26560233
Make CSV files UTF-8 w/o BOM (#4196) 2024-03-02 13:56:10 -06:00
Kurt
0f936f88f4 Gen3: Fix jump/berry max 9999->99990 2024-03-02 13:53:00 -06:00
BlackShark
378b28bc07
Fixed national magic for FRLG (#4199) 2024-03-02 13:51:28 -06:00
sora10pls
466b5f5ea9 Add latest distribution raid data 🍃🐸 2024-02-27 19:06:24 -05:00
Sakura
4a6eab1d2f
Add PokéCenter Snorlax Date (#4198) 2024-02-27 10:41:49 -06:00
Kurt
aa841aebe9 Bounds check on invalid encounter
Magmortar from Gen3 in Gen4+ somehow requests learnset; just give it species 0.
2024-02-26 00:02:46 -06:00
Kurt
645d65bf3b Update SaveBlockAccessor7b.cs 2024-02-25 23:51:53 -06:00
Kurt
8b5e4e798b Add NSO save handler for Gen1/2 saves
Closes #3921
Not sure if the RTC handling is correct (always 0x20 length?) but at least we have a non-fixed-sized header handled with some leniency for different builds.
2024-02-24 21:53:21 -06:00
Kurt
88569d1107 RBY: Add bool toggle for silph lapras
Closes #4188
2024-02-24 20:14:04 -06:00
Kurt
5334f1ef78 Fix IsMysteryGiftUnlocked checked
Closes #4106
2024-02-24 19:51:42 -06:00
Kurt
276a1952fb GSC: Add setter for daycare occupied/egg available
Closes #3862
2024-02-24 19:47:46 -06:00
Kurt
b435f8c258 Offload gen1/2 event templates to pickles
Thanks @ShadowMario3 for doing the raw data entry for these templates -- see PKHeX.EncounterSlotDumper for the csv -> pkl conversion logic.

Reduces object size by classifying groups of events with specific features, and reduces the explicitness of defining each single encounter.

Hard-code the span of Gen1 VC mew to not need to grab the resource, and add an overload to iterate a single template (skips an array creation).

Co-Authored-By: ShadowMario3 <36941677+ShadowMario3@users.noreply.github.com>
2024-02-24 17:53:46 -06:00
Kurt
8206427d21 Add display of pla static encounter seeds
Show single shiny roll slots for PLA too

Update MiscVerifier.cs
2024-02-23 22:37:37 -06:00
Kurt
43cd9300fb Display raid seed for valid SW/SH raids
Yay leadslot for making this possible
Sure we do a little bit of extra work (checking seed twice) but it's negligible overall.
2024-02-23 20:54:56 -06:00
Kurt
8da8f34896 Use TimeProvider.System instead of manual impl
Introduced in .NET 8; use it.
Remove unnecessary [Serializable] tags (binary/XML serialization are not used), will eventually be obsoleted by c# ;)
2024-02-23 20:37:29 -06:00
Kurt
1feec26d1a Split StringFontUtil
2700 line file too big; split into context-specific font classes.
2024-02-23 20:05:50 -06:00
Kurt
034658b764 Remove extra Memory forcing
Closes #4133
Refactors most of the `Trade` methods
Fix default egg friendship to 120
Fix Version value exposed for Gen4 saves (and others)
2024-02-23 17:01:36 -06:00
Jonathan Herbert
65d8ab025d
Update PlayerGeoLocation7b To Match Changes To SaveBlock Constructor (#4194) 2024-02-23 14:32:20 -06:00
Jonathan Herbert
becf158e2e
Fix PKMEditor Using Old Names For ContestStat Properties (#4195) 2024-02-23 14:32:06 -06:00
abcboy101
32e888d871
Check nicknames/OTs against characters in font (#4146)
* Check nicknames/OTs against characters in font

* Update translations

* Do not show warning for Gen4 and earlier

* Use Gen5/7 font for Gen 3-4/1-2 transfers

* Minor style pref

* Remove font legality checks

* Add missing/update Switch fonts
2024-02-23 13:23:50 -06:00
Kurt
4f568b1497 Add slot-set Count update bypass setting 2024-02-23 13:20:59 -06:00
Jonathan Herbert
4974371100
SaveBlock Constructor Changes (#4191)
* SaveBlock Constructor Changes

- Add primary constructor for SaveBlock with default offset
- Update SaveBlock  subclasses to specify offset in contructor rather than as the constructor body

* Fix MyItem Subclasses Using SaveFile Rather Than Specific Classes
2024-02-23 13:20:24 -06:00
Jonathan Herbert
92c964d6fa
Refactor And Deduplicate Some Switch Games Common Logic (#4187)
- Improve Epoch 1900 classes using similar logic from PlayTime7b.
- Move Time Classes into non Gen specific folder since it appears the logic is shared across a few of them.
- Use Epoch1900DateTimeValue for LastSaved in PlayTime7b since the logic is the same.
- Remove TeamIndexes9 since it is a duplicate of TeamIndexes9. Use the similar pattern like Box8 where it is reused in multiple locations.
- Add BlueberryClubRoom9 to ISaveBlock9Main since it wasn't added when the class was introduced.
- Simplify RaidSevenStar9 creation since GetBlockSafe does basically what the if-else block does.
- Change SAV8SWSH Base Raid to RaidGalar to match the same way the SAV9SV does for its Base Raid.
2024-02-23 13:19:17 -06:00
Jonathan Herbert
cf274bba70
Add LGPE Adventure Begin Date (#4189) 2024-02-23 13:19:01 -06:00
Kurt
95fbf66a6e
Refactor: Gen3/4 Lead Encounters, property fixing (#4193)
In addition to the Method 1 (and other sibling PIDIV types) correlation, an encounter can only be triggered if the calls prior land on the Method {1} seed. The RNG community has dubbed these patterns as "Method J" (D/P/Pt), "Method K" (HG/SS), and "Method H" (Gen3, coined by yours truly). The basic gist of these is that they are pre-requisites, like the Shadow locks of Colosseum/XD. 

Rename/re-type a bunch of properties to get the codebase more in line with correct property names & more obvious underlying types.
2024-02-22 21:20:54 -06:00
sora10pls
eb673dcbc5 Add latest distribution raid data 🔥🪨🐧 2024-02-15 19:03:30 -05:00
Jonathan Herbert
247340dff4
Handle SwSh and SV Team Locks (#4186) 2024-02-15 08:33:25 -06:00
Jonathan Herbert
704a7036ea
Determine Scarlet and Violet Save Time (#4183) 2024-02-14 22:56:41 -06:00
Kurt
a93a626dad Add dialog message for empty/FF save files
Users incapable of saving in-game and using save states instead, or emulators not flushing save data on in-game save.
2024-02-11 21:55:37 -06:00
sora10pls
a571c2a3cb Add latest distribution raid data 💘🐟 2024-02-11 19:02:08 -05:00
Kurt
069411d42e Modulo time spent for large gaps
Taking too many seconds to beat the storyline -> overflow on ticks. Since we already shave off the days portion, use the modulo to remove the day portion from the time portion.

Closes #4184
2024-02-10 00:25:22 -06:00
Lusamine
da83f1005a Label SV Surprise Trade storage block 2024-02-09 23:43:43 -06:00
Kurt
10c5adadb8 Misc tweaks
Add more xmldoc
Move files/functions to better spot
Less allocation on hover glow
fix honeytree dppt group/slot/shake rand call (ty real.96)
2024-02-03 14:11:17 -06:00
Sakura
3eb6ff2c9e
Add latest distribution raid data🐧 (#4181) 2024-02-02 19:49:37 -05:00
Kurt
6e9ffe6390 lgpe static encounters: Check specified IVs
https://discord.com/channels/401014193211441153/679178558597496872/1202059598941671434
2024-01-30 19:16:17 -08:00
Kurt
aba7c800b3 Add LCRNG distance calculating method
Solution known for over a decade, finally reminded myself that it'd be nice to have this available. Probably will use this for displaying Method J/K/H frame info when that branch is more mature.
2024-01-30 19:14:04 -08:00
Kurt
f99e4e54f3 Batch Editor: Combine simple & complex EC rand
Just pipe everything to the complicated EC pathway and allow it to select for biases. Swap input preferences to retain a proper EC for dudun/maus.
Closes #4179
2024-01-29 17:41:12 -08:00
Kurt
3618caa4e3 Retain last batch editor instructions for session
Closes #4180
2024-01-29 17:22:06 -08:00
Kurt
48b0f0c6a3 Pt: Swap roamer indexes (moltres, articuno)
f3604018fe/include/roaming_pokemon.h (L10-L12)
Closes #4176
2024-01-29 16:14:17 -08:00
Kurt
48646fd001 Batch edit: return error code on exception
#4177
2024-01-29 16:10:25 -08:00
Kurt
5706fb2fb5 Gen6: Add mach & acro to usable key item memories 2024-01-25 18:56:57 -08:00
Kurt
ff0030e598 SV dex: Remove special handling for Alcremie 2024-01-25 18:47:21 -08:00
sora10pls
72cf3c5fb5 Add latest distribution raid/outbreak data 👻🤖🥇 2024-01-25 19:04:59 -05:00
Kurt
ab5fd54079 Narrow Dynamax Level compare for swsh den seeds
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/?do=findComment&comment=287889

Adjust Showdown import to only set these values to PK8.
2024-01-22 19:06:38 -08:00
Kurt
26e4bf0a6a Add XY friend safari sawsbuck form bypass too
See previous commit.
AO only has Deerling, so remove Sawsbuck from the Species compare.
2024-01-22 18:58:22 -08:00
Kurt
6d2de333bf Add formarg deferral for wild annihilape/kingambit
Closes #4167
Also add form bypass for Gen6 Deerling slot that changed form in SV.
2024-01-22 18:51:55 -08:00
Kurt
560320e2cc Fix gen4 encoding of ? 2024-01-21 23:44:12 -08:00
Jonathan Herbert
3b1f8b225f
Gen 5 Misc and Gen 9 Trainer UI Edits (#4169)
Gen 5 Misc Edits
- Fix duplicate unlock all musical props button caused by me

Gen 9 Trainer Editor
- Move Gender next to Trainer Name
- Group Blueberry Quests on Blueberry Tab
- Move Indigo Disk related unlock buttons to Blueberry Tab
- Disable Unlock All Buttons after operations
2024-01-21 14:22:13 -08:00
Eelen
f06ba907b0
Update CHS Translations (#4166)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2024-01-21 13:02:13 -06:00
Kurt
9aa31e8563 Fix phione ball breed permit (back to poke only) 2024-01-19 06:28:51 -08:00
Jonathan Herbert
a00a6a7308
Label Scarlet and Violet Team Names (#4164) 2024-01-17 20:49:38 -08:00
Kurt
c617aa206d Misc tweaks
Re-dump safari slots for HGSS; the repackaged narc accidentally shuffled files so the wrong water tables were output.
Add error log output on reporting exception fail first to always dump an error log file. Don't trust the popup/form.
Permit TID/OT duplication for in-game trades/N's pokemon.
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/?do=findComment&comment=287395
2024-01-16 21:43:10 -08:00
Kurt
513a0d6b7a Update EncounterSlot9.cs 2024-01-16 07:10:57 -08:00
Kurt
2aec7572f8 Fix club board fetch for pre-indigo saves 2024-01-15 23:22:00 -08:00
Kurt
3ea44b5eb0 Allow Gourmand mark on Eggs
Flag it if it's affixed though.
2024-01-15 22:50:37 -08:00
Jonathan Herbert
5a5ce7c273
Scarlet and Violet Blueberry Clubroom Support Board + Style (#4162)
* Scarlet and Violet Blueberry Clubroom Support Board + Style

* Sync to master

* Fix BaseballClub2TwirlingNinja Flags

---------

Co-authored-by: Kurt <kaphotics@gmail.com>
2024-01-15 19:29:39 -08:00
Sakura
fe1a5750dc
Add コロコロ Pokémon Date (#4163) 2024-01-15 18:50:30 -08:00
Jonathan Herbert
690de20687
Add Support For SV Throw Style (#4153)
* Add Support For SV Throw Style
* Change Support Board To Club Room
The block contains more than just the support board so correcting name.
2024-01-15 18:50:17 -08:00
Kurt
13f118fffd Reduce wc3 egg strictness for match (fateful)
Closes #4161
2024-01-15 18:49:24 -08:00
Kurt
00c0aefe00 Add simple alolan evo trim for HOME+ permit
Closes #4159
2024-01-13 21:08:45 -08:00
Kurt
08104179b7 Update 24.01.12 2024-01-12 22:37:01 -08:00
Kurt
f2209ec7fd Add better handling for specific GO transfers
No formarg or bad EC evolution peek -> search for higher species rather than from the bottom.
2024-01-12 18:58:23 -08:00
Kurt
c591e9b809 Misc tweaks
Fix ug editor again
b4aeb1ac7c

make framegenerator info a struct (8 bytes); will be rewritten later anyway
2024-01-11 20:38:11 -08:00
sora10pls
e36035ebc7 Update Pecharunt sprite per battle data assets
It's smaller than the custom resized one I made from SV assets, but the compression on SV textures is atrocious. Can't win em all /:
2024-01-11 19:41:06 -05:00
sora10pls
b16e1e8746 Add latest distribution raid data 🐔🪽 2024-01-11 19:02:15 -05:00
Kurt
5272a32d40 re-add safari super rod slots
oops, missed these in the rip and no unit tests caught it :)
2024-01-11 09:30:25 -08:00
sora10pls
a39712e5cd Add Mochi Mayhem related content 2024-01-11 09:16:04 -05:00
Kurt
354721e20d Rip hgss safari slots with computed slot numbers
HGSS pickles lookin' pretty hefty
ty admiral_fish & real96 for their impl in pokefinder
2024-01-10 20:35:22 -08:00
Kurt
92a8fc9096 Update SAV_Misc4.cs
https://projectpokemon.org/home/forums/topic/64538-pkhex-unhandled-exception-on-opening-dpp-misc-edits/
2024-01-09 23:54:47 -08:00
Kurt
39c4463437 Handle two UI misbehaviors
Hover tooltip thread was throwing exceptions (silently, cuz thread); program starts with empty box slots, hovering between them would never show -> can't hide a never-shown (no handle) form
Fix quirk with stat ordering and visibility toggling
2024-01-09 23:49:00 -08:00
Kurt
b4aeb1ac7c Defensively load gen4 underground inventory/stats
https://projectpokemon.org/home/forums/topic/64536-pokemon-platinum-underground-treasure-bag-corruption/
2024-01-09 21:15:14 -08:00
Kurt
0ca20ff096 Reimplement applied Markings individual get/set
Closes #4156
Extracts to an interface, varied implementations in the appropriate PKM classes. No longer an abstract property inherited from the base `PKM` class.
2024-01-09 20:53:31 -08:00
Kurt
c66fc2f3bf Add gen4 headbutt slot numbers
All were zero, update dumper.
Extract some logic from other parts of the codebase
Fix wyrdeer level check if originated from GO
2024-01-09 19:07:32 -08:00
Kurt
671c3564ee Echoes of trash-test
Remove glyph remap for Switch gender symbols (none actually)
Might be wise to convert StringConverter to singletons...
2024-01-08 21:55:07 -08:00
Kurt
f80c2820f9 Misc tweaks
Local event db's don't need to absorb the builtin db.
More aggressive generator filtering for fuzzed PB7's from GO (can't fake a GO8 in PB7)
Fix splitbreed relearn suggestion (chain is reversed for Origin, don't bother truncating or anything)
Only check for filtered VC ot's if transferred (not still gen1/2 format)
Remove unnecessary xmldoc, add more xmldoc
2024-01-08 20:01:19 -08:00
Kurt
96aed1e3c5 Set default version ID for gen3 pkm templates
ur welcome matt
2024-01-08 19:56:51 -08:00
Kurt
d7473db6af Add plugin stub for notifying GUI language change 2024-01-08 19:56:04 -08:00
abcboy101
3ba2e8e376
Gen 1/2 VC filtered OT name legality (#4155) 2024-01-08 12:34:44 -08:00
abcboy101
01131e59f0
Modify G1/G2 character mappings (#4154)
### Treat 'ヘ' as katakana
- For consistency with ベ/ペ/リ which are treated as katakana here and in StringConverter12Transporter.cs, map 0xCD to katakana ヘ instead of hiragana へ.
- Allow input of hiragana べ/ぺ/へ/り, mapping them to katakana ベ/ペ/ヘ/リ. Previously, they would be treated as invalid and mapped to the string terminator.

### Swap mappings for 0xE8/0xF2
- Previously, 0xE8 was mapped to U+002E `.` FULL STOP, while 0xF2 was mapped to the special character U+2024 `․` ONE DOT LEADER. Since 0xF2 is used in user input while 0xE8 is only used in `MR.MIME`, swap these mappings so that normal keyboard input maps to the user-enterable character.
- Modifies SpeciesName.cs to produce U+2024 for Mr. Mime in G1/G2.
2024-01-07 23:27:03 -08:00
Kurt
018694b84f One less allocation
compiler can infer correct string concat size both ways
2024-01-07 13:13:21 -08:00
Eelen
9da0955f10
Update lang_zh.txt (#4151)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2024-01-06 16:45:06 -08:00
Samuel Magnan
dbb4df6ddc
Add getter/setter for Sav Gen3/4 Trendy Word, Trainer Hill, Berry Blender, Accessory, Backdrop, Wallpaper (#4152) 2024-01-06 16:44:58 -08:00
Kurt
608f7ebe12 Update Roaming8bRNG.cs 2024-01-05 09:47:53 -08:00
Kurt
a4ac8240b8 Allow replacing VC1/2 default transfer version
Exposed via settings
Conversion settings relocated from Advanced; `AllowIncompatibleConversion` will need to be re-set if you want to deviate from official rules.
2024-01-04 19:57:01 -08:00
sora10pls
0777b9fbf8 Add smoother resized type icons
Not as sharp as before, but this doesn't massively distort some type icons like Normal or Psychic
2024-01-04 16:41:38 -05:00
sora10pls
5ef8cfaf2e Update SAV_Database.cs 2024-01-04 11:08:58 -05:00
sora10pls
5bf43cfc19 Master Rank Ribbon: Regulation F
Also fix Roaring Moon/Iron Valiant WC9 minimum start date (please verify all time zones!)
2024-01-04 10:47:49 -05:00
Kurt
c866bd7044 Extract GetEXP w/ Table
Less magic -1
2024-01-04 01:19:38 -08:00
Kurt
528d927a85 Handle self-form pointer
Logic is much more clear now.
2024-01-04 00:00:34 -08:00
Kurt
b15211ce1b Widen trashbyte gui
special characters table present causes a scroll bar which wrapped controls; make wider to not wrap, set wrap manually.
2024-01-03 23:46:34 -08:00
Kurt
8fa951bcd7 Refactoring
Catch_Rate => CatchRate
Make Location* classes public
Extract a few methods, make public
Merge EncounterUtil & EncounterUtil1
add xmldoc
add missing deferral for Nest8 templates
improve binlinker span fetch to a single read (-1)
2024-01-03 23:06:09 -08:00
Kurt
cca9d55013 Swap stat display (Spe+Special) for Gen1 games
Closes #4014
Show Special as "Special" instead of "SpC" because I feel it's warranted only in this case.
2024-01-02 16:26:45 -08:00
Kurt
e6ad7c12cd Wipe transparent pixels in Silvally form sprites
Closes #4103
```
	var pixels = System.Runtime.InteropServices.MemoryMarshal.Cast<byte, uint>(data);
	foreach (ref var x in pixels)
	{
		bool isTransparent = (x >> 24) == 0;
		if (isTransparent)
			x = 0; // ensure rgb is zero too
	}
	ImageUtil.GetBitmap(data, w, h).Save(path);
```
2024-01-02 15:47:06 -08:00
Kurt
424cbcff21 Misc tweaks
ShowdownSet: Fix indentation, use explicit const
ItemStorage8BDSP: Rename GetAll->GetAllHeld to match others
EncounterLearn: Guard against >4 length enumerables, use explicit versions for S/V
EggMoves: Read u16[] directly rather than manually
SaveFinder: simplify expression
SAV_Database: extract func
SAV_Encounters: use RoM to match Moveset generator
2024-01-02 15:45:35 -08:00
Kurt
d56eb0e3c4 Rely on deferral ability check instead of exact
Closes #4144
2023-12-31 13:13:51 -08:00
Kurt
1b176eec53 Only ChangeType if enum is defined
Allows setting 99 (Stellar) to TeraTypeOverride now
2023-12-31 11:03:04 -08:00
Kurt
c021f893f8 Inject rule bypass logic for SV's level100 lvlup
Closes #4143
2023-12-31 11:02:23 -08:00
Kurt
9d83ce1855 Auto-affix might mark for 7star enc->pkm
Missed this encounter type when changing the behaviors for others (see 5316ad6b37 )
2023-12-30 20:26:47 -08:00
Kurt
d25fab0e87 Change box dump to use a form
Closes #4122
2023-12-30 14:36:33 -08:00
Kurt
a24e6e9cef Misc tweaks
Add notransfer for SV-ride legend
Replace dummy '0' in gen3 chartable to match Bulbapedia -- inaccessible char for entry anyways, and the byte never fetched via IndexOf due to the previous occurrence of '0'.
Enhance file namer interface to tag with a display name
2023-12-30 11:41:45 -08:00
Kurt
581d5158dc Pass ribbon temp struct byref
Quicker than creating defensive copies for each Parse call. Do the same for IV-set passing.
Not worth for binlinker as it's never passed multiple times / deref'd often.
2023-12-30 11:40:10 -08:00
Sakura
581f0e2e1b
Add 윈터페스타 Baxcalibur Date (#4140) 2023-12-29 23:30:38 -08:00
Kurt
d3a48968e0 Revise runtime localization language setter
Closes #4138
ty @fattard
2023-12-29 10:17:50 -08:00
Kurt
9897630b08 Fix off-by-1 for Hidden Power hover preview card 2023-12-29 10:07:37 -08:00
Kurt
50209c4f0d Misc tweaks
Fix ranch save load (index out of range on party stat fetch due to bad truncation)
Folder Browser: Reapply filter if active on column sort
Fix inverted VC1/2 cross-check, add missing derived type and ignore Generation==0 (empty/invalid) moves.
2023-12-28 22:39:42 -08:00
rcyggdra
6b1e967a74
Update text_Forms_zh.txt (#4137) 2023-12-28 22:35:05 -08:00
Lugiad
4ce354915d
Update lang_fr.txt (#4135) 2023-12-28 16:22:07 -08:00
Kurt
f7a888cc57 Extract some logic to SAV4
Closes #4128
I don't want to decipher to manual interactions to the Battle Frontier structures now. Prints were just work values, and fly flags were event flags.
2023-12-28 00:11:56 -08:00
Kurt
61df1981ce Include Ability in fixed pkl, beldum raid moves
Encode beldum's raid moves manually since it's the only one with empty moves (default -> learnset fetch). See pkNX for associated workarounds in pickle build.
2023-12-27 00:29:16 -08:00
Kurt
572b5e98e0 g9 raid beldum moves, g9 shiny trades, g7 kchart
Beldum: Default moves
Gen9 trades were defaulting to Random, not Never
KChart: >= instead of >
2023-12-26 22:30:40 -08:00
sora10pls
807b9031b1 Add more save block labels, Necrozma fusion move handling 2023-12-26 09:36:13 -05:00
sora10pls
007aed07ae Add latest distribution outbreak data 2023-12-24 19:05:00 -05:00
Kurt
92a3f71033 Update 23.12.22 2023-12-21 19:41:54 -08:00
Kurt
5316ad6b37 Set affixed for encounters w/ Gen9 ribbon/mark
Matches S/V 3.0.0 behavior, better to match
2023-12-21 19:04:58 -08:00
Kurt
6d0b4f77e4 Fix Gen2 quick hidden power calc 2023-12-21 19:04:08 -08:00
Kurt
740da4edde Update FormArgumentVerifier.cs 2023-12-21 18:01:03 -08:00
Kurt
35e46c4c4b Add mark deferral for partial match TimeOfDay 2023-12-21 17:42:11 -08:00
Kurt
9f1ad9f6fc Update EncounterSlot9.cs 2023-12-21 16:50:14 -08:00
Kurt
2a13874aee Rewrite evo move double-check
If (evolved into {species}), must have known {move}
I think Swinub->Mamoswine and Mankey->Annihilape might have evaded the check prior; was the only other 3-stage evo (now +Hydrapple).
2023-12-21 16:20:01 -08:00
Kurt
245f267b03 4.5 surskit quirk
int.TryParse no longer correct; `4.5` is a valid `4` per the game filtering behavior.
Surskit: `1,4.5,7`
2023-12-21 16:06:49 -08:00
sora10pls
52d55101d4 Add latest distribution raid/outbreak data 🐧🤖 2023-12-21 19:02:58 -05:00
Kurt
42f809b216 Update EncounterSlot9.cs 2023-12-21 15:39:50 -08:00
Kurt
6e3598d87b Recompute BDSP allowed hatch location list
Dunno what goofiness caused the original bad table.
Took the original hashsets from git and rewrote the table gen, now we have what should be correct.
2023-12-21 15:29:49 -08:00
Kurt
a45215b00d Update SpeciesCategory.cs
#4126
2023-12-21 12:31:10 -08:00
Kurt
7759596ffd Correct minior out-of-battle form 2023-12-21 11:31:37 -08:00
Jonathan Herbert
24a5b05fcb
Handle Seven Star Raids Change From Teal Mask Update (#4119)
* Handle Seven Star Raids Change From Teal Mask Update

* Refer To RaidSevenStarCaptured9.CountAll Rather Than Recalculate It

* Fix Wrong SevenStarRaid Size Variable Referenced

While they are the same value correcting the mistake so that there isn't confusion while looking at the code.
2023-12-21 10:51:09 -08:00
Jonathan Herbert
3ce726087c
Fix Language Files Not Matching DLC Raid Buttons (#4125)
* Fix Language Files Matching DLC Raid Buttons

Fix translation issue caused by PR #4112

* Update lang_es.txt
2023-12-21 10:50:51 -08:00
Kurt
d4eccd92ca Update raid minior-0, fixed spawns (stellar/under) 2023-12-20 23:42:20 -08:00
Kurt
2fe0f24301 Fix broken unit tests (location, item, evo order) 2023-12-20 14:40:13 -08:00
Kurt
fb87e9645a Update IHyperTrain.cs 2023-12-20 14:20:37 -08:00
Kurt
5da1ae4849 Handle Gold Bottle Cap 31IV bugfix
HOME fixes anything with 31IVs flagged. S/V no longer in error. Would be an incredible headache to detect "has visited S/V prior to 3.0.0" so just flag it.
It's up to the checker to know about this.
2023-12-20 11:29:50 -08:00
Kurt
e885a7d4f4 Play chatter without temp file
Direct from a MemoryStream, no need for temp file
2023-12-20 08:58:40 -08:00
Jonathan Herbert
54d3f377e9
Make Raid Editors Edit Copies Rather Than Origin Save's Data (#4118)
Use an enum to know which raids to use rather than passing the specific raids so that the save changes copy back operation only happens when a user saves modifications and thus won't flag the save as modified if nothing was changed.
2023-12-19 20:53:10 -08:00
Samuel Magnan
90d000d640
Add villa furniture getter/setter for Platinum (#4120)
* Add VillaFurniture to SAV4Pt
2023-12-19 20:50:32 -08:00
sora10pls
41c830fa2c Add BCAT Milcery outbreaks 2023-12-19 22:34:42 -05:00
Kurt
a89b13027b Update gen9 sketch disallow 2023-12-18 20:27:57 -08:00
Jonathan Herbert
80faf97425
Use Explicit Types Instead Of SaveFile For Forms (#4116) 2023-12-18 20:24:24 -08:00
Jonathan Herbert
53bbebae6a
Consolidate DLC Raid Buttons (#4112)
Given that the raid button is used for both games, consolidate the buttons for DLC raids.
2023-12-18 20:24:09 -08:00
Eelen
5ad7a31171
Update CHS Translations (#4117)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2023-12-18 20:23:58 -08:00
Kurt
10b158f82b Update EncounterTrade9.cs
Update EncounterTrade9.cs
2023-12-18 10:42:22 -08:00
Kurt
cbc63ef0a0 Handle non-nicknamed partner trades & weight/scale 2023-12-18 10:19:47 -08:00
Kurt
e371e87f1b Restrict cavern & labyrinth to inside 2023-12-18 08:03:50 -08:00
Kurt
9c0e9c31da Add weather marks, rebuild fixed encs
Still a few stragglers, but better overall.
2023-12-18 08:00:21 -08:00
sora10pls
09590a46b4 Update Encounters9.cs 2023-12-18 10:47:05 -05:00
Eelen
6f815c975c
Update CHS Translations (#4115)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2023-12-18 01:27:17 -08:00
Kurt
d95f424c62 Add another sav size 2023-12-18 00:47:53 -08:00
Kurt
f2419db092 Update PersonalInfo9SV.cs 2023-12-17 21:14:46 -08:00
Kurt
61d958e07a Fix blueberry raids & stellar showdown parse 2023-12-17 19:45:13 -08:00
Kurt
b8d370b661 Fix new tm bitflags, torque move check
ty roc for info
2023-12-17 17:51:55 -08:00
Kurt
c892225a55 Oops 2023-12-17 17:26:38 -08:00
Kurt
10cae1316e Update 23.12.18 2023-12-17 17:06:39 -08:00
Kurt
01c82e472e
Add support for Indigo Disk (#4111) 2023-12-17 16:41:15 -08:00
Sakura
4f56aa27f7
Update WC9 Dates (#4102)
* Add Paldea Gimmighoul Date

* Add コロコロ Roaring Moon and Iron Valiant Date

* Update
2023-12-17 16:39:26 -08:00
abcboy101
69cd3455be
Add Chatter Editor (#4101)
* Add Chatter accessors

* Add Chatter Editor

* Update translations
2023-12-17 16:39:15 -08:00
Eelen
30f3354b94
Update CHS Translations (#4100)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2023-12-17 16:39:03 -08:00
Jonathan Herbert
07cd05d1c1
Fix Some Missed Conversions To Collection Expressions (#4096) 2023-12-17 16:38:53 -08:00
Kurt
59e409bbd7 Update pk3.HeldMailID when not holding mail
https://projectpokemon.org/home/forums/topic/64361-bugs-when-editing-g3-japanese-version-pokemon/?do=findComment&comment=287038
2023-12-17 08:57:56 -08:00
Kurt
0769300803 Handle language-specific fateful bitflag location
Closes #4072
ty @Lusamine for checking japanese and english for complete clarity on this multi-year flip-flop.
historically, we used 0xFB.bit0 until jpn complained, changing it to 0xC9.bit4 to match the pk3 struct. Both were correct, and neither were correct.
2023-12-10 20:58:58 -08:00
Kurt
1fe2b4f29b ArgumentOutOfRangeException
Use the new NET8 API
2023-12-09 15:21:10 -08:00
abcboy101
edf28f74ff
Fix 3DS country/region names (#4095) 2023-12-09 06:22:05 -08:00
Kurt
b3684c58b2 Update EncounterDist9.cs 2023-12-08 17:30:41 -08:00
902PM
52be5b3d40 Update gen2/3 flags 2023-12-07 23:21:53 -08:00
Kurt
09018a4eaa Update EncounterDist9.cs 2023-12-07 23:20:49 -08:00
sora10pls
99b9911aa1 Add latest distribution raid/outbreak data 🌌🌸 2023-12-07 20:17:22 -05:00
Sakura
87efd747c4
Add Darkrai and Shiny Lucario Date (#4094) 2023-12-07 12:57:26 -05:00
Kurt
ef68886554 Add more annotations
Fix typo in swsh block name
2023-12-07 00:07:55 -08:00
902PM
61ab70f6bf
Update const_frlg_ja.txt (#4088)
Translated " const_frlg_ja" into Japanese.
2023-12-06 21:15:39 -08:00
902PM
1085a31a56
Update const_e_ja.txt (#4087)
Translated " const_e_ja" into Japanese.
2023-12-06 21:15:29 -08:00
902PM
50878846bf
Update flags_e_ja.txt (#4086)
Translated " flags_e_ja" into Japanese.
2023-12-06 21:15:20 -08:00
902PM
0be8873b89
Update flags_frlg_ja.txt (#4084)
Translated into Japanese.
2023-12-06 18:27:31 -08:00
Jonathan Herbert
d3452deb80
Document Dojo Watt Donation Related Flags and Values (#4085) 2023-12-06 18:27:17 -08:00
Kurt
762a2a0c41 Fix gen4 complete dex operation for female-only
Closes #4047
SetSeen(species) will set the seen flag but leave both bits 0-0; old code would SetSeen and fail to revise it to 1-1. Since we're setting both to their "complete" state, a "complete" state for a single gender is just a "new" registration operation.
2023-12-05 18:51:32 -08:00
Kurt
c09890366b Fix wc no nickname check, gen3 bvid return
Closes #4081
2023-12-04 18:38:05 -08:00
Kurt
61b4cb67b2 Update readmes
#4082
2023-12-03 21:25:44 -08:00
Kurt
d47bb1d297
Update .NET Runtime to .NET 8.0 (#4082)
With the new version of Visual Studio bringing C# 12, we can revise our logic for better readability as well as use new methods/APIs introduced in the .NET 8.0 BCL.
2023-12-03 20:13:20 -08:00
Kurt
a71597f3a8 Handle gen4 manaphy egg gift pid checks correctly
ty lincoln for bringing this to my attention and explaining the possible actions & outcomes

game checks an un-updated ID32 (original recipient, different if traded hatcher), not "oops Ranger is no longer stored"
2023-12-01 22:33:08 -08:00
Momonopopotee
dcb72b0993
Update Gen5 Event Flags (#4079) 2023-11-30 16:40:19 -08:00
Eelen
41a3196161
Update CHS Translations (#4075)
Co-authored-by: Professor Dirty <103500840+wubinwww@users.noreply.github.com>
2023-11-26 11:48:19 -08:00
Jonathan Herbert
39dddf1605
Update Switch Backup Paths With JKSV Path (#4076) 2023-11-26 11:46:42 -08:00
Fábio H. Attard
ef941dc0e6
Naming useful SV blocks (#4060)
* Named a few useful blocks

* Named a few useful blocks

* Renames for consistency as per suggestion

* Withdrawing hidden items blocks for more investigations

* Identification for Hidden Items blocks
2023-11-24 07:50:25 -08:00
Jonathan Herbert
e0fb60cdea
Update Pokétch Unlocked Count (#4073)
Closes #4067
2023-11-24 07:50:04 -08:00
Momonopopotee
4f998e2fe0
Add B2W2 Volcarona Event Flags (#4074)
* Update flags_b2w2_en.txt

* Update flags_b2w2_es.txt

* Update flags_b2w2_ja.txt

* Update flags_b2w2_ko.txt

* Update flags_b2w2_zh.txt

* Update flags_b2w2_zh2.txt
2023-11-24 07:49:40 -08:00
Kurt
2e736a1d4c Allow Colo Heracross lock-in skip
First = no locks; would be duplicate with the original Heracross locks, so just use the First since it's most permissive.

https://projectpokemon.org/home/forums/topic/64263-invalid-encounter-type-pid-mismatch/

ty Johh !
2023-11-23 23:32:06 -08:00
sora10pls
088bc29691 Ow, the edge 2023-11-23 19:06:30 -05:00
Sakura
ef52c390df
Add Team Star Revavroom Date (#4071) 2023-11-22 12:21:29 -05:00
Kurt
48d9368d3d Fix CountTutor3
ty chris
2023-11-21 18:47:14 -08:00
Sakura
684ea363d8
Add Alex Dragapult Date (#4065) 2023-11-18 21:45:30 -05:00
sora10pls
9b8cda39e7 Add Gen5 Musical Prop editor
Closes #4069
Chinese localizations copied from English (was not an available language until Gen7), contributions welcome!

Co-Authored-By: Jonathan Herbert <3344332+foohyfooh@users.noreply.github.com>
2023-11-18 21:41:10 -05:00
sora10pls
2e1e2b74f7 Add B2W2 Medal localizations
Closes #4068
Chinese localizations copied from English (was not an available language until Gen7), contributions welcome!

Fix typo categries -> categories
2023-11-18 21:15:38 -05:00
Fábio H. Attard
002d1d015f
Secondary heuristic to distinguish SAV1 save files between versions RB and YW earlier, if the Starter value was not set yet (#4064) 2023-11-17 10:25:31 -08:00
sora10pls
aec0367085 Add Eevee Day raid/outbreak BCAT data 2023-11-16 19:07:20 -05:00
Jonathan Herbert
876f4f4737
Document Location of Scarlet and Violet Rental Team Codes (#4062) 2023-11-14 19:36:23 -08:00
Kurt
c505b5a49d
Enhanced Slot Hover Preview (#4059)
Adds a new primary Hover Preview tooltip form. Users can change setting to use the old tooltip if they want.

When the user hovers over a slot in their Box / Party, PKHeX displays a tooltip indicating details about the Pokémon. This text tooltip shows the Showdown text (with some localization based on program setting), and includes details about the encounter the legality check matched it to.
2023-11-14 19:36:11 -08:00
sora10pls
d43b78349c Remove unreleased Altering Cave (E) enc slots
See 149c3fec20
2023-11-13 11:30:44 -05:00
Fábio H. Attard
5ede6feb38
SAV1 can implement IEventWorkArray<byte> (#4061) 2023-11-12 23:05:55 -08:00
Kurt
9df8f16ed3 Misc tweaks 2023-11-12 21:11:11 -08:00
Kurt
f0cacef8e2 Use met level if available for Gen2-C enc moves
Closes #4046
gbera allows move relearning via Stadium which hid the issue; old code just used the enc min level instead of actual. If a move was learned between min & actual, was flagged incorrectly.

pass the pkm thru the call chain
2023-11-11 23:02:15 -08:00
Kurt
a01c9ff5c6 Permit Headbutt from Water (Route 47)
See associated dumper commit for unprocessed change:
c06dc6893f

Closes #4058 ty @MaxAkito !
2023-11-11 19:57:56 -08:00
Kurt
40b6c97358 Fix Gen3 min sheen check
referred to wrong const value
https://projectpokemon.org/home/forums/topic/57375-pkhex-new-update-legality-errors-contribution-page/?do=findComment&comment=286554
2023-11-10 19:06:52 -08:00
Kurt
64ed92a566 Minor clean 2023-11-09 22:02:23 -08:00
abcboy101
894ea1d628
Add conversion for Gen 4 saves between KO and JP/INTL formats (#4057)
* Refactor Gen 4 extra blocks

* Replace FetchHallBlock with extra block getter

* Add UI to convert save to/from Korean

* Do not modify uninitialized General/Storage blocks

* Detect invalid extra blocks
2023-11-08 23:33:40 -08:00
Jonathan Herbert
1f6d2de891
Add GUI For XY Roamer (#4056)
* Add GUI For XY Roamer

* Account For Roamer Not Set In XY
2023-11-08 23:33:02 -08:00
Kurt
3df5478d11 Minor tweaks, add Gigantamax info class 2023-11-08 23:32:41 -08:00
Sakura
5ce9619690
Add PokéCenter Pawmi&Charcadet、Korea Bundle Fidough Date (#4052)
Co-authored-by: Joseph11024 <83472295+Joseph11024@users.noreply.github.com>
2023-11-05 14:27:41 -08:00
Kurt
5cf90deaee Use Shiny state in Glow
Closes #4055
Glow the shiny star for all formats.
2023-11-05 14:24:26 -08:00
Kurt
59dc7fb694 Misc tweaks 2023-11-05 14:20:35 -08:00
abcboy101
62df64e6f5
Gen 6/7/8/9 map position/rotation (#4054)
* Add Rotation to Gen 6 map position

* Keep 7 digits of precision for XYZ coordinates

* Scale coordinates in Trainer Data Editor

* Localization for SAV_Trainer.L_R

* Add SWSH map position

* Add LA map position

* Add Rotation to SV map position

* Add GG map position
2023-11-05 12:24:08 -08:00
Jonathan Herbert
9cdb08155e
Document Sword and Shield Swords Of Justice Flags and Values (#4053)
- Rename capture flags to more appropriate name since it is just if they are in the overworld or not.
- Document progress values for each Sword of Justice.
2023-11-05 10:00:53 -08:00
sora10pls
5708310f6f Add latest distribution raid data 🔥👻 2023-11-02 20:03:32 -04:00
Kurt
bced546c63
Memoize Ball Breeding permission tables (#4050)
O(1) lookup for arbitrary species, some edge handling rules for specific game islands/forms. With HOME, there's only 3 islands of permissions. No allocation besides the singletons which aren't really necessary.

No longer need to peek within multiple hashsets, just fetch the "is possible" bit from the species listing and check if the bit is set.

Will be fun if ball shell swaps are added 🤞, get to set all bits for anything that can enter game with that feature (if ever added).

Add unit test for gen67 no-patch exclusion
2023-11-02 15:55:26 -07:00
abcboy101
8e28ca8cf4
Fix Gen 7 map position (#4049)
* Prevent UpdateOverworldCoordinates from corrupting FieldMoveModelSave

* Fix Gen 7 player coordinates

* Fix Gen 7 player rotation
2023-11-02 15:32:45 -07:00
Kurt
e0cf4447ff Explicitly handle mark8 presence (wc9/static9)
Move some files around
WC9 fidough gift sets Classic AND Uncommon, and it doesn't set the lowest ribbon indexes. Nice GUI.
2023-11-01 19:19:50 -07:00
Kurt
be574948db Add more xmldoc, conditional flawless IVs
Rerolling IVs now rerolls correctly for SOS hidden ability encounters.
2023-10-29 20:27:42 -07:00
Kurt
65bc027f22 Improve perf of dummied move check
O(1) of a not-allocated array is faster than O(1) of an allocated HashSet.
2023-10-29 20:25:23 -07:00
Kurt
e448679f4a Minor clean
No alloc EncounterEgg relearn moves (use span instead)
Move HoneyTreeUtil out of root Saves folder
Minor readability tweaks
2023-10-28 22:07:58 -07:00
Kurt
06c36130c5 Add IFixedIVSet; better reroll IVs for fixed sets 2023-10-28 22:02:12 -07:00
Kurt
9cc36578c5 Add GameConsole enum and EncounterDate pivot
Can now fetch a valid date for a context
2023-10-28 22:01:26 -07:00
Kurt
b088a29c88 Gen4 Pt: remove invalid Korean event flag rows 2023-10-28 22:00:14 -07:00
Kokaikokai
a351672721
Add one more SV DLC1 save size (#4045)
Signed-off-by: Kokaikokai <fianity.gani@gmail.com>
2023-10-28 11:27:32 -07:00
Samuel Magnan
be576be88b Add Crystal Mystery Gift item in Simple Editor (#3983)
For Pokémon Crystal international release, add the option to edit the Mystery Gift item in the Simple Editor.
2023-10-27 21:19:13 -07:00
Samuel Magnan
fda815c3e5
Gen2: Add MysteryGiftIsUnlocked flag in Simple Editor (#4007)
The flag indicates whether the option "Mystery Gift" has been unlocked and it is displayed in the start menu.
Note: It is unlocked after talking with the little girl on the fifth floor of the Goldenrod Department Store.
2023-10-27 21:09:15 -07:00
Jonathan Herbert
0015beb8d8
Add Accessors for Black 2 And White 2 Medals (#4043)
Thanks @suloku as the logic this uses comes from their BW_tool.
2023-10-27 21:02:04 -07:00
Jonathan Herbert
fd1f00e1a6
Handle Sun and Moon Legendary At Altar Flags Correctly (#4044)
According to the research by IgnisSpace shown on https://projectpokemon.org/home/forums/topic/64103-sm-battle-flags-and-others-misc-flags/ , Solgaleo and Lunala have different flags for if they are at the Altar of the Sunne/Moone.
2023-10-27 21:01:27 -07:00
Kurt
5c7ce2c0a6 Misc tweaks
Fix g12 str set (jp/en was swapped, oops)
add more xmldoc
minor readability/short circuiting
2023-10-27 21:01:11 -07:00
sora10pls
7fd1c9da1e Add Halloween raid/outbreak data 2023-10-26 21:12:14 -04:00
Kurt
16aba45371 EncDB: Fix colo starter yield
Improve enc->ck3 creation, inline the shiny/gender reroll logic instead of trying again outside the loop.
Rewrite the pidiv logic to be more obvious

colo starters now show up in encdb list
2023-10-26 01:07:20 -07:00
Kurt
325f75e3d3 Gen12 strings: span instead of runtime dictionary
More performant byte->str performance (no longer needing to hash byte and fetch bucket), unmeasured str->byte performance, but it is the same implementation as Gen3 which is not a bottleneck.
Reduces the dll size by 80KB, and RAM usage by an unmeasured (likely similar amount). Better startup speeds since multiple dictionaries do not need to be allocated and created.

Moves Gen1 trainer names (0x5D) to Transporter class, and remove Korean entry (not legally obtainable since there's only Gen2 Korean games and Gen2 Korean VC cannot trade with international).

Hard verify Gen1 trainer name for language, since 0x5D is the ROM transfer language. Nickname can still be from any of the connected games.

This refactor makes it easier to use a different charmap for byte<->str for Boxes (see #4027) and the language differences. We have a master table that "works" for all text name entry, but localizations differ for some glyphs accessible in the box naming UI.
2023-10-25 16:34:11 -07:00
Zazsona
1816aefc25
Ranch level indexing fixes and general API usability improvements (#4041) 2023-10-20 18:28:43 -07:00
Lusamine
8d409be8ba Fix translation scrape for TechRecordEditor 2023-10-15 10:35:01 -05:00
Lusamine
7efa575eb0 Update translateables 2023-10-15 10:30:46 -05:00
Eelen
90039f62b6
Update lang_zh.txt (#4037) 2023-10-14 23:42:13 -05:00
Kurt
da27814504 Minor tweaks
I quite like the DeSmuME footer check simplification.
2023-10-14 19:28:46 -07:00
Kurt
738c51d596 Save/Open file dialog init
Initialize outside of object initializer, solves warnings if any property sets throw exceptions
2023-10-14 19:26:56 -07:00
Kurt
0da8c33c52 Use byte span for BinLinker check
Need to change ConstantExpected to Length when NET8 comes out next month
2023-10-14 19:25:39 -07:00
Kurt
b3bbc044ca Misc jp Gen1/2 vc fixes
Fix iterator jump
Allow bu dragonair encounter -- should probably rip out all the old catchrate compare stuff anyway.
2023-10-13 18:39:25 -07:00
Kai
6978db5e9a
Add Trixie Mimikyu Eligible Date Range (#4035) 2023-10-13 19:28:11 -04:00
Jonathan Herbert
63b8257315
Fix Typo In 23.10.11 Changelog (#4034) 2023-10-11 22:37:33 -07:00
Kurt
4877c63750 Update 23.10.11
Add sizes for 2.0.2
Add crossover Unown-C location for PLA - Closes #4031
2023-10-11 20:12:51 -07:00
Kurt
0491ab2372 SAV3E: Max Item ID +2
Closes #4033
2023-10-11 19:30:26 -07:00
Kurt
b6a42d414b RNG: Move files & extract some logic
Rearrange some folder organization to extract some common/reusable logic for dealing with RNG operations for specific gens/games.
2023-10-11 19:28:51 -07:00
Kurt
40ce70b9e8 Change OT name of test cases
Used to be `KKK` but now that trips a more modern wordfilter.
2023-10-11 19:27:31 -07:00
BlackShark
bf8a5451ed
Updated badwords.txt (#4032)
* Updated badwords.txt

* More clean up
2023-10-10 15:18:45 -07:00
Kurt
abcaaa44cd Extract pokewalker logic from template ctor
Actually searching (instead of brute-forcing) for a spread will forever be haunting.

Add to Legality Check matching with vague partial match
2023-10-07 23:14:34 -07:00
Jonathan Herbert
176d0d670a
Enable Game Start Date Editor For Sword and Shield (#4029) 2023-10-07 10:20:54 -07:00
Hendi48
1c097598e7
HGSS: Add support for editing Pokeathlon Points (#4030) 2023-10-07 08:57:38 -07:00
Kurt
707898d4e2 Add Pokewalker IV validation methods
Not yet hooked into the legality analysis (MethodFinder doesn't know about encounter template info).
2023-10-07 00:00:36 -07:00
Kurt
fac682bcad Update WordFilter.cs
Reduces startup alloc by 24KB if we don't allocate an array to store all regex strings
kinda small but ez free
2023-10-06 23:58:13 -07:00
sora10pls
bb5cc7cff1 Add latest distribution raid data 🦉🥋 2023-10-05 20:03:16 -04:00
Lusamine
e64205e504 Fix unsafe camera config block find
Closes #4026
2023-10-05 10:11:34 -05:00
Kurt
c23bae5180 Add Distribution Outbreak type & recognition 2023-10-04 22:49:56 -07:00
Kurt
965039e258 Misc tweaks, fix slot8b ball
Fix Slot8b fallback ball not being Poké (was 0).

Swap eevee & none move sentinels; branch-if-zero easier for compiler.

Lift EV-any-above100 to be checked first; fetching personal and getting EXP is slower than checking 6 indexes in stack. Both branches would check. Most pokemon won't have EVs >100 as users flip through boxes in gen3/4.
2023-10-02 20:54:17 -07:00
sora10pls
e58a1565ad Add ConfigCamera9 2023-10-02 15:55:19 -04:00
sora10pls
8b2b858e69 Update RibbonRules.cs
Closes #4023
2023-10-02 08:07:14 -04:00
sora10pls
57292b23c8 Update Master Rank Ribbon check for Regulation E
Sinnoh starters permitted, Kitakami dex permitted, Legendary/Mythical still disallowed.

Also check for Battle Version on GO8 encounters in SWSH, which while are Gen 8 encounters, require Battle Version since they didn't originate from SWSH.
2023-10-01 09:17:51 -04:00
Kurt
17e0f1feee Prefer cave when finding Fixed origin AreaInfo
see pkNX commit
2023-09-29 22:33:43 -07:00
sora10pls
07191e6aad Label a whole bunch of delivery outbreak blocks
Paldea, Kitakami, Blueberry, BCAT Paldea, BCAT Kitakami, BCAT Blueberry
2023-09-28 21:26:27 -04:00
Kurt
96e74a05d0 Add HoneyTree API 2023-09-28 17:35:29 -07:00
Kurt
963eaaaa40 Merge branch 'master' of https://github.com/kwsch/PKHeX 2023-09-28 16:40:17 -07:00
sora10pls
3debc89e3c Fix sudachi outbreak subjugation block keys
Co-Authored-By: santacrab2 <79347566+santacrab2@users.noreply.github.com>
Co-Authored-By: notzyro <62817135+zyro670@users.noreply.github.com>
Co-Authored-By: Manu <52102823+Manu098vm@users.noreply.github.com>
2023-09-28 10:34:48 -04:00
BlackShark
cfadf86d27
Load plugins before initial files (#4020) 2023-09-28 07:04:12 -07:00
Kurt
12ee201431 Extract EV limit logic 2023-09-27 22:15:59 -07:00
Kurt
0cda774b97 Misc tweaks
Closes #4016
Closes #4018

Adds interface to fetch 64bit correlation seeds from template
Improves re-entry performance for 64bit seed search
2023-09-26 09:32:49 -07:00
Kurt
35696c97ed Update GUI translations 2023-09-24 21:27:56 -07:00
3968 changed files with 266038 additions and 89422 deletions

View File

@ -5,30 +5,22 @@ root = true
charset = utf-8
indent_style = space
indent_size = 4
tab_width = 4
insert_final_newline = true
trim_trailing_whitespace = true
# Solution Files
[*.sln]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.txt]
insert_final_newline = false
# XML Project Files
[*.csproj]
indent_style = space
[*.{slnx,csproj}]
indent_size = 2
tab_width = 2
# Code Files
[*.cs]
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
tab_width = 4
[*.{cs,vb}]
end_of_line = crlf
csharp_prefer_braces = when_multiline:warning
dotnet_diagnostic.IDE0047.severity = none
dotnet_diagnostic.IDE0048.severity = none
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggest
@ -36,10 +28,72 @@ dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:sug
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggest
dotnet_style_parentheses_in_other_operators = always_for_clarity:suggest
[*.{cs,vb}]
csharp_indent_labels = one_less_than_current
csharp_prefer_braces = when_multiline:warning
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_system_threading_lock = true:suggestion
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_using_directive_placement = outside_namespace:silent
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.capitalization = pascal_case
# IDE0130: Namespace does not match folder structure
dotnet_diagnostic.IDE0130.severity = none
# WFO1000: Property does not configure the code serialization for its property content.
dotnet_diagnostic.WFO1000.severity = none

View File

@ -24,13 +24,13 @@ PKHeX erwartet entschlüsselte Spielstände. Da diese konsolenspezifisch verschl
## Screenshots
![Main Window](https://i.imgur.com/37SMQSr.png)
![Main Window](https://i.imgur.com/0KYz0rO.png)
## Erstellen
PKHeX ist eine Windows Forms Anwendung, welche die [.NET 7.0](https://dotnet.microsoft.com/download/dotnet/7.0) runtime benötigt.
PKHeX ist eine Windows Forms Anwendung, welche die [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0) runtime benötigt.
Die Anwendung kann mit jedem Kompiler erstellt werden, der C# 11 unterstützt.
Die Anwendung kann mit jedem Kompiler erstellt werden, der C# 14 unterstützt.
### Erstell Konfiguration

View File

@ -24,13 +24,13 @@ PKHeX espera archivos de guardado que no estén cifrados con las claves específ
## Capturas de Pantalla
![Pantalla principal](https://i.imgur.com/gREbXFH.png)
![Pantalla principal](https://i.imgur.com/JFKIhnz.png)
## Building
PKHeX es una aplicación de Windows Forms que requiere [.NET 7.0](https://dotnet.microsoft.com/download/dotnet/7.0).
PKHeX es una aplicación de Windows Forms que requiere [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0).
El archivo ejecutable puede ser construido con cualquier compilador que soporte C# 11.
El archivo ejecutable puede ser construido con cualquier compilador que soporte C# 14.
### Configuraciones del Build

48
.github/README-fr.md vendored
View File

@ -1,48 +1,48 @@
PKHeX
=====
![License](https://img.shields.io/badge/License-GPLv3-blue.svg)
![Licence](https://img.shields.io/badge/License-GPLv3-blue.svg)
Éditeur de sauvegarde de la série de base Pokémon, programmé en [C#](https://fr.wikipedia.org/wiki/C_sharp).
Éditeur de sauvegarde des jeux principaux de la série Pokémon, programmé en [C#](https://fr.wikipedia.org/wiki/C_sharp).
Prend en charge les fichiers suivants :
* Enregistrer les fichiers ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
* Fichiers de carte mémoire GameCube (\*.raw, \*.bin) contenant des sauvegardes de Pokémon GC.
Les fichiers suivants sont pris en charge :
* Fichiers de sauvegarde (« main », \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
* Fichiers de carte mémoire GameCube (\*.raw, \*.bin) contenant des sauvegardes de jeux Pokémon GameCube.
* Fichiers d'entités Pokémon individuels (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* Fichiers de cadeau mystère (\*.pgt, \*.pcd, \*.pgf, .wc\*) y compris la conversion en .pk\*
* Importation d'entités GO Park (\*.gp1) incluant la conversion en .pb7
* Importation d'équipes à partir de 3DS Battle Videos
* Transfert d'une génération à l'autre, conversion des formats en cours de route.
* Fichiers de Cadeau Mystère (\*.pgt, \*.pcd, \*.pgf, .wc\*), incluant la conversion en .pk\*
* Importation d'entités GO Park (\*.gp1), incluant la conversion en .pb7
* Importation d'équipes à partir de vidéos de combat 3DS déchiffrées
* Transfert d'une génération à l'autre, avec une conversion du format au passage.
Les données sont affichées dans une vue qui peut être modifiée et enregistrée. L'interface peut être traduite avec des fichiers de ressources/textes externes afin que différentes langues puissent être prises en charge.
Les données sont affichées sur une interface graphique, permettant de faire des modifications et des sauvegardes. L'interface peut être traduite avec des fichiers de ressources/textes externes afin que différentes langues puissent être prises en charge.
Les ensembles Pokémon Showdown et les QR codes peuvent être importés/exportés pour faciliter le partage.
Les sets Pokémon Showdown! et les QR codes peuvent être importés/exportés pour faciliter le partage.
PKHeX attend des fichiers de sauvegarde qui ne sont pas chiffrés avec des clés spécifiques à la console. Utilisez un gestionnaire de données enregistrées pour importer et exporter des données enregistrées à partir de la console ([Checkpoint](https://github.com/FlagBrew/Checkpoint) ou [JKSM](https://github.com/J-D-K/JKSM)).
PKHeX demande des fichiers de sauvegarde qui ne sont pas chiffrés par des clés spécifiques aux consoles. Utilisez un gestionnaire de sauvegardes pour importer et exporter des sauvegardes depuis une console ([Checkpoint](https://github.com/FlagBrew/Checkpoint), save_manager, [JKSM](https://github.com/J-D-K/JKSM), ou SaveDataFiler).
**Nous ne soutenons ni ne tolérons la tricherie aux dépens des autres. N'utilisez pas de Pokémon piratés de manière significative au combat ou dans des échanges avec ceux qui ne savent pas que des Pokémon piratés sont en cours d'utilisation.**
**Nous ne soutenons ni ne tolérons la tricherie aux dépens des autres. N'utilisez pas de Pokémon piratés de manière significative en combat ou en échanges avec ceux qui ne savent pas que des Pokémon piratés sont utilisés.**
## Captures d'écran
## Capture d'écran
![Main Window](https://i.imgur.com/fk6IX2a.png)
![Fenêtre principale](https://i.imgur.com/CpUzqmY.png)
## Construction
## Compilation
PKHeX est une application Windows Forms qui nécessite [.NET 7.0.](https://dotnet.microsoft.com/download/dotnet/7.0)
PKHeX est une application Windows Forms qui nécessite [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0).
L'exécutable peut être construit avec n'importe quel compilateur prenant en charge C# 11.
L'exécutable peut être compilé avec n'importe quel compilateur prenant en charge C# 14.
### Construire les configurations
### Configurations de la compilation
Utilisez les configurations Debug ou Release lors de la construction. Il n'y a pas de code spécifique à la plate-forme à craindre!
Utilisez la configuration Debug ou Release lors de la compilation. Aucun code spécifique à une plateforme n'est utilisée dans le programme, donc soyez sans crainte !
## Dépendances
Le code de génération du QR code de PKHeX est extrait de [QRCoder](https://github.com/codebude/QRCoder), qui est [sous licence MIT](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt).
Le code de génération des QR codes de PKHeX est extrait de [QRCoder](https://github.com/codebude/QRCoder), qui est [sous licence MIT](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt).
La collection de sprites shiny de PKHeX est tirée de [pokesprite](https://github.com/msikma/pokesprite), qui est [sous licence MIT](https://github.com/msikma/pokesprite/blob/master/LICENSE).
La collection de sprites chromatiques de PKHeX est tirée de [pokesprite](https://github.com/msikma/pokesprite), qui est [sous licence 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 collection de sprites Légendes Pokémon : Arceus de PKHeX est tirée du projet [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934), avec son abondance de collaborateurs et de contributeurs.
## IDE
PKHeX peut être ouvert avec des IDE tels que [Visual Studio](https://visualstudio.microsoft.com/fr/downloads/) en ouvrant le fichier .sln ou .csproj.
PKHeX peut être ouvert avec des IDEs tels que [Visual Studio](https://visualstudio.microsoft.com/fr/downloads/) en ouvrant le fichier .sln ou .csproj.

10
.github/README-it.md vendored
View File

@ -4,12 +4,12 @@ PKHeX
Editor di Salvataggi Pokémon per la serie principale, programmato in [C#](https://it.wikipedia.org/wiki/C_sharp).
Supporta i seguenti tipi di file:
Supporta i seguenti tipi di file:
* File di salvataggio ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
* File di Memory Card GameCube (\*.raw, \*.bin) contenenti File di Salvataggio Pokémon.
* File di Entità Pokémon individuali (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* File di Dono Segreto (\*.pgt, \*.pcd, \*.pgf, .wc\*) inclusa conversione in .pk\*
* Importazione di Entità del Go Park (\*.gp1) inclusa conversione in .pb7
* Importazione di Entità del GO Park (\*.gp1) inclusa conversione in .pb7
* Importazione di squadre da Video Lotta del 3DS decriptati
* Trasferimento da una generazione all'altra, convertendo i formati propriamente.
@ -24,13 +24,13 @@ PKHeX si aspetta file di salvataggio non criptati con le chiavi specifiche della
## Screenshots
![Main Window](https://i.imgur.com/epXOQ5M.png)
![Main Window](https://i.imgur.com/vrWs9Xq.png)
## Building
PKHeX è un applicazione Windows Form che necessita del [.NET Desktop Runtime 7.0](https://dotnet.microsoft.com/download/dotnet/7.0).
PKHeX è un applicazione Windows Form che necessita del [.NET Desktop Runtime 10.0](https://dotnet.microsoft.com/download/dotnet/10.0).
L'eseguibile può essere compilato con qualsiasi compiler che supporti C# 11.
L'eseguibile può essere compilato con qualsiasi compiler che supporti C# 14.
### Configurazioni di Build

49
.github/README-ko.md vendored Normal file
View File

@ -0,0 +1,49 @@
PKHeX(포케헥스)
=====
![License](https://img.shields.io/badge/License-GPLv3-blue.svg)
포켓몬 코어 시리즈 세이브 에디터, [C#](https://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29)으로 프로그래밍됨.
다음 파일을 지원합니다:
* 세이브 파일 ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
* GC 포켓몬 세이브 게임이 들어 있는 게임큐브 메모리 카드 파일(\*.raw, \*.bin) 포함
* 개별 포켓몬 엔티티 파일 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* 이상한소포 파일(\*.pgt, \*.pcd, \*.pgf, .wc\*)을 .pk로 변환하는 기능 포함
* GO 파크 엔티티 가져오기 (\*.gp1) .pb7로 변환 포함
* Decrypted 3DS Battle Videos에서 팀 가져오기
* 한 세대에서 다른 세대로 이동하면서 그 과정에서 형식이 변환됩니다.
데이터는 편집하고 저장할 수 있는 보기로 표시됩니다.
인터페이스는 리소스/외부 텍스트 파일로 번역할 수 있어 다양한 언어를 지원할 수 있습니다.
포켓몬 쇼다운 세트와 QR 코드를 가져오고 내보낼 수 있어 공유에 도움을 줄 수 있습니다.
PKHeX는 콘솔 전용 키로 암호화되지 않은 세이브 파일을 요구합니다. ([Checkpoint](https://github.com/FlagBrew/Checkpoint), save_manager, [JKSM](https://github.com/J-D-K/JKSM), 또는 SaveDataFiler)를 사용하여 콘솔에서 세이브 데이터를 가져오고 내보낼 수 있습니다.
**저희는 타인을 희생시키는 부정행위를 지지하거나 묵인하지 않습니다. 해킹된 포켓몬이 사용 중이라는 사실을 모르는 사람들과의 배틀 또는 통신에서 심각하게 해킹된 포켓몬을 사용하지 마십시오.**
## 스크린샷
![Main Window](https://i.imgur.com/vDiaS7k.png)
## 빌드
PKHeX는 [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)이 필요한 Windows Forms 애플리케이션입니다.
실행 파일은 C# 14을 지원하는 모든 컴파일러로 빌드할 수 있습니다.
### 빌드 구성
빌드할 때 디버그 또는 릴리스 빌드 구성을 사용하세요. 플랫폼 전용 코드는 걱정할 필요가 없습니다!
## 종속성
PKHeX의 QR 코드 생성 코드는 [the MIT license](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt)에 따라 라이선스가 부여된 [QRCoder](https://github.com/codebude/QRCoder) 에서 가져왔습니다.
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) 프로젝트와 수많은 협력자 및 기여자의 도움을 받아 만들어졌습니다.
### IDE(통합 개발 환경)
PKHeX는 .sln 또는 .csproj 파일을 열어 [Visual Studio](https://visualstudio.microsoft.com/downloads/)와 같은 IDE(통합 개발 환경)로 열 수 있습니다.

View File

@ -9,7 +9,7 @@ PKHeX
* GameCube 宝可梦游戏存档包含 GameCube 记忆存档 (\*.raw, \*.bin)
* 单个宝可梦实体文件 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
* 神秘礼物文件 (\*.pgt, \*.pcd, \*.pgf, .wc\*) 并转换为 .pk\*
* 导入 Go Park存档 (\*.gp1) 并转换为 .pb7
* 导入 GO Park存档 (\*.gp1) 并转换为 .pb7
* 从已破解的 3DS 对战视频中导入队伍
* 支持宝可梦在不同世代的间转移,并转换文件格式
@ -24,13 +24,13 @@ PKHeX 所读取存档文件必须是未经主机唯一密钥加密,因此请
## 截图
![主介面](https://i.imgur.com/hM8GJ4U.png)
![主介面](https://i.imgur.com/MPN4Hk9.png)
## 构建
PKHeX 是 Windows 窗口应用程序,依赖于 [.NET 7.0](https://dotnet.microsoft.com/download/dotnet/7.0)。
PKHeX 是 Windows 窗口应用程序,依赖于 [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)。
可以使用任何支持 C# 11 的编译器生成可执行文件。
可以使用任何支持 C# 14 的编译器生成可执行文件。
### 构建配置

View File

@ -24,13 +24,13 @@ PKHeX 所讀取檔案須未經主機唯一密鑰加密,因而請使用儲存
## 螢幕擷取截圖
![主介面](https://i.imgur.com/U5FKBIF.png)
![主介面](https://i.imgur.com/8IQx2jo.png)
## 構建
PKHeX 係 Windows 窗體應用程式,其須依賴於 [.NET 7.0](https://dotnet.microsoft.com/download/dotnet/7.0)。
PKHeX 係 Windows 窗體應用程式,其須依賴於 [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)。
程式可透過任意支援 C# 11 之編譯器構建。
程式可透過任意支援 C# 14 之編譯器構建。
### 構建配置

32
.gitignore vendored
View File

@ -227,38 +227,6 @@ $RECYCLE.BIN/
pingme.txt
#############
## Python
#############
*.py[cod]
__pycache__/
# Packages
*.egg
*.egg-info
dist/
build/
eggs/
parts/
var/
sdist/
develop-eggs/
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
#################
## MonoDevelop
#################

View File

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>23.09.25</Version>
<LangVersion>11</LangVersion>
<Version>26.03.20</Version>
<LangVersion>14</LangVersion>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>
<Product>PKHeX</Product>

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using static PKHeX.Core.Ball;
namespace PKHeX.Core;
@ -9,191 +8,149 @@ namespace PKHeX.Core;
/// </summary>
public static class BallApplicator
{
private const Ball BallMin = Master; // first defined Enum value
private const Ball BallMax = LAOrigin; // all indexes up to and including LAOrigin are defined Enum values.
/// <summary>
/// Maximum number of <see cref="Ball"/> values that can be returned in a span.
/// </summary>
public const byte MaxBallSpanAlloc = (byte)BallMax + 1;
private static IEncounterTemplate Get(LegalityAnalysis la) => la.EncounterOriginal;
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/>.
/// </remarks>
/// <inheritdoc cref="GetLegalBalls(Span{Ball}, PKM, IEncounterTemplate)"/>
public static int GetLegalBalls(Span<Ball> result, PKM pk) => GetLegalBalls(result, pk, new LegalityAnalysis(pk));
/// <inheritdoc cref="GetLegalBalls(Span{Ball}, PKM, IEncounterTemplate)"/>
public static int GetLegalBalls(Span<Ball> result, PKM pk, LegalityAnalysis la) => GetLegalBalls(result, pk, Get(la));
/// <summary>
/// Gets all balls that are legal for the input <see cref="PKM"/>.
/// </summary>
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="result">Result storage.</param>
/// <param name="pk">Pokémon to retrieve a list of valid balls for.</param>
/// <returns>Enumerable list of <see cref="Ball"/> values that the <see cref="PKM"/> is legal with.</returns>
public static IEnumerable<Ball> GetLegalBalls(PKM pk)
/// <param name="enc">Encounter matched to.</param>
/// <returns>Count of <see cref="Ball"/> values that the <see cref="PKM"/> is legal with.</returns>
public static int GetLegalBalls(Span<Ball> result, PKM pk, IEncounterTemplate enc)
{
var clone = pk.Clone();
foreach (var b in BallList)
{
var ball = (int)b;
clone.Ball = ball;
if (clone.Ball != ball)
continue; // Some setters guard against out of bounds values.
if (new LegalityAnalysis(clone).Valid)
yield return b;
}
if (enc is EncounterInvalid)
return 0;
if (enc.Species is (ushort)Species.Nincada && pk.Species is (ushort)Species.Shedinja)
return GetLegalBallsEvolvedShedinja(result, pk, enc);
return LoadLegalBalls(result, pk, enc);
}
private static ReadOnlySpan<Ball> ShedinjaEvolve4 => [Sport, Poke];
private static int GetLegalBallsEvolvedShedinja(Span<Ball> result, PKM pk, IEncounterTemplate enc)
{
switch (enc)
{
case EncounterSlot4 s4 when IsNincadaEvolveInOrigin(pk, s4):
ShedinjaEvolve4.CopyTo(result);
return ShedinjaEvolve4.Length;
case EncounterSlot3 s3 when IsNincadaEvolveInOrigin(pk, s3):
return LoadLegalBalls(result, pk, enc);
}
result[0] = Poke;
return 1;
}
private static bool IsNincadaEvolveInOrigin(PKM pk, IEncounterTemplate enc)
{
// Rough check to see if Nincada evolved in the origin context (Gen3/4).
// Does not do PID/IV checks to know the original met level.
var current = pk.CurrentLevel;
var met = pk.MetLevel;
if (pk.Format == enc.Generation)
return current > met;
return enc.LevelMin != met && current > enc.LevelMin;
}
private static int LoadLegalBalls(Span<Ball> result, PKM pk, IEncounterTemplate enc)
{
int ctr = 0;
for (var b = BallMin; b <= BallMax; b++)
{
if (BallVerifier.VerifyBall(enc, b, pk).IsValid)
result[ctr++] = b;
}
return ctr;
}
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/>.
/// </remarks>
/// <inheritdoc cref="ApplyBallLegalRandom(PKM, IEncounterTemplate)"/>
public static byte ApplyBallLegalRandom(PKM pk) => ApplyBallLegalRandom(pk, new LegalityAnalysis(pk));
/// <inheritdoc cref="ApplyBallLegalRandom(PKM, IEncounterTemplate)"/>
public static byte ApplyBallLegalRandom(PKM pk, LegalityAnalysis la) => ApplyBallLegalRandom(pk, Get(la));
/// <summary>
/// Applies a random legal ball value if any exist.
/// </summary>
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="pk">Pokémon to modify.</param>
public static int ApplyBallLegalRandom(PKM pk)
/// <param name="enc">Encounter matched to.</param>
public static byte ApplyBallLegalRandom(PKM pk, IEncounterTemplate enc)
{
Span<Ball> balls = stackalloc Ball[MaxBallSpanAlloc];
var count = GetBallListFromColor(pk, balls);
var count = GetLegalBalls(balls, pk, enc);
balls = balls[..count];
Util.Rand.Shuffle(balls);
return ApplyFirstLegalBall(pk, balls);
return ApplyFirstLegalBall(pk, balls, []);
}
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/>.
/// </remarks>
/// <inheritdoc cref="ApplyBallLegalByColor(PKM, IEncounterTemplate, PersonalColor)"/>
public static byte ApplyBallLegalByColor(PKM pk) => ApplyBallLegalByColor(pk, PersonalColorUtil.GetColor(pk));
/// <inheritdoc cref="ApplyBallLegalByColor(PKM, IEncounterTemplate, PersonalColor)"/>
public static byte ApplyBallLegalByColor(PKM pk, PersonalColor color) => ApplyBallLegalByColor(pk, new LegalityAnalysis(pk), color);
/// <inheritdoc cref="ApplyBallLegalByColor(PKM, IEncounterTemplate, PersonalColor)"/>
public static byte ApplyBallLegalByColor(PKM pk, LegalityAnalysis la, PersonalColor color) => ApplyBallLegalByColor(pk, Get(la), color);
/// <summary>
/// Applies a legal ball value if any exist, ordered by color.
/// </summary>
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="pk">Pokémon to modify.</param>
public static int ApplyBallLegalByColor(PKM pk)
/// <param name="enc">Encounter matched to.</param>
/// <param name="color">Color preference to order by.</param>
public static byte ApplyBallLegalByColor(PKM pk, IEncounterTemplate enc, PersonalColor color)
{
Span<Ball> balls = stackalloc Ball[MaxBallSpanAlloc];
GetBallListFromColor(pk, balls);
return ApplyFirstLegalBall(pk, balls);
var count = GetLegalBalls(balls, pk, enc);
balls = balls[..count];
var prefer = PersonalColorUtil.GetPreferredByColor(enc, color);
return ApplyFirstLegalBall(pk, balls, prefer);
}
/// <summary>
/// Applies a random ball value in a cyclical manner.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static int ApplyBallNext(PKM pk)
private static byte ApplyFirstLegalBall(PKM pk, Span<Ball> legal, ReadOnlySpan<Ball> prefer)
{
Span<Ball> balls = stackalloc Ball[MaxBallSpanAlloc];
GetBallList(pk.Ball, balls);
var next = balls[0];
return pk.Ball = (int)next;
}
private static int ApplyFirstLegalBall(PKM pk, ReadOnlySpan<Ball> balls)
{
foreach (var b in balls)
foreach (var ball in prefer)
{
pk.Ball = (int)b;
if (new LegalityAnalysis(pk).Valid)
break;
if (Contains(legal, ball))
return pk.Ball = (byte)ball;
}
return pk.Ball;
}
private static int GetBallList(int ball, Span<Ball> result)
{
var balls = BallList;
var currentBall = (Ball)ball;
return GetCircularOnce(balls, currentBall, result);
}
private static int GetBallListFromColor(PKM pk, Span<Ball> result)
{
// Gen1/2 don't store color in personal info
var pi = pk.Format >= 3 ? pk.PersonalInfo : PersonalTable.USUM.GetFormEntry(pk.Species, 0);
var color = (PersonalColor)pi.Color;
var balls = BallColors[color];
var currentBall = (Ball)pk.Ball;
return GetCircularOnce(balls, currentBall, result);
}
private static int GetCircularOnce<T>(T[] items, T current, Span<T> result)
{
var currentIndex = Array.IndexOf(items, current);
if (currentIndex < 0)
currentIndex = items.Length - 2;
return GetCircularOnce(items, currentIndex, result);
}
private static int GetCircularOnce<T>(ReadOnlySpan<T> items, int startIndex, Span<T> result)
{
var tail = items[(startIndex + 1)..];
tail.CopyTo(result);
items[..startIndex].CopyTo(result[tail.Length..]);
return items.Length;
}
private static readonly Ball[] BallList = (Ball[])Enum.GetValues(typeof(Ball));
private static int MaxBallSpanAlloc => BallList.Length;
static BallApplicator()
{
ReadOnlySpan<Ball> exclude = stackalloc Ball[] {None, Poke};
ReadOnlySpan<Ball> end = stackalloc Ball[] {Poke};
Span<Ball> all = stackalloc Ball[BallList.Length - exclude.Length];
all = all[..FillExcept(all, exclude, BallList)];
var colors = (PersonalColor[])Enum.GetValues(typeof(PersonalColor));
foreach (var c in colors)
foreach (var ball in legal)
{
// Replace the array reference with a new array that appends non-matching values, followed by the end values.
var defined = BallColors[c];
Span<Ball> match = (BallColors[c] = new Ball[all.Length + end.Length]);
defined.CopyTo(match);
FillExcept(match[defined.Length..], defined, all);
end.CopyTo(match[^end.Length..]);
if (!Contains(prefer, ball))
return pk.Ball = (byte)ball;
}
return pk.Ball; // fail
static int FillExcept(Span<Ball> result, ReadOnlySpan<Ball> exclude, ReadOnlySpan<Ball> all)
static bool Contains(ReadOnlySpan<Ball> balls, Ball ball)
{
int ctr = 0;
foreach (var b in all)
foreach (var b in balls)
{
if (Contains(exclude, b))
continue;
result[ctr++] = b;
}
return ctr;
static bool Contains(ReadOnlySpan<Ball> arr, Ball b)
{
foreach (var a in arr)
{
if (a == b)
return true;
}
return false;
if (b == ball)
return true;
}
return false;
}
}
/// <summary>
/// Priority Match ball IDs that match the color ID in descending order
/// </summary>
private static readonly Dictionary<PersonalColor, Ball[]> BallColors = new()
{
[PersonalColor.Red] = new[] { Cherish, Repeat, Fast, Heal, Great, Dream, Lure },
[PersonalColor.Blue] = new[] { Dive, Net, Great, Beast, Lure },
[PersonalColor.Yellow] = new[] { Level, Ultra, Repeat, Quick, Moon },
[PersonalColor.Green] = new[] { Safari, Friend, Nest, Dusk },
[PersonalColor.Black] = new[] { Luxury, Heavy, Ultra, Moon, Net, Beast },
[PersonalColor.Brown] = new[] { Level, Heavy },
[PersonalColor.Purple] = new[] { Master, Love, Dream, Heal },
[PersonalColor.Gray] = new[] { Heavy, Premier, Luxury },
[PersonalColor.White] = new[] { Premier, Timer, Luxury, Ultra },
[PersonalColor.Pink] = new[] { Love, Dream, Heal },
};
/// <summary>
/// Personal Data color IDs
/// </summary>
private enum PersonalColor : byte
{
Red,
Blue,
Yellow,
Green,
Black,
Brown,
Purple,
Gray,
White,
Pink,
}
}

View File

@ -1,12 +1,12 @@
namespace PKHeX.Core;
/// <summary>
/// Logic for applying a <see cref="PK1.Catch_Rate"/> value.
/// Logic for applying a <see cref="PK1.CatchRate"/> value.
/// </summary>
public static class CatchRateApplicator
{
/// <summary>
/// Gets the suggested <see cref="PK1.Catch_Rate"/> for the entity.
/// Gets the suggested <see cref="PK1.CatchRate"/> for the entity.
/// </summary>
public static int GetSuggestedCatchRate(PK1 pk, SaveFile sav)
{
@ -15,13 +15,13 @@ public static int GetSuggestedCatchRate(PK1 pk, SaveFile sav)
}
/// <summary>
/// Gets the suggested <see cref="PK1.Catch_Rate"/> for the entity.
/// Gets the suggested <see cref="PK1.CatchRate"/> for the entity.
/// </summary>
public static byte GetSuggestedCatchRate(PK1 pk, SaveFile sav, LegalityAnalysis la)
{
// If it is already valid, just use the current value.
if (la.Valid)
return pk.Catch_Rate;
return pk.CatchRate;
// If it has ever visited generation 2, the Held Item can be removed prior to trade back.
if (la.Info.Generation == 2)
@ -31,20 +31,25 @@ public static byte GetSuggestedCatchRate(PK1 pk, SaveFile sav, LegalityAnalysis
var enc = la.EncounterMatch;
switch (enc)
{
case EncounterGift1 { Version: GameVersion.Stadium, Species: (int)Species.Psyduck }:
case EncounterGift1 { Trainer: EncounterGift1.TrainerType.Stadium, Species: (int)Species.Psyduck }:
return pk.Japanese ? (byte)167 : (byte)168; // Amnesia Psyduck has different catch rates depending on language
default:
var pt = GetPersonalTable(sav, enc);
var pt = GetPersonalTable(sav, enc.Version);
var pi = pt[enc.Species];
return (byte)pi.CatchRate;
return pi.CatchRate;
}
}
private static PersonalTable1 GetPersonalTable(SaveFile sav, IEncounterable v)
private static PersonalTable1 GetPersonalTable(SaveFile sav, GameVersion version)
{
if (sav.Personal is PersonalTable1 pt && (sav.Version.Contains(v.Version) || v.Version.Contains(sav.Version)))
return pt;
if (!GameVersion.RB.Contains(v.Version))
if (sav.Personal is PersonalTable1 pt)
{
var other = sav.Version;
if (other.Contains(version) || version.Contains(other))
return pt;
}
if (!GameVersion.RB.Contains(version))
return PersonalTable.Y;
return PersonalTable.RB;
}

View File

@ -7,72 +7,78 @@ namespace PKHeX.Core;
/// </summary>
public static class GenderApplicator
{
/// <summary>
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
/// <remarks>Has special logic for an unspecified gender.</remarks>
public static void SetSaneGender(this PKM pk, int gender)
extension(PKM pk)
{
int g = gender == -1 ? pk.GetSaneGender() : gender;
pk.SetGender(g);
}
/// <summary>
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
public static void SetGender(this PKM pk, int gender)
{
gender = Math.Clamp(gender, 0, 2);
if (pk.Gender == gender)
return;
if (pk.Format <= 2)
/// <summary>
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
/// </summary>
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
/// <remarks>Has special logic for an unspecified gender.</remarks>
public void SetSaneGender(byte gender)
{
pk.SetAttackIVFromGender(gender);
var g = gender > 2 ? pk.GetSaneGender() : gender;
pk.SetGender(g);
}
else if (pk.Format <= 5)
{
pk.SetPIDGender(gender);
pk.Gender = gender;
}
else
{
pk.Gender = gender;
}
}
/// <summary>
/// Sanity checks the provided <see cref="PKM.Gender"/> value, and returns a sane value.
/// </summary>
/// <param name="pk"></param>
/// <returns>Most-legal <see cref="PKM.Gender"/> value</returns>
public static int GetSaneGender(this PKM pk)
{
var gt = pk.PersonalInfo.Gender;
switch (gt)
/// <inheritdoc cref="SetSaneGender(PKM, byte)"/>
public void SetSaneGender(byte? gender)
{
case PersonalInfo.RatioMagicGenderless: return 2;
case PersonalInfo.RatioMagicFemale: return 1;
case PersonalInfo.RatioMagicMale: return 0;
var g = gender ?? pk.GetSaneGender();
pk.SetGender(g);
}
if (!pk.IsGenderValid())
return EntityGender.GetFromPIDAndRatio(pk.PID, gt);
return pk.Gender;
}
/// <summary>
/// Updates the <see cref="PKM.IV_ATK"/> for a Generation 1/2 format <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/>.</param>
public static void SetAttackIVFromGender(this PKM pk, int gender)
{
var rnd = Util.Rand;
while (pk.Gender != gender)
pk.IV_ATK = rnd.Next(16);
/// <summary>
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
/// </summary>
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
public void SetGender(byte gender)
{
gender = Math.Clamp(gender, (byte)0, (byte)2);
if (pk.Gender == gender)
return;
if (pk.Format <= 2)
{
pk.SetAttackIVFromGender(gender);
}
else if (pk.Format <= 5)
{
pk.SetPIDGender(gender);
pk.Gender = gender;
}
else
{
pk.Gender = gender;
}
}
/// <summary>
/// Sanity checks the provided <see cref="PKM.Gender"/> value, and returns a sane value.
/// </summary>
/// <returns>Most-legal <see cref="PKM.Gender"/> value</returns>
public byte GetSaneGender()
{
var gt = pk.PersonalInfo.Gender;
switch (gt)
{
case PersonalInfo.RatioMagicGenderless: return 2;
case PersonalInfo.RatioMagicFemale: return 1;
case PersonalInfo.RatioMagicMale: return 0;
}
if (!pk.IsGenderValid())
return EntityGender.GetFromPIDAndRatio(pk.PID, gt);
return pk.Gender;
}
/// <summary>
/// Updates the <see cref="PKM.IV_ATK"/> for a Generation 1/2 format <see cref="PKM"/>.
/// </summary>
/// <param name="gender">Desired <see cref="PKM.Gender"/>.</param>
public void SetAttackIVFromGender(byte gender)
{
var rnd = Util.Rand;
while (pk.Gender != gender)
pk.IV_ATK = rnd.Next(16);
}
}
}

View File

@ -7,23 +7,24 @@ namespace PKHeX.Core;
/// </summary>
public static class HiddenPowerApplicator
{
/// <summary>
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
public static void SetHiddenPower(this PKM pk, int hiddenPowerType)
extension(PKM pk)
{
Span<int> IVs = stackalloc int[6];
pk.GetIVs(IVs);
HiddenPower.SetIVsForType(hiddenPowerType, IVs, pk.Context);
pk.SetIVs(IVs);
}
/// <summary>
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
public void SetHiddenPower(int hiddenPowerType)
{
Span<int> IVs = stackalloc int[6];
pk.GetIVs(IVs);
HiddenPower.SetIVsForType(hiddenPowerType, IVs, pk.Context);
pk.SetIVs(IVs);
}
/// <summary>
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
public static void SetHiddenPower(this PKM pk, MoveType hiddenPowerType) => pk.SetHiddenPower((int)hiddenPowerType);
/// <summary>
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
public void SetHiddenPower(MoveType hiddenPowerType) => pk.SetHiddenPower((int)hiddenPowerType);
}
}

View File

@ -1,9 +1,9 @@
using System;
using System;
namespace PKHeX.Core;
/// <summary>
/// Logic for modifying the <see cref="PKM.MarkValue"/>.
/// Logic for modifying the <see cref="IAppliedMarkings"/>.
/// </summary>
public static class MarkingApplicator
{
@ -14,21 +14,42 @@ public static class MarkingApplicator
public static Func<PKM, Func<int, int, int>> MarkingMethod { get; set; } = FlagHighLow;
/// <summary>
/// Sets the <see cref="PKM.MarkValue"/> to indicate flawless (or near-flawless) <see cref="PKM.IVs"/>.
/// Sets the applied Markings to indicate flawless (or near-flawless) <see cref="PKM.IVs"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMarkings(this PKM pk)
{
if (pk.MarkingCount < 6)
if (pk is not IAppliedMarkings { MarkingCount: 6 })
return; // insufficient marking indexes
if (pk is IAppliedMarkings<MarkingColor> c)
c.SetMarkings(pk);
else if (pk is IAppliedMarkings<bool> b)
b.SetMarkings(pk);
}
/// <inheritdoc cref="SetMarkings(PKM)"/>
public static void SetMarkings(this IAppliedMarkings<bool> mark, PKM pk)
{
var method = MarkingMethod(pk);
pk.SetMarking(0, method(pk.IV_HP , 0));
pk.SetMarking(1, method(pk.IV_ATK, 1));
pk.SetMarking(2, method(pk.IV_DEF, 2));
pk.SetMarking(3, method(pk.IV_SPA, 3));
pk.SetMarking(4, method(pk.IV_SPD, 4));
pk.SetMarking(5, method(pk.IV_SPE, 5));
mark.SetMarking(0, method(pk.IV_HP , 0) == 1);
mark.SetMarking(1, method(pk.IV_ATK, 1) == 1);
mark.SetMarking(2, method(pk.IV_DEF, 2) == 1);
mark.SetMarking(3, method(pk.IV_SPA, 3) == 1);
mark.SetMarking(4, method(pk.IV_SPD, 4) == 1);
mark.SetMarking(5, method(pk.IV_SPE, 5) == 1);
}
/// <inheritdoc cref="SetMarkings(PKM)"/>
public static void SetMarkings(this IAppliedMarkings<MarkingColor> mark, PKM pk)
{
var method = MarkingMethod(pk);
mark.SetMarking(0, (MarkingColor)method(pk.IV_HP, 0));
mark.SetMarking(1, (MarkingColor)method(pk.IV_ATK, 1));
mark.SetMarking(2, (MarkingColor)method(pk.IV_DEF, 2));
mark.SetMarking(3, (MarkingColor)method(pk.IV_SPA, 3));
mark.SetMarking(4, (MarkingColor)method(pk.IV_SPD, 4));
mark.SetMarking(5, (MarkingColor)method(pk.IV_SPE, 5));
}
/// <summary>
@ -37,18 +58,19 @@ public static void SetMarkings(this PKM pk)
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Marking index to toggle</param>
/// <returns>Current marking value</returns>
public static int ToggleMarking(this PKM pk, int index)
public static void ToggleMarking(this PKM pk, int index)
{
var marking = pk.GetMarking(index);
var revised = NextMarking(pk.Format, marking);
pk.SetMarking(index, revised);
return revised;
if (pk is IAppliedMarkings<MarkingColor> c)
c.SetMarking(index, c.GetMarking(index).Next());
else if (pk is IAppliedMarkings<bool> b)
b.SetMarking(index, !b.GetMarking(index));
}
private static int NextMarking(int format, int marking) => format switch
private static MarkingColor Next(this MarkingColor value) => value switch
{
<= 6 => marking ^ 1, // toggle : 0 (off) | 1 (on)
_ => (marking + 1) % 3, // cycle 0->1->2->0... : 0 (none) | 1 (blue) | 2 (pink)
MarkingColor.Blue => MarkingColor.Pink,
MarkingColor.Pink => MarkingColor.None,
_ => MarkingColor.Blue,
};
private static Func<int, int, int> FlagHighLow(PKM pk)

View File

@ -1,50 +1,52 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary>
/// Logic for modifying the Memory parameters of a <see cref="PKM"/>.
/// </summary>
public static class MemoryApplicator
{
/// <summary>
/// Sets all Memory related data to the default value (zero).
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void ClearMemories(this PKM pk)
extension(PKM pk)
{
if (pk is IAffection a)
a.OT_Affection = a.HT_Affection = 0;
if (pk is IMemoryOT o)
o.ClearMemoriesOT();
if (pk is IMemoryHT h)
h.ClearMemoriesHT();
}
/// <summary>
/// Sets the Memory details to a Hatched Egg's memories.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetHatchMemory6(this PKM pk)
{
if (pk is IMemoryOT o)
/// <summary>
/// Sets all Memory related data to the default value (zero).
/// </summary>
public void ClearMemories()
{
o.OT_Memory = 2;
o.OT_Feeling = MemoryContext6.GetRandomFeeling6(2);
o.OT_Intensity = 1;
o.OT_TextVar = pk.XY ? (ushort)43 : (ushort)27; // riverside road : battling spot
if (pk is IAffection a)
a.OriginalTrainerAffection = a.HandlingTrainerAffection = 0;
if (pk is IMemoryOT o)
o.ClearMemoriesOT();
if (pk is IMemoryHT h)
h.ClearMemoriesHT();
}
/// <summary>
/// Sets the Memory details to a Hatched Egg's memories specific to <see cref="EntityContext.Gen6"/> origin.
/// </summary>
public void SetHatchMemory6()
{
if (pk is IMemoryOT o)
{
o.OriginalTrainerMemory = 2;
o.OriginalTrainerMemoryFeeling = MemoryContext6.GetRandomFeeling6(2);
o.OriginalTrainerMemoryIntensity = 1;
o.OriginalTrainerMemoryVariable = pk.XY ? (ushort)43 : (ushort)27; // riverside road : battling spot
}
if (pk is IAffection a)
a.OriginalTrainerAffection = 0;
}
if (pk is IAffection a)
a.OT_Affection = 0;
}
/// <summary>
/// Sets a random memory specific to <see cref="GameVersion.Gen6"/> locality.
/// Sets a random memory specific to <see cref="EntityContext.Gen6"/> origin.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRandomMemory6(this PK6 pk)
{
// for lack of better randomization :)
pk.OT_Memory = 63;
pk.OT_Intensity = 6;
pk.OT_Feeling = MemoryContext6.GetRandomFeeling6(pk.OT_Memory);
const byte memory = 63; // almost got lost when it explored a forest with {Trainer}
pk.OriginalTrainerMemory = memory;
pk.OriginalTrainerMemoryFeeling = MemoryContext6.GetRandomFeeling6(memory);
pk.OriginalTrainerMemoryIntensity = MemoryContext6.MaxIntensity;
}
}

View File

@ -7,107 +7,77 @@ namespace PKHeX.Core;
/// </summary>
public static class MoveApplicator
{
/// <summary>
/// Sets the individual PP Up count values depending if a Move is present in the move's slot or not.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to use.</param>
public static void SetMaximumPPUps(this PKM pk, ReadOnlySpan<ushort> moves)
extension(PKM pk)
{
pk.Move1_PPUps = GetPPUpCount(moves[0]);
pk.Move2_PPUps = GetPPUpCount(moves[1]);
pk.Move3_PPUps = GetPPUpCount(moves[2]);
pk.Move4_PPUps = GetPPUpCount(moves[3]);
pk.SetMaximumPPCurrent(moves);
static int GetPPUpCount(ushort moveID)
/// <summary>
/// Sets the individual PP Up count values depending on if a Move is present in the move's slot or not.
/// </summary>
/// <param name="moves"><see cref="PKM.Moves"/> to use.</param>
public void SetMaximumPPUps(ReadOnlySpan<ushort> moves)
{
if (Legal.IsPPUpAvailable(moveID))
return 3;
return 0;
}
}
pk.Move1_PPUps = GetPPUpCount(moves[0]);
pk.Move2_PPUps = GetPPUpCount(moves[1]);
pk.Move3_PPUps = GetPPUpCount(moves[2]);
pk.Move4_PPUps = GetPPUpCount(moves[3]);
/// <summary>
/// Sets the individual PP Up count values depending if a Move is present in the move slot or not.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMaximumPPUps(this PKM pk)
{
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoves(moves);
pk.SetMaximumPPUps(moves);
}
/// <summary>
/// Updates the <see cref="PKM.Moves"/> and updates the current PP counts.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="input"><see cref="PKM.Moves"/> to set.</param>
/// <param name="maxPP">Option to maximize PP Ups</param>
public static void SetMoves(this PKM pk, ReadOnlySpan<ushort> input, bool maxPP = false)
{
Span<ushort> moves = stackalloc ushort[4];
if (input.Length <= 4)
input.CopyTo(moves);
else
input[..4].CopyTo(moves);
// Remote all indexes with a value above the maximum move ID allowed by the format.
var max = pk.MaxMoveID;
for (int i = 0; i < moves.Length; i++)
{
if (moves[i] > max)
moves[i] = 0;
pk.SetMaximumPPCurrent(moves);
static int GetPPUpCount(ushort moveID)
{
if (Legal.IsPPUpAvailable(moveID))
return 3;
return 0;
}
}
pk.SetMoves(moves);
if (maxPP && Legal.IsPPUpAvailable(pk))
/// <summary>
/// Sets the individual PP Up count values depending on if a Move is present in the move slot or not.
/// </summary>
public void SetMaximumPPUps()
{
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoves(moves);
pk.SetMaximumPPUps(moves);
pk.FixMoves();
}
}
/// <summary>
/// Updates the individual PP count values for each move slot based on the maximum possible value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known). Will fetch the current <see cref="PKM.Moves"/> if not provided.</param>
public static void SetMaximumPPCurrent(this PKM pk, ReadOnlySpan<ushort> moves)
{
pk.Move1_PP = moves.Length == 0 ? 0 : pk.GetMovePP(moves[0], pk.Move1_PPUps);
pk.Move2_PP = moves.Length <= 1 ? 0 : pk.GetMovePP(moves[1], pk.Move2_PPUps);
pk.Move3_PP = moves.Length <= 2 ? 0 : pk.GetMovePP(moves[2], pk.Move3_PPUps);
pk.Move4_PP = moves.Length <= 3 ? 0 : pk.GetMovePP(moves[3], pk.Move4_PPUps);
}
/// <summary>
/// Updates the <see cref="PKM.Moves"/> and updates the current PP counts.
/// </summary>
/// <param name="input"><see cref="PKM.Moves"/> to set.</param>
/// <param name="maxPP">Option to maximize PP Ups</param>
public void SetMoves(ReadOnlySpan<ushort> input, bool maxPP = false)
{
Span<ushort> moves = stackalloc ushort[4];
if (input.Length <= 4)
input.CopyTo(moves);
else
input[..4].CopyTo(moves);
/// <summary>
/// Updates the individual PP count values for each move slot based on the maximum possible value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known). Will fetch the current <see cref="PKM.Moves"/> if not provided.</param>
public static void SetMaximumPPCurrent(this PKM pk, Moveset moves)
{
pk.Move1_PP = moves.Move1 == 0 ? 0 : pk.GetMovePP(moves.Move1, pk.Move1_PPUps);
pk.Move2_PP = moves.Move2 == 0 ? 0 : pk.GetMovePP(moves.Move2, pk.Move2_PPUps);
pk.Move3_PP = moves.Move3 == 0 ? 0 : pk.GetMovePP(moves.Move3, pk.Move3_PPUps);
pk.Move4_PP = moves.Move4 == 0 ? 0 : pk.GetMovePP(moves.Move4, pk.Move4_PPUps);
}
// Remote all indexes with a value above the maximum move ID allowed by the format.
var max = pk.MaxMoveID;
for (int i = 0; i < moves.Length; i++)
{
if (moves[i] > max)
moves[i] = 0;
}
/// <summary>
/// Updates the individual PP count values for each move slot based on the maximum possible value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMaximumPPCurrent(this PKM pk)
{
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoves(moves);
pk.SetMaximumPPCurrent(moves);
}
pk.SetMoves(moves);
if (maxPP && Legal.IsPPUpAvailable(pk))
pk.SetMaximumPPUps(moves);
pk.FixMoves();
}
/// <summary>
/// Refreshes the Move PP for the desired move.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Move PP to refresh.</param>
public static void SetSuggestedMovePP(this PKM pk, int index) => pk.HealPPIndex(index);
/// <summary>
/// Updates the individual PP count values for each move slot based on the maximum possible value.
/// </summary>
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known).</param>
public void SetMaximumPPCurrent(ReadOnlySpan<ushort> moves)
{
// In some games, move[i] == 0` *should* set 0, but the game's configuration has a non-zero PP for `(None)`
// (I'm looking at you, S/V and Z-A)
pk.Move1_PP = pk.GetMovePP(moves.Length > 0 ? moves[0] : (ushort)0, pk.Move1_PPUps);
pk.Move2_PP = pk.GetMovePP(moves.Length > 1 ? moves[1] : (ushort)0, pk.Move2_PPUps);
pk.Move3_PP = pk.GetMovePP(moves.Length > 2 ? moves[2] : (ushort)0, pk.Move3_PPUps);
pk.Move4_PP = pk.GetMovePP(moves.Length > 3 ? moves[3] : (ushort)0, pk.Move4_PPUps);
}
}
}

View File

@ -7,130 +7,109 @@ namespace PKHeX.Core;
/// </summary>
public static class MoveSetApplicator
{
/// <summary>
/// Applies a new legal moveset to the <see cref="pk"/>, with option to apply random moves instead.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="random">True to apply a random moveset, false to apply a level-up moveset.</param>
public static void SetMoveset(this PKM pk, bool random = false)
extension(PKM pk)
{
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoveSet(moves, random);
pk.SetMoves(moves);
}
/// <summary>
/// Applies the suggested Relearn Moves to the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="la">Legality Analysis to use.</param>
public static void SetRelearnMoves(this PKM pk, LegalityAnalysis la)
{
Span<ushort> moves = stackalloc ushort[4];
la.GetSuggestedRelearnMoves(moves);
pk.SetRelearnMoves(moves);
}
/// <summary>
/// Gets a moveset for the provided <see cref="PKM"/> data.
/// </summary>
/// <param name="pk">PKM to generate for</param>
/// <param name="moves">Result storage</param>
/// <param name="random">Full movepool &amp; shuffling</param>
/// <returns>4 moves</returns>
public static void GetMoveSet(this PKM pk, Span<ushort> moves, bool random = false)
{
var la = new LegalityAnalysis(pk);
la.GetMoveSet(moves, random);
if (random)
return;
var clone = pk.Clone();
clone.SetMoves(moves);
var newLa = new LegalityAnalysis(clone);
if (newLa.Valid)
return;
// ReSharper disable once TailRecursiveCall
GetMoveSet(pk, moves, true);
}
/// <summary>
/// Gets a moveset for the provided <see cref="PKM"/> data.
/// </summary>
/// <param name="la">Precomputed optional</param>
/// <param name="moves">Result storage</param>
/// <param name="random">Full movepool &amp; shuffling</param>
/// <returns>4 moves</returns>
public static void GetMoveSet(this LegalityAnalysis la, Span<ushort> moves, bool random = false)
{
la.GetSuggestedCurrentMoves(moves, random ? MoveSourceType.All : MoveSourceType.Encounter);
if (random && !la.Entity.IsEgg)
Util.Rand.Shuffle(moves);
}
/// <summary>
/// Fetches <see cref="PKM.RelearnMoves"/> based on the provided <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="legal"><see cref="LegalityAnalysis"/> which contains parsed information pertaining to legality.</param>
/// <param name="moves">Result storage</param>
/// <param name="enc">Encounter the relearn moves should be suggested for. If not provided, will use the original encounter from the analysis. </param>
/// <returns><see cref="PKM.RelearnMoves"/> best suited for the current <see cref="PKM"/> data.</returns>
public static void GetSuggestedRelearnMoves(this LegalityAnalysis legal, Span<ushort> moves, IEncounterTemplate? enc = null)
{
enc ??= legal.EncounterOriginal;
legal.GetSuggestedRelearnMovesFromEncounter(moves, enc);
if (moves[0] != 0)
return;
if (enc is MysteryGift or EncounterEgg)
return;
if (enc is EncounterSlot6AO {CanDexNav: true} dn)
/// <summary>
/// Applies a new legal moveset to the <see cref="pk"/>, with option to apply random moves instead.
/// </summary>
/// <param name="random">True to apply a random moveset, false to apply a level-up moveset.</param>
public void SetMoveset(bool random = false)
{
var chk = legal.Info.Moves;
for (int i = 0; i < chk.Length; i++)
{
if (!chk[i].ShouldBeInRelearnMoves())
continue;
var move = legal.Entity.GetMove(i);
if (!dn.CanBeDexNavMove(move))
continue;
moves.Clear();
moves[0] = move;
return;
}
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoveSet(moves, random);
pk.SetMoves(moves);
}
if (enc is EncounterSlot8b { IsUnderground: true } ug)
/// <summary>
/// Applies the suggested Relearn Moves to the <see cref="pk"/>.
/// </summary>
/// <param name="la">Legality Analysis to use.</param>
public void SetRelearnMoves(LegalityAnalysis la)
{
var chk = legal.Info.Moves;
for (int i = 0; i < chk.Length; i++)
{
if (!chk[i].ShouldBeInRelearnMoves())
continue;
var move = legal.Entity.GetMove(i);
if (!ug.CanBeUndergroundMove(move))
continue;
moves.Clear();
moves[0] = move;
return;
}
if (ug.GetBaseEggMove(out var any))
{
moves.Clear();
moves[0] = any;
return;
}
Span<ushort> moves = stackalloc ushort[4];
la.GetSuggestedRelearnMoves(moves);
pk.SetRelearnMoves(moves);
}
var encounter = EncounterSuggestion.GetSuggestedMetInfo(legal.Entity);
if (encounter is IRelearn {Relearn: {HasMoves:true} r})
r.CopyTo(moves);
/// <summary>
/// Gets a moveset for the provided <see cref="PKM"/> data.
/// </summary>
/// <param name="moves">Result storage</param>
/// <param name="random">Full movepool &amp; shuffling</param>
/// <returns>4 moves</returns>
public void GetMoveSet(Span<ushort> moves, bool random = false)
{
var la = new LegalityAnalysis(pk);
la.GetMoveSet(moves, random);
if (random)
return;
var clone = pk.Clone();
clone.SetMoves(moves);
var newLa = new LegalityAnalysis(clone);
if (!newLa.Valid)
newLa.GetMoveSet(moves, true);
}
}
extension(LegalityAnalysis la)
{
/// <summary>
/// Gets a moveset for the provided <see cref="PKM"/> data.
/// </summary>
/// <param name="moves">Result storage</param>
/// <param name="random">Full movepool &amp; shuffling</param>
/// <returns>4 moves</returns>
public void GetMoveSet(Span<ushort> moves, bool random = false)
{
la.GetSuggestedCurrentMoves(moves, random ? MoveSourceType.All : MoveSourceType.Encounter);
if (random && !la.Entity.IsEgg)
Util.Rand.Shuffle(moves);
}
/// <summary>
/// Fetches <see cref="PKM.RelearnMoves"/> based on the provided <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="moves">Result storage</param>
/// <param name="enc">Encounter the relearn moves should be suggested for. If not provided, will use the original encounter from the analysis. </param>
/// <returns><see cref="PKM.RelearnMoves"/> best suited for the current <see cref="PKM"/> data.</returns>
public void GetSuggestedRelearnMoves(Span<ushort> moves, IEncounterTemplate? enc = null)
{
enc ??= la.EncounterOriginal;
la.GetSuggestedRelearnMovesFromEncounter(moves, enc);
if (moves[0] != 0)
return;
if (enc is MysteryGift or IEncounterEgg)
return;
if (enc is ISingleMoveBonus {IsMoveBonusPossible: true} bonus)
{
var chk = la.Info.Moves;
for (int i = 0; i < chk.Length; i++)
{
if (!chk[i].ShouldBeInRelearnMoves())
continue;
var move = la.Entity.GetMove(i);
if (!bonus.IsMoveBonus(move))
continue;
moves.Clear();
moves[0] = move;
return;
}
if (bonus.IsMoveBonusRequired && bonus.TryGetRandomMoveBonus(out var bonusMove))
{
moves.Clear();
moves[0] = bonusMove;
return;
}
}
var encounter = EncounterSuggestion.GetSuggestedMetInfo(la.Entity);
if (encounter is IRelearn {Relearn: {HasMoves:true} r})
r.CopyTo(moves);
}
}
}

View File

@ -7,134 +7,183 @@ namespace PKHeX.Core;
/// </summary>
public static class MoveShopRecordApplicator
{
/// <summary>
/// Clears all the "purchased" and "mastered" move shop flags.
/// </summary>
public static void ClearMoveShopFlags(this IMoveShop8 shop)
extension(IMoveShop8 shop)
{
var bits = shop.Permit;
for (int i = 0; i < bits.RecordCountUsed; i++)
shop.SetPurchasedRecordFlag(i, false);
if (shop is IMoveShop8Mastery m)
m.ClearMoveShopFlagsMastered();
}
/// <summary>
/// Clears all the "mastered" move shop flags.
/// </summary>
/// <param name="shop"></param>
public static void ClearMoveShopFlagsMastered(this IMoveShop8Mastery shop)
{
var bits = shop.Permit;
for (int i = 0; i < bits.RecordCountUsed; i++)
shop.SetMasteredRecordFlag(i, false);
}
/// <summary>
/// Sets the required move shop flags for the requested entity.
/// </summary>
public static void SetMoveShopFlags(this IMoveShop8Mastery shop, PKM pk)
{
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoves(moves);
shop.SetMoveShopFlags(moves, pk);
}
/// <summary>
/// Sets the required move shop flags for the requested entity.
/// </summary>
public static void SetMoveShopFlags(this IMoveShop8Mastery shop, ReadOnlySpan<ushort> moves, PKM pk)
{
var (learn, mastery) = LearnSource8LA.GetLearnsetAndMastery(pk.Species, pk.Form);
shop.SetMoveShopFlags(moves, learn, mastery, pk.CurrentLevel);
}
/// <summary>
/// Sets all possible move shop flags for the requested entity.
/// </summary>
public static void SetMoveShopFlagsAll(this IMoveShop8Mastery shop, PKM pk)
{
var (learn, mastery) = LearnSource8LA.GetLearnsetAndMastery(pk.Species, pk.Form);
shop.SetMoveShopFlagsAll(learn, mastery, pk.CurrentLevel);
}
/// <summary>
/// Sets all possible move shop flags for the requested entity.
/// </summary>
public static void SetMoveShopFlagsAll(this IMoveShop8Mastery shop, Learnset learn, Learnset mastery, int level)
{
var permit = shop.Permit;
var possible = permit.RecordPermitIndexes;
for (int index = 0; index < permit.RecordCountUsed; index++)
/// <summary>
/// Clears all the "purchased" and "mastered" move shop flags.
/// </summary>
public void ClearMoveShopFlags()
{
var allowed = permit.IsRecordPermitted(index);
if (!allowed)
continue;
var bits = shop.Permit;
for (int i = 0; i < bits.RecordCountUsed; i++)
shop.SetPurchasedRecordFlag(i, false);
var move = possible[index];
SetMasteredFlag(shop, learn, mastery, level, index, move);
if (shop is IMoveShop8Mastery m)
m.ClearMoveShopFlagsMastered();
}
}
/// <summary>
/// Sets all move shop flags for the currently known moves.
/// </summary>
public static void SetMoveShopFlags(this IMoveShop8Mastery shop, ReadOnlySpan<ushort> moves, Learnset learn, Learnset mastery, int level)
extension(IMoveShop8Mastery shop)
{
var permit = shop.Permit;
var possible = permit.RecordPermitIndexes;
foreach (var move in moves)
/// <summary>
/// Clears all the "mastered" move shop flags.
/// </summary>
public void ClearMoveShopFlagsMastered()
{
var index = possible.IndexOf(move);
if (index == -1)
continue;
if (!permit.IsRecordPermitted(index))
continue;
SetMasteredFlag(shop, learn, mastery, level, index, move);
}
}
/// <summary>
/// Sets the "mastered" move shop flag for the requested move.
/// </summary>
public static void SetMasteredFlag(this IMoveShop8Mastery shop, Learnset learn, Learnset mastery, int level, int index, ushort move)
{
if (shop.GetMasteredRecordFlag(index))
return;
if (level < (uint)learn.GetLevelLearnMove(move)) // Can't learn it yet; must purchase.
{
shop.SetPurchasedRecordFlag(index, true);
shop.SetMasteredRecordFlag(index, true);
return;
var bits = shop.Permit;
for (int i = 0; i < bits.RecordCountUsed; i++)
shop.SetMasteredRecordFlag(i, false);
}
if (level < (uint)mastery.GetLevelLearnMove(move)) // Can't master it yet; must Seed of Mastery
shop.SetMasteredRecordFlag(index, true);
}
/// <summary>
/// Sets the "mastered" move shop flag for the encounter.
/// </summary>
public static void SetEncounterMasteryFlags(this IMoveShop8Mastery shop, ReadOnlySpan<ushort> moves, Learnset mastery, int level)
{
var permit = shop.Permit;
var possible = permit.RecordPermitIndexes;
foreach (var move in moves)
/// <summary>
/// Sets the required move shop flags for the requested entity.
/// </summary>
public void SetMoveShopFlags(PKM pk)
{
var index = possible.IndexOf(move);
if (index == -1)
continue;
if (!permit.IsRecordPermitted(index))
continue;
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoves(moves);
shop.SetMoveShopFlags(moves, pk);
}
// If the Pokémon is caught with any move shop move in its learnset
// and it is high enough level to master it, the game will automatically
// give it the "Mastered" flag but not the "Purchased" flag
// For moves that are not in the learnset, it returns -1 which is true, thus set as mastered.
if (level >= mastery.GetLevelLearnMove(move))
shop.SetMasteredRecordFlag(index, true);
/// <summary>
/// Sets the required move shop flags for the requested entity.
/// </summary>
public void SetMoveShopFlags(ReadOnlySpan<ushort> moves, PKM pk)
{
var (learn, mastery) = LearnSource8LA.GetLearnsetAndMastery(pk.Species, pk.Form);
shop.SetMoveShopFlags(moves, learn, mastery, pk.CurrentLevel);
}
/// <summary>
/// Sets all possible move shop flags for the requested entity.
/// </summary>
public void SetMoveShopFlagsAll(PKM pk)
{
var (learn, mastery) = LearnSource8LA.GetLearnsetAndMastery(pk.Species, pk.Form);
shop.SetMoveShopFlagsAll(learn, mastery, pk.CurrentLevel);
}
/// <summary>
/// Sets all possible move shop flags for the requested entity.
/// </summary>
public void SetMoveShopFlagsAll(Learnset learn, Learnset mastery, byte level)
{
var permit = shop.Permit;
var possible = permit.RecordPermitIndexes;
for (int index = 0; index < permit.RecordCountUsed; index++)
{
var allowed = permit.IsRecordPermitted(index);
if (!allowed)
continue;
var move = possible[index];
shop.SetMasteredFlag(learn, mastery, level, index, move);
}
}
/// <summary>
/// Sets all move shop flags for the currently known moves.
/// </summary>
public void SetMoveShopFlags(ReadOnlySpan<ushort> moves, Learnset learn, Learnset mastery, byte level)
{
var permit = shop.Permit;
var possible = permit.RecordPermitIndexes;
foreach (var move in moves)
{
var index = possible.IndexOf(move);
if (index == -1)
continue;
if (!permit.IsRecordPermitted(index))
continue;
shop.SetMasteredFlag(learn, mastery, level, index, move);
}
}
/// <summary>
/// Sets the "mastered" move shop flag for the requested move.
/// </summary>
public void SetMasteredFlag(Learnset learn, Learnset mastery, byte level, int index, ushort move)
{
if (shop.GetMasteredRecordFlag(index))
return;
if (learn.TryGetLevelLearnMove(move, out var learnLevel))
{
if (level < learnLevel) // Can't learn it yet; must purchase.
{
shop.SetPurchasedRecordFlag(index, true);
shop.SetMasteredRecordFlag(index, true);
return;
}
if (mastery.TryGetLevelLearnMove(move, out var masterLevel) && level < masterLevel) // Can't master it yet; must Seed of Mastery
shop.SetMasteredRecordFlag(index, true);
// Otherwise, is innately mastered, no need to force the flag.
}
else // Can't learn it without purchasing.
{
if (shop.GetPurchasedRecordFlag(index))
shop.SetMasteredRecordFlag(index, true);
}
}
/// <summary>
/// Sets the "mastered" move shop flag for the encounter.
/// </summary>
public void SetEncounterMasteryFlags(ReadOnlySpan<ushort> moves, Learnset mastery, byte metLevel, ushort alphaMove)
{
var permit = shop.Permit;
var possible = permit.RecordPermitIndexes;
foreach (var move in moves)
{
var index = possible.IndexOf(move);
if (index == -1)
continue;
if (!permit.IsRecordPermitted(index))
continue;
// If the Pokémon is caught with any move shop move in its learnset,
// and it is high enough level to master it, the game will automatically
// give it the "Mastered" flag but not the "Purchased" flag
// For moves that are not in the learnset, set as mastered.
if (!mastery.TryGetLevelLearnMove(move, out var masteryLevel) || metLevel >= masteryLevel)
shop.SetMasteredRecordFlag(index, true);
}
if (alphaMove != 0)
{
var index = possible.IndexOf(alphaMove);
if (index != -1)
shop.SetMasteredRecordFlag(index, true);
}
}
/// <summary>
/// Sets the "purchased" move shop flag for all possible moves.
/// </summary>
public void SetPurchasedFlagsAll(PKM pk)
{
var (learn, _) = LearnSource8LA.GetLearnsetAndMastery(pk.Species, pk.Form);
var level = pk.CurrentLevel;
var alpha = pk is PA8 pa ? pa.AlphaMove : (ushort)0;
var permit = shop.Permit;
for (int index = 0; index < permit.RecordCountUsed; index++)
{
var allowed = permit.IsRecordPermitted(index);
if (!allowed)
continue;
// If it can learn it naturally, it can't be purchased anymore.
var move = permit.RecordPermitIndexes[index];
if (learn.TryGetLevelLearnMove(move, out var learnLevel) && learnLevel <= level)
continue;
// Skip purchasing alpha moves, even though it was possible on early versions of the game.
if (move == alpha)
continue;
shop.SetPurchasedRecordFlag(index, true);
}
}
}
}

View File

@ -0,0 +1,67 @@
using System;
using static PKHeX.Core.Ball;
using static PKHeX.Core.PersonalColor;
namespace PKHeX.Core;
public static class PersonalColorUtil
{
public static PersonalColor GetColor(PKM pk)
{
// Gen1/2 don't store color in personal info
if (pk.Format < 3)
return (PersonalColor)PersonalTable.USUM[pk.Species, 0].Color;
return (PersonalColor)pk.PersonalInfo.Color;
}
public static PersonalColor GetColor(IEncounterTemplate enc)
{
// Gen1/2 don't store color in personal info
if (enc.Generation < 3)
return (PersonalColor)PersonalTable.USUM[enc.Species, 0].Color;
var pt = GameData.GetPersonal(enc.Version);
var pi = pt[enc.Species, enc.Form];
return (PersonalColor)pi.Color;
}
public static ReadOnlySpan<Ball> GetPreferredByColor(IEncounterTemplate enc) => GetPreferredByColor(enc, GetColor(enc));
public static ReadOnlySpan<Ball> GetPreferredByColor<T>(T enc, PersonalColor color) where T : IContext
{
if (enc.Context is EntityContext.Gen8a)
return GetPreferredByColorLA(color);
return GetPreferredByColor(color);
}
/// <summary>
/// Priority Match ball IDs that match the color ID
/// </summary>
public static ReadOnlySpan<Ball> GetPreferredByColor(PersonalColor color) => color switch
{
Red => [Repeat, Fast, Heal, Great, Dream, Lure],
Blue => [Dive, Net, Great, Lure, Beast],
Yellow => [Level, Ultra, Repeat, Quick, Moon],
Green => [Safari, Friend, Nest, Dusk],
Black => [Luxury, Heavy, Ultra, Moon, Net, Beast],
Brown => [Level, Heavy],
Purple => [Master, Love, Heal, Dream],
Gray => [Heavy, Premier, Luxury],
White => [Premier, Timer, Luxury, Ultra],
_ => [Love, Heal, Dream],
};
public static ReadOnlySpan<Ball> GetPreferredByColorLA(PersonalColor color) => color switch
{
Red => [LAPoke],
Blue => [LAFeather, LAGreat, LAJet],
Yellow => [LAUltra],
Green => [LAPoke],
Black => [LAGigaton, LALeaden, LAHeavy, LAUltra],
Brown => [LAPoke],
Purple => [LAPoke],
Gray => [LAGigaton, LALeaden, LAHeavy],
White => [LAWing, LAJet],
_ => [LAPoke],
};
}

View File

@ -0,0 +1,211 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Logic for modifying the Plus Record flags of a <see cref="PA9"/>.
/// </summary>
public static class PlusRecordApplicator
{
extension(IPlusRecord record)
{
/// <summary>
/// Sets all the Plus Record flags for the <see cref="record"/> to the given value.
/// </summary>
/// <param name="count">Total count of flags to modify [0,x).</param>
/// <param name="value">Value to set for each record.</param>
public void SetPlusFlagsAll(int count, bool value)
{
for (int i = 0; i < count; i++)
record.SetMovePlusFlag(i, value);
}
/// <summary>
/// Clears the Plus Record flags for the <see cref="record"/>.
/// </summary>
/// <param name="count">Total count of flags to modify [0,x).</param>
public void ClearPlusFlags(int count) => record.SetPlusFlagsAll(count, false);
/// <summary>
/// Sets the Plus Record flags for the <see cref="record"/> based on the legality of learning moves.
/// </summary>
/// <param name="permit">Sanity check to retrieve plus record indexes.</param>
/// <param name="la">Legality analysis of the Pokémon.</param>
/// <param name="seedOfMastery">Use a Seed of Mastery to bypass the level requirement of mastering the move.</param>
/// <param name="tm">Apply TM flags as Plus too.</param>
public void SetPlusFlags(IPermitPlus permit, LegalityAnalysis la, bool seedOfMastery, bool tm)
{
// Hopefully this is only called for Legends: Z-A format entities.
var entity = la.Entity;
var context = entity.Context;
var evos = la.Info.EvoChainsAllGens.Get(context);
switch (la.Entity)
{
case PA9 pa9:
{
var learn = LearnSource9ZA.Instance;
record.SetPlusFlagsNatural(permit, evos, learn, seedOfMastery);
if (pa9 is { IsAlpha: true, ZA: true })
{
var table = PersonalTable.ZA;
var enc = la.EncounterMatch;
var epi = table[enc.Species, enc.Form];
pa9.SetPlusFlagsSpecific(epi, epi.AlphaMove);
}
if (tm)
{
var table = PersonalTable.ZA;
record.SetPlusFlagsTM<PersonalTable9ZA, PersonalInfo9ZA>(permit, evos, table);
}
break;
}
default:
throw new Exception("Format not supported.");
}
}
public void SetPlusFlags(PKM pk, IPermitPlus permit, PlusRecordApplicatorOption option)
{
record.ClearPlusFlags(permit.PlusCountTotal);
if (option is PlusRecordApplicatorOption.None)
return;
if (option is PlusRecordApplicatorOption.ForceAll)
{
record.SetPlusFlagsAll(permit.PlusCountUsed, true);
return;
}
var la = new LegalityAnalysis(pk);
record.SetPlusFlagsInternal(permit, option, la);
}
public void SetPlusFlags(IPermitPlus permit, PlusRecordApplicatorOption option, LegalityAnalysis la)
{
record.ClearPlusFlags(permit.PlusCountTotal);
if (option is PlusRecordApplicatorOption.None)
return;
if (option is PlusRecordApplicatorOption.ForceAll)
{
record.SetPlusFlagsAll(permit.PlusCountUsed, true);
return;
}
record.SetPlusFlagsInternal(permit, option, la);
}
public void SetPlusFlagsNatural<TSource>(IPermitPlus permit, ReadOnlySpan<EvoCriteria> evos, TSource source, bool seedOfMastery) where TSource : ILearnSourceBonus
{
var indexes = permit.PlusMoveIndexes;
foreach (var evo in evos)
{
record.SetPlusFlagsNatural(indexes, evo, source, seedOfMastery);
if (evo.Form != 0 && evo.Species is (int)Species.Rotom or (int)Species.Hoopa)
record.SetPlusFlagsNatural(indexes, evo with { Form = 0 }, source, seedOfMastery);
}
}
private void SetPlusFlagsNatural<TSource>(ReadOnlySpan<ushort> indexes, EvoCriteria evo, TSource source, bool seedOfMastery) where TSource : ILearnSourceBonus
{
var (levelUp, plus) = source.GetLearnsetAndOther(evo.Species, evo.Form);
var set = seedOfMastery ? levelUp : plus;
var levels = set.GetAllLevels();
var moves = set.GetAllMoves();
for (int i = 0; i < levels.Length; i++)
{
if (evo.LevelMax < levels[i])
break;
var move = moves[i];
var index = indexes.IndexOf(move);
record.SetMovePlusFlag(index);
}
}
/// <summary>
/// Sets all moves that would be learned and naturally available as Plus based on the given level
/// </summary>
/// <param name="permit">Permit to use</param>
/// <param name="plus">Learnset to use</param>
/// <param name="level">Current level</param>
/// <param name="extra">Extra moves to set as Plus</param>
public void SetPlusFlagsEncounter(IPermitPlus permit, Learnset plus, byte level, params ReadOnlySpan<ushort> extra)
{
var indexes = permit.PlusMoveIndexes;
var levels = plus.GetAllLevels();
var moves = plus.GetAllMoves();
for (int i = 0; i < levels.Length; i++)
{
if (level < levels[i])
break;
var move = moves[i];
var index = indexes.IndexOf(move);
record.SetMovePlusFlag(index);
}
if (extra.Length != 0)
record.SetPlusFlagsSpecific(permit, extra);
}
public void SetPlusFlagsSpecific(IPermitPlus permit, ushort move)
{
var indexes = permit.PlusMoveIndexes;
var index = indexes.IndexOf(move);
record.SetMovePlusFlag(index);
}
public void SetPlusFlagsSpecific(IPermitPlus permit, params ReadOnlySpan<ushort> extra)
{
var indexes = permit.PlusMoveIndexes;
foreach (var move in extra)
{
var index = indexes.IndexOf(move);
record.SetMovePlusFlag(index);
}
}
private void SetPlusFlagsInternal(IPermitPlus permit, PlusRecordApplicatorOption option, LegalityAnalysis la)
{
if (option is PlusRecordApplicatorOption.LegalCurrent)
record.SetPlusFlags(permit, la, false, false);
else if (option is PlusRecordApplicatorOption.LegalCurrentTM)
record.SetPlusFlags(permit, la, false, true);
else if (option is PlusRecordApplicatorOption.LegalSeedTM)
record.SetPlusFlags(permit, la, true, true);
}
public void SetPlusFlagsTM<TTable, TInfo>(IPermitPlus permit, ReadOnlySpan<EvoCriteria> evos, TTable table)
where TTable : IPersonalTable<TInfo>
where TInfo : IPersonalInfo, IPersonalInfoTM
{
var indexes = permit.PlusMoveIndexes;
foreach (var evo in evos)
{
var pi = table[evo.Species, evo.Form];
for (int index = 0; index < indexes.Length; index++)
{
var move = indexes[index];
var tmIndex = permit.RecordPermitIndexes.IndexOf(move);
if (tmIndex != -1 && pi.GetIsLearnTM(tmIndex))
record.SetMovePlusFlag(index);
}
}
}
}
public static void SetPlusFlags<T>(this T pk, IPermitPlus permit, PlusRecordApplicatorOption option)
where T : PKM, IPlusRecord
=> pk.SetPlusFlags(pk, permit, option);
}
public enum PlusRecordApplicatorOption
{
None,
ForceAll,
LegalCurrent,
LegalCurrentTM,
LegalSeedTM,
}

View File

@ -14,15 +14,34 @@ public static class RibbonApplicator
public static void SetAllValidRibbons(PKM pk) => SetAllValidRibbons(new LegalityAnalysis(pk));
/// <inheritdoc cref="SetAllValidRibbons(PKM)"/>
public static void SetAllValidRibbons(LegalityAnalysis la)
public static void SetAllValidRibbons(LegalityAnalysis la) => SetAllValidRibbons(la.Entity, la.EncounterMatch, la.Info.EvoChainsAllGens);
/// <inheritdoc cref="SetAllValidRibbons(PKM)"/>
public static void SetAllValidRibbons(PKM pk, IEncounterTemplate enc, EvolutionHistory history)
{
var args = new RibbonVerifierArguments(la.Entity, la.EncounterMatch, la.Info.EvoChainsAllGens);
var args = new RibbonVerifierArguments(pk, enc, history);
SetAllRibbonState(args, true);
FixInvalidRibbons(args);
// Ribbon Deadlock
if (la.Entity is IRibbonSetCommon6 c6)
if (pk.IsEgg)
return;
if (pk is IRibbonSetCommon6 c6)
{
// Medal Deadlock
if (pk is ISuperTrain s && history.HasVisitedGen6)
{
s.SuperTrainBitFlags = RibbonRules.SetSuperTrainSupremelyTrained(s.SuperTrainBitFlags);
if (pk.Format == 6) // cleared on 6->7 transfer; only set in Gen6.
{
s.SecretSuperTrainingUnlocked = true;
s.SuperTrainSupremelyTrained = true;
}
c6.RibbonTraining = true;
}
// Ribbon Deadlock
InvertDeadlockContest(c6, true);
}
}
/// <summary>
@ -34,7 +53,16 @@ public static void SetAllValidRibbons(LegalityAnalysis la)
/// <inheritdoc cref="RemoveAllValidRibbons(PKM)"/>
public static void RemoveAllValidRibbons(LegalityAnalysis la)
{
var args = new RibbonVerifierArguments(la.Entity, la.EncounterMatch, la.Info.EvoChainsAllGens);
var pk = la.Entity;
var enc = la.EncounterMatch;
var history = la.Info.EvoChainsAllGens;
RemoveAllValidRibbons(pk, enc, history);
}
/// <inheritdoc cref="RemoveAllValidRibbons(PKM)"/>
public static void RemoveAllValidRibbons(PKM pk, IEncounterTemplate enc, EvolutionHistory history)
{
var args = new RibbonVerifierArguments(pk, enc, history);
SetAllRibbonState(args, false);
FixInvalidRibbons(args);
}
@ -42,7 +70,7 @@ public static void RemoveAllValidRibbons(LegalityAnalysis la)
/// <summary>
/// Parses the Entity for all ribbons, then fixes any ribbon that was invalid.
/// </summary>
public static void FixInvalidRibbons(RibbonVerifierArguments args)
public static void FixInvalidRibbons(in RibbonVerifierArguments args)
{
Span<RibbonResult> result = stackalloc RibbonResult[RibbonVerifier.MaxRibbonCount];
var count = RibbonVerifier.GetRibbonResults(args, result);
@ -50,7 +78,7 @@ public static void FixInvalidRibbons(RibbonVerifierArguments args)
ribbon.Fix(args);
}
private static void SetAllRibbonState(RibbonVerifierArguments args, bool desiredState)
private static void SetAllRibbonState(in RibbonVerifierArguments args, bool desiredState)
{
for (RibbonIndex3 r = 0; r < RibbonIndex3.MAX_COUNT; r++)
r.Fix(args, desiredState);
@ -59,7 +87,7 @@ private static void SetAllRibbonState(RibbonVerifierArguments args, bool desired
if (desiredState)
{
// Skip Marks, don't set them.
// Skip personality marks (Encounter specific, never required); don't set them.
for (RibbonIndex r = 0; r <= RibbonIndex.MasterRank; r++)
r.Fix(args, desiredState);
for (RibbonIndex r = RibbonIndex.Hisui; r < RibbonIndex.MAX_COUNT; r++)
@ -75,6 +103,7 @@ private static void SetAllRibbonState(RibbonVerifierArguments args, bool desired
private static void InvertDeadlockContest(IRibbonSetCommon6 c6, bool desiredState)
{
// Contest Star is a deadlock ribbon with the Master ribbons, as it needs all five Master ribbons to be true.
if (desiredState)
c6.RibbonContestStar = c6.HasAllContestRibbons();
}

View File

@ -1,89 +1,154 @@
using System;
using static PKHeX.Core.TechnicalRecordApplicatorOption;
namespace PKHeX.Core;
/// <summary>
/// Logic for modifying the Technical Record flags of a <see cref="PK8"/>.
/// Logic for modifying the Technical Record flags of a <see cref="ITechRecord"/>.
/// </summary>
public static class TechnicalRecordApplicator
{
/// <summary>
/// Sets the Technical Record flags for the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="value">Value to set for the record.</param>
/// <param name="max">Max record to set.</param>
public static void SetRecordFlagsAll(this ITechRecord pk, bool value, int max)
extension(ITechRecord record)
{
for (int i = 0; i < max; i++)
pk.SetMoveRecordFlag(i, value);
}
/// <summary>
/// Clears the Technical Record flags for the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void ClearRecordFlags(this ITechRecord pk) => pk.SetRecordFlagsAll(false, pk.Permit.RecordCountTotal);
/// <summary>
/// Sets the Technical Record flags for the <see cref="pk"/> based on the current moves.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves">Moves to set flags for. If a move is not a Technical Record, it is skipped.</param>
public static void SetRecordFlags(this ITechRecord pk, ReadOnlySpan<ushort> moves)
{
var permit = pk.Permit;
SetRecordFlags(pk, moves, permit);
}
/// <inheritdoc cref="SetRecordFlags(ITechRecord, ReadOnlySpan{ushort})"/>
public static void SetRecordFlags(ITechRecord pk, ReadOnlySpan<ushort> moves, IPermitRecord permit)
{
var moveIDs = permit.RecordPermitIndexes;
foreach (var m in moves)
/// <summary>
/// Sets the Technical Record flags for the <see cref="record"/>.
/// </summary>
/// <param name="value">Value to set for the record.</param>
/// <param name="max">Max record to set.</param>
public void SetRecordFlagsAll(bool value, int max)
{
var index = moveIDs.IndexOf(m);
if (index == -1)
continue;
if (permit.IsRecordPermitted(index))
pk.SetMoveRecordFlag(index);
for (int i = 0; i < max; i++)
record.SetMoveRecordFlag(i, value);
}
}
/// <summary>
/// Sets all the Technical Record flags for the <see cref="pk"/> if they are permitted to be learned in-game.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRecordFlagsAll(this ITechRecord pk)
{
var permit = pk.Permit;
SetRecordFlagsAll(pk, permit);
}
/// <summary>
/// Clears the Technical Record flags for the <see cref="record"/>.
/// </summary>
public void ClearRecordFlags() => record.SetRecordFlagsAll(false, record.Permit.RecordCountTotal);
/// <inheritdoc cref="SetRecordFlagsAll(PKHeX.Core.ITechRecord)"/>"/>
public static void SetRecordFlagsAll(ITechRecord pk, IPermitRecord permit)
{
for (int i = 0; i < permit.RecordCountUsed; i++)
/// <summary>
/// Sets the Technical Record flags for the <see cref="record"/> based on the current moves.
/// </summary>
/// <param name="moves">Moves to set flags for. If a move is not a Technical Record, it is skipped.</param>
public void SetRecordFlags(ReadOnlySpan<ushort> moves)
{
if (permit.IsRecordPermitted(i))
pk.SetMoveRecordFlag(i);
var permit = record.Permit;
record.SetRecordFlags(moves, permit);
}
}
private static void SetRecordFlags<TTable, TInfo>(this ITechRecord pk, ReadOnlySpan<ushort> moves, ReadOnlySpan<EvoCriteria> evos, TTable pt)
where TTable : IPersonalTable<TInfo> where TInfo : IPersonalInfo, IPermitRecord
{
foreach (var evo in evos)
SetRecordFlags(pk, moves, pt[evo.Species, evo.Form]);
}
private void SetRecordFlags<TTable, TInfo>(ReadOnlySpan<ushort> moves, ReadOnlySpan<EvoCriteria> evos, TTable pt)
where TTable : IPersonalTable<TInfo> where TInfo : IPersonalInfo, IPermitRecord
{
foreach (var evo in evos)
record.SetRecordFlags(moves, pt[evo.Species, evo.Form]);
}
/// <inheritdoc cref="SetRecordFlagsAll(ITechRecord)"/>
private static void SetRecordFlagsAll<TTable, TInfo>(this ITechRecord pk, ReadOnlySpan<EvoCriteria> evos, TTable pt)
where TTable : IPersonalTable<TInfo> where TInfo : IPersonalInfo, IPermitRecord
{
foreach (var evo in evos)
SetRecordFlagsAll(pk, pt[evo.Species, evo.Form]);
/// <inheritdoc cref="SetRecordFlagsAll(ITechRecord)"/>
private void SetRecordFlagsAll<TTable, TInfo>(ReadOnlySpan<EvoCriteria> evos, TTable pt)
where TTable : IPersonalTable<TInfo> where TInfo : IPersonalInfo, IPermitRecord
{
foreach (var evo in evos)
record.SetRecordFlagsAll(pt[evo.Species, evo.Form]);
}
/// <inheritdoc cref="SetRecordFlags(ITechRecord, ReadOnlySpan{ushort})"/>
public void SetRecordFlags(ReadOnlySpan<ushort> moves, ReadOnlySpan<EvoCriteria> evos)
{
if (record is PK9 pk9)
pk9.SetRecordFlags<PersonalTable9SV, PersonalInfo9SV>(moves, evos, PersonalTable.SV);
else if (record is PA9 pa9)
pa9.SetRecordFlags<PersonalTable9ZA, PersonalInfo9ZA>(moves, evos, PersonalTable.ZA);
else if (record is PK8 pk8)
pk8.SetRecordFlags<PersonalTable8SWSH, PersonalInfo8SWSH>(moves, evos, PersonalTable.SWSH);
}
/// <inheritdoc cref="SetRecordFlagsAll(ITechRecord)"/>
public void SetRecordFlagsAll(ReadOnlySpan<EvoCriteria> evos)
{
if (record is PK9 pk9)
pk9.SetRecordFlagsAll<PersonalTable9SV, PersonalInfo9SV>(evos, PersonalTable.SV);
else if (record is PA9 pa9)
pa9.SetRecordFlagsAll<PersonalTable9ZA, PersonalInfo9ZA>(evos, PersonalTable.ZA);
else if (record is PK8 pk8)
pk8.SetRecordFlagsAll<PersonalTable8SWSH, PersonalInfo8SWSH>(evos, PersonalTable.SWSH);
}
/// <inheritdoc cref="IPermitRecord.IsRecordPermitted"/>
public bool IsRecordPermitted(ReadOnlySpan<EvoCriteria> evos, int index) => record switch
{
PK9 => IsRecordPermitted<PersonalTable9SV, PersonalInfo9SV>(evos, PersonalTable.SV, index),
PA9 => IsRecordPermitted<PersonalTable9ZA, PersonalInfo9ZA>(evos, PersonalTable.ZA, index),
PK8 => IsRecordPermitted<PersonalTable8SWSH, PersonalInfo8SWSH>(evos, PersonalTable.SWSH, index),
_ => false,
};
/// <inheritdoc cref="SetRecordFlags(ITechRecord, PKM, TechnicalRecordApplicatorOption, LegalityAnalysis)"/>
public void SetRecordFlags(PKM pk, TechnicalRecordApplicatorOption option)
{
record.ClearRecordFlags();
if (option is None)
return;
if (option is ForceAll)
{
record.SetRecordFlagsAll(true, record.Permit.RecordCountUsed);
return;
}
var la = new LegalityAnalysis(pk);
SetRecordFlagsInternal(record, pk, option, la);
}
/// <summary>
/// Applies the Technical Record flags based on the <see cref="option"/>.
/// </summary>
/// <param name="pk">Object to apply to, but base type for other logic.</param>
/// <param name="option">Option to apply.</param>
/// <param name="la">Legality analysis to use for the option.</param>
public void SetRecordFlags(PKM pk, TechnicalRecordApplicatorOption option, LegalityAnalysis la)
{
record.ClearRecordFlags();
if (option is None)
return;
if (option is ForceAll)
{
record.SetRecordFlagsAll(true, record.Permit.RecordCountUsed);
return;
}
SetRecordFlagsInternal(record, pk, option, la);
}
/// <summary>
/// Sets all the Technical Record flags for the <see cref="record"/> if they are permitted to be learned in-game.
/// </summary>
public void SetRecordFlagsAll()
{
var permit = record.Permit;
record.SetRecordFlagsAll(permit);
}
/// <inheritdoc cref="SetRecordFlagsAll(PKHeX.Core.ITechRecord)"/>"/>
public void SetRecordFlagsAll(IPermitRecord permit)
{
for (int i = 0; i < permit.RecordCountUsed; i++)
{
if (permit.IsRecordPermitted(i))
record.SetMoveRecordFlag(i);
}
}
/// <inheritdoc cref="SetRecordFlags(ITechRecord, ReadOnlySpan{ushort})"/>
public void SetRecordFlags(ReadOnlySpan<ushort> moves, IPermitRecord permit)
{
var moveIDs = permit.RecordPermitIndexes;
foreach (var m in moves)
{
var index = moveIDs.IndexOf(m);
if (index == -1)
continue;
if (permit.IsRecordPermitted(index))
record.SetMoveRecordFlag(index);
}
}
}
/// <inheritdoc cref="IPermitRecord.IsRecordPermitted"/>
@ -98,29 +163,50 @@ public static void SetRecordFlagsAll(ITechRecord pk, IPermitRecord permit)
return false;
}
/// <inheritdoc cref="SetRecordFlags(ITechRecord, ReadOnlySpan{ushort})"/>
public static void SetRecordFlags(this ITechRecord pk, ReadOnlySpan<ushort> moves, ReadOnlySpan<EvoCriteria> evos)
{
if (pk is PK9 pk9)
SetRecordFlags<PersonalTable9SV, PersonalInfo9SV>(pk9, moves, evos, PersonalTable.SV);
else if (pk is PK8 pk8)
SetRecordFlags<PersonalTable8SWSH, PersonalInfo8SWSH>(pk8, moves, evos, PersonalTable.SWSH);
}
/// <inheritdoc cref="SetRecordFlags(ITechRecord, PKM, TechnicalRecordApplicatorOption, LegalityAnalysis)"/>
public static void SetRecordFlags<T>(this T pk, TechnicalRecordApplicatorOption option)
where T : PKM, ITechRecord
=> pk.SetRecordFlags(pk, option);
/// <inheritdoc cref="SetRecordFlagsAll(ITechRecord)"/>
public static void SetRecordFlagsAll(this ITechRecord pk, ReadOnlySpan<EvoCriteria> evos)
private static void SetRecordFlagsInternal(ITechRecord record, PKM pk, TechnicalRecordApplicatorOption option, LegalityAnalysis la)
{
if (pk is PK9 pk9)
SetRecordFlagsAll<PersonalTable9SV, PersonalInfo9SV>(pk9, evos, PersonalTable.SV);
else if (pk is PK8 pk8)
SetRecordFlagsAll<PersonalTable8SWSH, PersonalInfo8SWSH>(pk8, evos, PersonalTable.SWSH);
if (option is LegalCurrent)
{
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoves(moves);
var evos = la.Info.EvoChainsAllGens.Get(pk.Context);
record.SetRecordFlags(moves, evos);
}
else if (option is LegalAll)
{
var evos = la.Info.EvoChainsAllGens.Get(pk.Context);
record.SetRecordFlagsAll(evos);
}
}
/// <inheritdoc cref="IPermitRecord.IsRecordPermitted"/>
public static bool IsRecordPermitted(this ITechRecord pk, ReadOnlySpan<EvoCriteria> evos, int index) => pk switch
{
PK9 => IsRecordPermitted<PersonalTable9SV, PersonalInfo9SV>(evos, PersonalTable.SV, index),
PK8 => IsRecordPermitted<PersonalTable8SWSH, PersonalInfo8SWSH>(evos, PersonalTable.SWSH, index),
_ => false,
};
}
/// <summary>
/// Options for applying Technical Record flags.
/// </summary>
public enum TechnicalRecordApplicatorOption
{
/// <summary>
/// Do not apply any flags. Clear all flags.
/// </summary>
None,
/// <summary>
/// Apply all flags, regardless of legality.
/// </summary>
ForceAll,
/// <summary>
/// Apply legal flags based on the current moves.
/// </summary>
LegalCurrent,
/// <summary>
/// Apply legal flags based on all moves able to learn in the game it resides in.
/// </summary>
LegalAll,
}

View File

@ -0,0 +1,280 @@
using System;
using System.Text;
namespace PKHeX.Core;
/// <summary>
/// Grammar and prefix/suffix tokens for <see cref="IBattleTemplate"/> localization.
/// </summary>
public sealed record BattleTemplateConfig
{
public sealed record BattleTemplateTuple(BattleTemplateToken Token, string Text);
/// <summary> Prefix tokens - e.g. Friendship: {100} </summary>
public required BattleTemplateTuple[] Left { get; init; }
/// <summary> Suffix tokens - e.g. {Timid} Nature </summary>
public required BattleTemplateTuple[] Right { get; init; }
/// <summary> Tokens that always display the same text, with no value - e.g. Shiny: Yes </summary>
public required BattleTemplateTuple[] Center { get; init; }
/// <summary>
/// Stat names, ordered with speed in the middle (not last).
/// </summary>
public required StatDisplayConfig StatNames { get; init; }
/// <summary>
/// Stat names, ordered with speed in the middle (not last).
/// </summary>
public required StatDisplayConfig StatNamesFull { get; init; }
public required string Male { get; init; }
public required string Female { get; init; }
/// <summary>
/// Gets the stat names in the requested format.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public StatDisplayConfig GetStatDisplay(StatDisplayStyle style = StatDisplayStyle.Abbreviated) => style switch
{
StatDisplayStyle.Abbreviated => StatNames,
StatDisplayStyle.Full => StatNamesFull,
StatDisplayStyle.HABCDS => StatDisplayConfig.HABCDS,
StatDisplayStyle.Raw => StatDisplayConfig.Raw,
StatDisplayStyle.Raw00 => StatDisplayConfig.Raw00,
_ => throw new ArgumentOutOfRangeException(nameof(style), style, null),
};
public static ReadOnlySpan<char> GetMoveDisplay(MoveDisplayStyle style = MoveDisplayStyle.Fill) => style switch
{
MoveDisplayStyle.Fill => "----",
MoveDisplayStyle.Directional => "↑→←↓",
_ => throw new ArgumentOutOfRangeException(nameof(style), style, null),
};
public static bool IsMovePrefix(char c) => c is '-' or '' or '↑' or '←' or '↓' or '→';
public static ReadOnlySpan<BattleTemplateToken> CommunityStandard =>
[
BattleTemplateToken.FirstLine,
BattleTemplateToken.Ability,
BattleTemplateToken.Level,
BattleTemplateToken.Shiny,
BattleTemplateToken.Friendship,
BattleTemplateToken.DynamaxLevel,
BattleTemplateToken.Gigantamax,
BattleTemplateToken.TeraType,
BattleTemplateToken.EVs,
BattleTemplateToken.Nature,
BattleTemplateToken.IVs,
BattleTemplateToken.Moves,
];
public static ReadOnlySpan<BattleTemplateToken> Showdown => CommunityStandard;
public static ReadOnlySpan<BattleTemplateToken> ShowdownNew =>
[
BattleTemplateToken.FirstLine,
BattleTemplateToken.AbilityHeldItem,
BattleTemplateToken.Moves,
BattleTemplateToken.EVsAppendNature,
BattleTemplateToken.IVs,
BattleTemplateToken.Level,
BattleTemplateToken.Shiny,
BattleTemplateToken.Friendship,
BattleTemplateToken.DynamaxLevel,
BattleTemplateToken.Gigantamax,
BattleTemplateToken.TeraType,
];
public static ReadOnlySpan<BattleTemplateToken> DefaultHover =>
[
// First line is handled manually.
BattleTemplateToken.HeldItem,
BattleTemplateToken.Ability,
BattleTemplateToken.Level,
BattleTemplateToken.Shiny,
BattleTemplateToken.DynamaxLevel,
BattleTemplateToken.Gigantamax,
BattleTemplateToken.TeraType,
BattleTemplateToken.EVs,
BattleTemplateToken.IVs,
BattleTemplateToken.Nature,
BattleTemplateToken.Moves,
// Other tokens are handled manually (Ganbaru, Awakening) as they are not stored by the battle template interface, only entity objects.
];
/// <summary>
/// Tries to parse the line for a token and value, if applicable.
/// </summary>
/// <param name="line">Line to parse</param>
/// <param name="value">Value for the token, if applicable</param>
/// <returns>Token type that was found</returns>
public BattleTemplateToken TryParse(ReadOnlySpan<char> line, out ReadOnlySpan<char> value)
{
value = default;
if (line.Length == 0)
return BattleTemplateToken.None;
foreach (var tuple in Left)
{
if (!line.StartsWith(tuple.Text, StringComparison.OrdinalIgnoreCase))
continue;
value = line[tuple.Text.Length..];
return tuple.Token;
}
foreach (var tuple in Right)
{
if (!line.EndsWith(tuple.Text, StringComparison.OrdinalIgnoreCase))
continue;
value = line[..^tuple.Text.Length];
return tuple.Token;
}
foreach (var tuple in Center)
{
if (!line.Equals(tuple.Text, StringComparison.OrdinalIgnoreCase))
continue;
return tuple.Token;
}
return BattleTemplateToken.None;
}
private string GetToken(BattleTemplateToken token, out bool isLeft)
{
foreach (var tuple in Left)
{
if (tuple.Token != token)
continue;
isLeft = true;
return tuple.Text;
}
foreach (var tuple in Right)
{
if (tuple.Token != token)
continue;
isLeft = false;
return tuple.Text;
}
foreach (var tuple in Center)
{
if (tuple.Token != token)
continue;
isLeft = false;
return tuple.Text;
}
throw new ArgumentException($"Token {token} not found in config");
}
/// <summary>
/// Gets the string representation of the token. No value is combined with it.
/// </summary>
public string Push(BattleTemplateToken token) => GetToken(token, out _);
/// <summary>
/// Gets the string representation of the token, and combines the value with it.
/// </summary>
public string Push<T>(BattleTemplateToken token, T value)
{
var str = GetToken(token, out var isLeft);
if (isLeft)
return $"{str}{value}";
return $"{value}{str}";
}
/// <inheritdoc cref="Push{T}(BattleTemplateToken,T)"/>
public void Push<T>(BattleTemplateToken token, T value, StringBuilder sb)
{
var str = GetToken(token, out var isLeft);
if (isLeft)
sb.Append(str).Append(value);
else
sb.Append(value).Append(str);
}
/// <summary>
/// Checks all representations of the stat name for a match.
/// </summary>
/// <param name="stat">Stat name</param>
/// <returns>-1 if not found, otherwise the index of the stat</returns>
public int GetStatIndex(ReadOnlySpan<char> stat)
{
var index = StatNames.GetStatIndex(stat);
if (index != -1)
return index;
index = StatNamesFull.GetStatIndex(stat);
if (index != -1)
return index;
foreach (var set in StatDisplayConfig.Custom)
{
index = set.GetStatIndex(stat);
if (index != -1)
return index;
}
return -1;
}
public StatParseResult TryParseStats(ReadOnlySpan<char> message, Span<int> bestResult)
{
var result = ParseInternal(message, bestResult);
ReorderSpeedNotLast(bestResult);
return result;
}
private StatParseResult ParseInternal(ReadOnlySpan<char> message, Span<int> bestResult)
{
Span<int> original = stackalloc int[bestResult.Length];
bestResult.CopyTo(original);
var result = StatNames.TryParse(message, bestResult);
if (result.IsParseClean)
return result;
// Check if the others get a better result
int bestCount = result.CountParsed;
Span<int> tmp = stackalloc int[bestResult.Length];
// Check Long Stat names
{
original.CopyTo(tmp); // restore original defaults
var other = StatNamesFull.TryParse(message, tmp);
if (other.IsParseClean)
{
tmp.CopyTo(bestResult);
return other;
}
if (other.CountParsed > bestCount)
{
bestCount = other.CountParsed;
tmp.CopyTo(bestResult);
}
}
// Check custom parsers
foreach (var set in StatDisplayConfig.Custom)
{
original.CopyTo(tmp); // restore original defaults
var other = set.TryParse(message, tmp);
if (other.IsParseClean)
{
tmp.CopyTo(bestResult);
return other;
}
if (other.CountParsed > bestCount)
{
bestCount = other.CountParsed;
tmp.CopyTo(bestResult);
}
}
return result;
}
private static void ReorderSpeedNotLast<T>(Span<T> arr)
{
ArgumentOutOfRangeException.ThrowIfLessThan(arr.Length, 6);
var speed = arr[5];
arr[5] = arr[4];
arr[4] = arr[3];
arr[3] = speed;
}
}

View File

@ -0,0 +1,12 @@
namespace PKHeX.Core;
/// <summary>
/// Token order for displaying the battle template.
/// </summary>
public enum BattleTemplateDisplayStyle : sbyte
{
Custom = -1,
Showdown = 0, // default
Legacy,
Brief, // default preview hover style
}

View File

@ -0,0 +1,96 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Settings for exporting a battle template.
/// </summary>
public readonly ref struct BattleTemplateExportSettings
{
/// <summary>
/// Order of the tokens in the export.
/// </summary>
public ReadOnlySpan<BattleTemplateToken> Order { get; init; }
/// <summary>
/// Localization for the battle template.
/// </summary>
public BattleTemplateLocalization Localization { get; }
/// <summary>
/// Display style for the EVs.
/// </summary>
public StatDisplayStyle StatsEVs { get; init; }
/// <summary>
/// Display style for the IVs.
/// </summary>
public StatDisplayStyle StatsIVs { get; init; }
public StatDisplayStyle StatsOther { get; init; }
/// <summary>
/// Display style for the moves.
/// </summary>
public MoveDisplayStyle Moves { get; init; }
public static BattleTemplateExportSettings Showdown => new(BattleTemplateConfig.Showdown);
public static BattleTemplateExportSettings CommunityStandard => new(BattleTemplateConfig.CommunityStandard);
public BattleTemplateExportSettings(string language) : this(BattleTemplateConfig.Showdown, language) { }
public BattleTemplateExportSettings(LanguageID language) : this(BattleTemplateConfig.Showdown, language) { }
public BattleTemplateExportSettings(ReadOnlySpan<BattleTemplateToken> order, string language = BattleTemplateLocalization.DefaultLanguage)
{
Localization = BattleTemplateLocalization.GetLocalization(language);
Order = order;
}
public BattleTemplateExportSettings(ReadOnlySpan<BattleTemplateToken> order, LanguageID language)
{
Localization = BattleTemplateLocalization.GetLocalization(language);
Order = order;
}
/// <summary>
/// Checks if the token is in the export.
/// </summary>
public bool IsTokenInExport(BattleTemplateToken token)
{
foreach (var t in Order)
{
if (t == token)
return true;
}
return false;
}
/// <summary>
/// Gets the index of the token in the export.
/// </summary>
public int GetTokenIndex(BattleTemplateToken token)
{
for (int i = 0; i < Order.Length; i++)
{
if (Order[i] == token)
return i;
}
return -1;
}
/// <summary>
/// Checks if the token is in the export.
/// </summary>
/// <remarks>Should be a static method, but is not because it feels better this way.</remarks>
/// <param name="token">Token to check</param>
/// <param name="tokens">Tokens to check against</param>
public bool IsTokenInExport(BattleTemplateToken token, ReadOnlySpan<BattleTemplateToken> tokens)
{
foreach (var t in tokens)
{
if (t == token)
return true;
}
return false;
}
}

View File

@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace PKHeX.Core;
/// <summary>
/// Provides information for localizing <see cref="IBattleTemplate"/> sets.
/// </summary>
/// <param name="Strings">In-game strings</param>
/// <param name="Config">Grammar and prefix/suffix tokens</param>
public sealed record BattleTemplateLocalization(GameStrings Strings, BattleTemplateConfig Config)
{
public const string DefaultLanguage = GameLanguage.DefaultLanguage; // English
private static readonly Dictionary<string, BattleTemplateLocalization> Cache = new(1);
private static readonly BattleTemplateConfigContext Context = new(LocalizationStorage<BattleTemplateConfig>.Options);
public static readonly LocalizationStorage<BattleTemplateConfig> ConfigCache = new("battle", Context.BattleTemplateConfig);
public static readonly BattleTemplateLocalization Default = GetLocalization(DefaultLanguage);
/// <summary>
/// Gets the localization for the requested language.
/// </summary>
/// <param name="language">Language code</param>
public static BattleTemplateConfig GetConfig(string language) => ConfigCache.Get(language);
/// <param name="language"><see cref="LanguageID"/> index</param>
/// <inheritdoc cref="GetLocalization(string)"/>
public static BattleTemplateLocalization GetLocalization(LanguageID language) =>
GetLocalization(language.GetLanguageCode());
/// <summary>
/// Gets the localization for the requested language.
/// </summary>
/// <param name="language">Language code</param>
public static BattleTemplateLocalization GetLocalization(string language)
{
if (Cache.TryGetValue(language, out var result))
return result;
var strings = GameInfo.GetStrings(language);
var cfg = GetConfig(language);
result = new BattleTemplateLocalization(strings, cfg);
Cache[language] = result;
return result;
}
/// <summary>
/// Force loads all localizations.
/// </summary>
public static bool ForceLoadAll()
{
bool anyLoaded = false;
foreach (var lang in GameLanguage.AllSupportedLanguages)
{
if (Cache.ContainsKey(lang))
continue;
_ = GetLocalization(lang);
anyLoaded = true;
}
return anyLoaded;
}
/// <summary>
/// Gets all localizations.
/// </summary>
public static IReadOnlyDictionary<string, BattleTemplateLocalization> GetAll()
{
_ = ForceLoadAll();
return Cache;
}
}
[JsonSerializable(typeof(BattleTemplateConfig))]
public sealed partial class BattleTemplateConfigContext : JsonSerializerContext;

View File

@ -0,0 +1,49 @@
using System.Text.Json.Serialization;
namespace PKHeX.Core;
/// <summary>
/// Enum for the different tokens used in battle templates.
/// </summary>
/// <remarks>
/// Each token represents a specific aspect of a Pokémon's battle template.
/// One token per line. Each token can have specific grammar rules depending on the language.
/// </remarks>
[JsonConverter(typeof(JsonStringEnumConverter<BattleTemplateToken>))]
public enum BattleTemplateToken : byte
{
None = 0, // invalid, used as a magic value to signal that a token is not recognized
// Standard tokens
Shiny,
Ability,
Nature,
Friendship,
EVs,
IVs,
Level,
DynamaxLevel,
Gigantamax,
TeraType,
// Tokens that can appear multiple times
Moves,
// When present, first line will not contain values for these tokens (instead outputting on separate token line)
// Not part of the standard export format, but can be recognized/optionally used in the program
HeldItem,
Nickname,
Gender,
// Manually appended, not stored or recognized on import
AVs,
GVs,
// Future Showdown propositions
AbilityHeldItem, // [Ability] Item
EVsWithNature, // +/-
EVsAppendNature, // +/- and .. (Nature)
// Omitting the first line (species) shouldn't be done unless it is manually added in the presentation/export.
FirstLine = byte.MaxValue,
}

View File

@ -0,0 +1,76 @@
using System;
using System.ComponentModel;
namespace PKHeX.Core;
public sealed class BattleTemplateSettings
{
[LocalizedDescription("Settings for showing details when hovering a slot.")]
public BattleTemplateTypeSetting Hover { get; set; } = new(BattleTemplateDisplayStyle.Brief, LanguageID.None, MoveDisplayStyle.Directional);
[LocalizedDescription("Settings for showing details when exporting a slot.")]
public BattleTemplateTypeSetting Export { get; set; } = new(BattleTemplateDisplayStyle.Showdown, LanguageID.English);
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class BattleTemplateTypeSetting
{
[LocalizedDescription("Language to use when exporting a battle template. If not specified in settings, will use current language.")]
public LanguageID Language { get; set; }
public StatDisplayStyle StyleStatEVs { get; set; }
public StatDisplayStyle StyleStatIVs { get; set; }
public StatDisplayStyle StyleStatOther { get; set; }
public MoveDisplayStyle StyleMove { get; set; }
[LocalizedDescription("Custom stat labels and grammar.")]
public StatDisplayConfig StatsCustom { get; set; } = StatDisplayConfig.HABCDS;
[LocalizedDescription("Display format to use when exporting a battle template from the program.")]
public BattleTemplateDisplayStyle TokenOrder { get; set; }
[LocalizedDescription("Custom ordering for exporting a set, if chosen via export display style.")]
public BattleTemplateToken[] TokenOrderCustom { get; set; } = BattleTemplateConfig.Showdown.ToArray();
public BattleTemplateTypeSetting() { }
public BattleTemplateTypeSetting(BattleTemplateDisplayStyle style, LanguageID lang, MoveDisplayStyle move = MoveDisplayStyle.Fill)
{
TokenOrder = style;
Language = lang;
StyleMove = move;
}
public override string ToString() => $"{TokenOrder} {Language}";
private LanguageID GetLanguageExport(LanguageID program) => GetLanguage(Language, program);
public BattleTemplateExportSettings GetSettings(LanguageID programLanguage, EntityContext context) => new(GetOrder(TokenOrder, TokenOrderCustom), GetLanguageExport(programLanguage))
{
StatsEVs = StyleStatEVs,
StatsIVs = StyleStatIVs,
StatsOther = StyleStatOther,
Moves = GetMoveDisplayStyle(StyleMove, context),
};
private static LanguageID GetLanguage(LanguageID choice, LanguageID program)
{
if (choice != LanguageID.None)
return choice;
if (program == LanguageID.None)
return LanguageID.English;
return program;
}
private static ReadOnlySpan<BattleTemplateToken> GetOrder(BattleTemplateDisplayStyle style, ReadOnlySpan<BattleTemplateToken> custom) => style switch
{
BattleTemplateDisplayStyle.Legacy => BattleTemplateConfig.CommunityStandard,
BattleTemplateDisplayStyle.Brief => BattleTemplateConfig.DefaultHover,
BattleTemplateDisplayStyle.Custom => custom,
_ => BattleTemplateConfig.Showdown,
};
private static MoveDisplayStyle GetMoveDisplayStyle(MoveDisplayStyle style, EntityContext context) => style switch
{
MoveDisplayStyle.Directional when context is EntityContext.Gen9a => MoveDisplayStyle.Directional,
_ => MoveDisplayStyle.Fill,
};
}

View File

@ -0,0 +1,6 @@
namespace PKHeX.Core;
public readonly record struct BattleTemplateParseError(BattleTemplateParseErrorType Type, string Value)
{
public string Humanize(BattleTemplateParseErrorLocalization localization) => Type.Humanize(localization, Value);
}

View File

@ -0,0 +1,55 @@
using System.Text.Json.Serialization;
namespace PKHeX.Core;
/// <summary>
/// Localized strings for <see cref="BattleTemplateParseErrorType"/> values.
/// Each enum member maps 1:1 to a property for JSON (de)serialization.
/// </summary>
public sealed class BattleTemplateParseErrorLocalization
{
private static readonly BattleTemplateParseErrorLocalizationContext Context = new(LocalizationStorage<BattleTemplateParseErrorLocalization>.Options);
public static readonly LocalizationStorage<BattleTemplateParseErrorLocalization> Cache = new("setparse", Context.BattleTemplateParseErrorLocalization);
public static BattleTemplateParseErrorLocalization Get(string language = GameLanguage.DefaultLanguage) => Cache.Get(language);
public static BattleTemplateParseErrorLocalization Get(LanguageID language) => Cache.Get(language.GetLanguageCode());
// General / structural
public required string LineLength { get; init; } = "Line exceeded the maximum supported length: {0}";
// Token issues
public required string TokenUnknown { get; init; } = "Unrecognized: {0}";
public required string TokenFailParse { get; init; } = "Token could not be parsed: {0}";
// Move issues
public required string MoveCountTooMany { get; init; } = "Too many moves specified: {0}";
public required string MoveSlotAlreadyUsed { get; init; } = "Move slot already used: {0}";
public required string MoveDuplicate { get; init; } = "Duplicate move specified: {0}";
public required string MoveUnrecognized { get; init; } = "Move not recognized: {0}";
// Item
public required string ItemUnrecognized { get; init; } = "Held item not recognized: {0}";
// Ability
public required string AbilityDeclaration { get; init; } = "Ability already declared: {0}";
public required string AbilityUnrecognized { get; init; } = "Ability not recognized: {0}";
public required string AbilityAlreadySpecified { get; init; } = "Ability already specified: {0}";
// Nature
public required string NatureUnrecognized { get; init; } = "Nature not recognized: {0}";
public required string NatureAlreadySpecified { get; init; } = "Nature already specified: {0}";
// Hidden Power
public required string HiddenPowerUnknownType { get; init; } = "Hidden Power type not recognized: {0}";
public required string HiddenPowerIncompatibleIVs { get; init; } = "Hidden Power type incompatible with IVs: {0}";
// EffortValue Nature Amp (Stat modifiers with + / - )
public required string NatureEffortAmpDeclaration { get; init; } = "Nature / effort amp already declared: {0}";
public required string NatureEffortAmpUnknown { get; init; } = "Unknown nature effort amp token: {0}";
public required string NatureEffortAmpAlreadySpecified { get; init; } = "Nature effort amp already specified: {0}";
public required string NatureEffortAmpConflictNature { get; init; } = "Declared effort amp conflicts with previously specified nature.";
public required string NatureAmpNoPlus { get; init; } = "Missing '+' nature amp token.";
public required string NatureAmpNoMinus { get; init; } = "Missing '-' nature amp token.";
}
[JsonSerializable(typeof(BattleTemplateParseErrorLocalization))]
public sealed partial class BattleTemplateParseErrorLocalizationContext : JsonSerializerContext;

View File

@ -0,0 +1,75 @@
using System;
using static PKHeX.Core.BattleTemplateParseErrorType;
namespace PKHeX.Core;
public enum BattleTemplateParseErrorType : byte
{
None = 0,
LineLength,
TokenUnknown,
TokenFailParse,
MoveCountTooMany,
MoveSlotAlreadyUsed,
MoveDuplicate,
MoveUnrecognized,
ItemUnrecognized,
AbilityDeclaration,
AbilityUnrecognized,
AbilityAlreadySpecified,
NatureUnrecognized,
NatureAlreadySpecified,
HiddenPowerUnknownType,
HiddenPowerIncompatibleIVs,
NatureEffortAmpDeclaration,
NatureEffortAmpUnknown,
NatureEffortAmpAlreadySpecified,
NatureEffortAmpConflictNature,
NatureAmpNoPlus,
NatureAmpNoMinus,
}
public static class BattleTemplateParseErrorExtensions
{
/// <summary>
/// Returns the localized string for the provided <paramref name="type"/>.
/// Falls back to the enum name if no mapping exists.
/// </summary>
public static string Humanize(this BattleTemplateParseErrorType type, BattleTemplateParseErrorLocalization localization, string value)
{
var template = GetTemplate(type, localization);
if (value.Length == 0)
return template;
return string.Format(template, value);
}
private static string GetTemplate(BattleTemplateParseErrorType type, BattleTemplateParseErrorLocalization localization) => type switch
{
None => "",
LineLength => localization.LineLength,
TokenUnknown => localization.TokenUnknown,
TokenFailParse => localization.TokenFailParse,
MoveCountTooMany => localization.MoveCountTooMany,
MoveSlotAlreadyUsed => localization.MoveSlotAlreadyUsed,
MoveDuplicate => localization.MoveDuplicate,
MoveUnrecognized => localization.MoveUnrecognized,
ItemUnrecognized => localization.ItemUnrecognized,
AbilityDeclaration => localization.AbilityDeclaration,
AbilityUnrecognized => localization.AbilityUnrecognized,
AbilityAlreadySpecified => localization.AbilityAlreadySpecified,
NatureUnrecognized => localization.NatureUnrecognized,
NatureAlreadySpecified => localization.NatureAlreadySpecified,
HiddenPowerUnknownType => localization.HiddenPowerUnknownType,
HiddenPowerIncompatibleIVs => localization.HiddenPowerIncompatibleIVs,
NatureEffortAmpDeclaration => localization.NatureEffortAmpDeclaration,
NatureEffortAmpUnknown => localization.NatureEffortAmpUnknown,
NatureEffortAmpAlreadySpecified => localization.NatureEffortAmpAlreadySpecified,
NatureEffortAmpConflictNature => localization.NatureEffortAmpConflictNature,
NatureAmpNoPlus => localization.NatureAmpNoPlus,
NatureAmpNoMinus => localization.NatureAmpNoMinus,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
};
}

View File

@ -18,11 +18,12 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
/// <summary>
/// <see cref="PKM.Gender"/> name of the Set entity.
/// </summary>
int Gender { get; }
byte? Gender { get; }
/// <summary>
/// <see cref="PKM.HeldItem"/> of the Set entity.
/// </summary>
/// <remarks>Depends on <see cref="Context"/> for context-specific item lists.</remarks>
int HeldItem { get; }
/// <summary>
@ -33,7 +34,7 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
/// <summary>
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
/// </summary>
int Level { get; }
byte Level { get; }
/// <summary>
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
@ -43,7 +44,7 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
/// <summary>
/// <see cref="PKM.CurrentFriendship"/> of the Set entity.
/// </summary>
int Friendship { get; }
byte Friendship { get; }
/// <summary>
/// <see cref="PKM.Form"/> name of the Set entity, stored in PKHeX style (instead of Showdown's)
@ -53,7 +54,7 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
/// <summary>
/// <see cref="PKM.HPType"/> of the Set entity.
/// </summary>
int HiddenPowerType { get; }
sbyte HiddenPowerType { get; }
/// <summary>
/// <see cref="EffortValues"/> of the Set entity.

View File

@ -0,0 +1,17 @@
namespace PKHeX.Core;
/// <summary>
/// Style to display moves.
/// </summary>
public enum MoveDisplayStyle : byte
{
/// <summary>
/// Moves are slots 1-4, with no empty slots, and correspond to the rectangular grid without empty spaces.
/// </summary>
Fill,
/// <summary>
/// Move slots are assigned to the directional pad, and unused directional slots are not displayed.
/// </summary>
Directional,
}

View File

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <summary>
/// Logic for retrieving teams from URLs.
/// </summary>
public static class BattleTemplateTeams
{
/// <summary>
/// Tries to check if the input text is a valid URL for a team, and if so, retrieves the team data.
/// </summary>
/// <param name="text">The input text to check.</param>
/// <param name="content">When the method returns, contains the retrieved team data if the text is a valid URL; otherwise, null.</param>
/// <returns><see langword="true"/> if the text is a valid URL and the team data was successfully retrieved; otherwise, <see langword="false"/>.</returns>
public static bool TryGetSetLines(string text, [NotNullWhen(true)] out string? content)
{
if (ShowdownTeam.IsURL(text, out var url))
return ShowdownTeam.TryGetSets(url, out content);
if (PokepasteTeam.IsURL(text, out url))
return PokepasteTeam.TryGetSets(url, out content);
content = text;
return false;
}
/// <summary>
/// Attempts to retrieve sets from the provided text. If the text is a valid URL, it retrieves the team data from the URL.
/// </summary>
/// <param name="text">The input text to check.</param>
/// <returns>An enumerable collection of <see cref="ShowdownSet"/> objects representing the sets.</returns>
public static IEnumerable<ShowdownSet> TryGetSets(string text)
{
var ingest = TryGetSetLines(text, out var many) ? many : text;
return ShowdownParsing.GetShowdownSets(ingest);
}
}

View File

@ -0,0 +1,94 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace PKHeX.Core;
/// <summary>
/// Logic for retrieving Showdown teams from URLs.
/// </summary>
/// <remarks>
/// <see href="https://pokepast.es/"/>
/// </remarks>
public static class PokepasteTeam
{
/// <summary>
/// Generates the raw URL for retrieving a team based on the supplied team identifier.
/// </summary>
/// <param name="team">The numeric identifier of the team.</param>
/// <returns>A string containing the full URL to access the team data.</returns>
public static string GetURL(ulong team) => $"https://pokepast.es/{team:x16}/raw";
/// <inheritdoc cref="GetURL"/>
/// <remarks>For legacy team indexes (first 255 or so), shouldn't ever be triggered non-test team indexes.</remarks>
public static string GetURLOld(int team) => $"https://pokepast.es/{team}/raw";
/// <summary>
/// Attempts to retrieve the Showdown team data from a specified URL, and reformats it.
/// </summary>
/// <param name="url">The URL to retrieve the team data from.</param>
/// <param name="content">When the method returns, contains the processed team data if retrieval and formatting succeed; otherwise, null.</param>
/// <returns><see langword="true"/> if the team data is successfully retrieved and reformatted; otherwise, <see langword="false"/>.</returns>
public static bool TryGetSets(string url, [NotNullWhen(true)] out string? content)
{
content = null;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uriResult) || (uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps))
return false;
content = NetUtil.GetStringFromURL(uriResult);
return content != null;
}
/// <summary>
/// Determines if the provided text is a valid Showdown team URL. If valid, returns a normalized API URL.
/// </summary>
/// <param name="text">The text to evaluate.</param>
/// <param name="url">When the method returns, contains the normalized API URL if the text represents a valid Showdown team URL; otherwise, null.</param>
/// <returns><see langword="true"/> if the text is a valid Showdown team URL; otherwise, <see langword="false"/>.</returns>
public static bool IsURL(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
{
text = text.Trim();
url = null;
if (!text.StartsWith("https://pokepast.es/")) // short link
return false;
return TryCheckWeb(text, out url);
}
/// <summary>
/// Attempts to extract the team identifier from a Showdown web URL and converts it to a standard API URL.
/// </summary>
/// <param name="text">The Showdown web URL as a read-only span of characters.</param>
/// <param name="url">When the method returns, contains the standardized API URL if extraction is successful; otherwise, null.</param>
/// <returns><see langword="true"/> if the team index is successfully extracted and converted; otherwise, <see langword="false"/>.</returns>
public static bool TryCheckWeb(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
{
// if ends with `/`, remove.
if (text.EndsWith('/'))
text = text[..^1]; // remove trailing slash
// if ends with `/raw`, remove.
if (text.EndsWith("/raw"))
text = text[..^4]; // remove trailing /raw
url = null;
// seek back to `/`
int start = text.LastIndexOf('/'); // seek back to /
if (start == -1)
return false;
// get the substring after
var number = text[(start + 1)..];
switch (number.Length)
{
case 16 when ulong.TryParse(number, NumberStyles.HexNumber, null, out var hash):
url = GetURL(hash);
return true;
case <= 8 when int.TryParse(number, out var team):
url = GetURLOld(team);
return true;
default:
return false;
}
}
}

View File

@ -0,0 +1,476 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.Species;
namespace PKHeX.Core;
/// <summary>
/// Logic for parsing details for <see cref="ShowdownSet"/> objects.
/// </summary>
public static class ShowdownParsing
{
private static readonly string[] genderForms = ["", "F", ""];
/// <inheritdoc cref="ShowdownSet.DefaultListAllocation"/>
private const int DefaultListAllocation = ShowdownSet.DefaultListAllocation;
/// <summary>
/// Gets the Form ID from the input <see cref="name"/>.
/// </summary>
/// <param name="name">Form name to find the form index of</param>
/// <param name="strings">Localized string source to fetch with</param>
/// <param name="species">Species ID the form belongs to</param>
/// <param name="context">Format the form name should appear in</param>
/// <returns>Zero (base form) if no form matches the input string.</returns>
public static byte GetFormFromString(ReadOnlySpan<char> name, GameStrings strings, ushort species, EntityContext context)
{
if (name.Length == 0)
return 0;
var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, context);
if (forms.Length < 1)
return 0;
// Find first matching index that matches any case, ignoring dashes interchanged with spaces.
for (byte i = 0; i < forms.Length; i++)
{
if (IsFormEquivalent(forms[i], name))
return i;
}
// No match, assume default 0 form.
return 0;
}
private static bool IsFormEquivalent(ReadOnlySpan<char> reference, ReadOnlySpan<char> input)
{
if (input.Length != reference.Length)
return false;
for (int i = 0; i < input.Length; i++)
{
var c1 = input[i];
var c2 = reference[i];
if (char.ToUpperInvariant(c1) == char.ToUpperInvariant(c2))
continue;
if (c1 is ' ' or '-' && c2 is ' ' or '-')
continue;
return false;
}
return true;
}
/// <summary>
/// Converts a Form ID to string.
/// </summary>
/// <param name="form">Form to get the form name of</param>
/// <param name="strings">Localized string source to fetch with</param>
/// <param name="species">Species ID the form belongs to</param>
/// <param name="context">Format the form name should appear in</param>
public static string GetStringFromForm(byte form, GameStrings strings, ushort species, EntityContext context)
{
if (form == 0)
return string.Empty;
var result = FormConverter.GetStringFromForm(species, form, strings, genderForms, context);
if (result.Length == 0)
return string.Empty;
// Showdown uses a non-standard representation for some forms, and uses interstitial dashes instead of spaces.
if (strings.Language != LanguageID.English)
return result;
return GetShowdownFormName(species, result);
}
private const string MiniorFormName = "Meteor";
/// <summary>
/// Converts the PKHeX standard form name to Showdown's form name.
/// </summary>
/// <param name="species">Species ID</param>
/// <param name="form">PKHeX form name</param>
public static string GetShowdownFormName(ushort species, string form)
{
if (form.Length == 0)
{
return species switch
{
(int)Minior => MiniorFormName,
_ => form,
};
}
return species switch
{
(int)Basculin when form is "Blue" => "Blue-Striped",
(int)Vivillon when form is "Poké Ball" => "Pokeball",
(int)Zygarde => form.Replace("-C", string.Empty).Replace("50%", string.Empty),
(int)Minior when form.StartsWith("M-", StringComparison.OrdinalIgnoreCase) => MiniorFormName,
(int)Minior => form.Replace("C-", string.Empty),
(int)Necrozma when form is "Dusk" => $"{form}-Mane",
(int)Necrozma when form is "Dawn" => $"{form}-Wings",
(int)Polteageist or (int)Sinistea => form == "Antique" ? form : string.Empty,
(int)Maushold when form is "Family of Four" => "Four",
(int)Greninja or (int)Rockruff or (int)Koraidon or (int)Miraidon => string.Empty,
_ => FormInfo.HasTotemForm(species) && form == "Large"
? species is (int)Raticate or (int)Marowak ? "Alola-Totem" : "Totem"
: form.Replace(' ', '-'),
};
}
public static bool IsTotemForm(ReadOnlySpan<char> formName) =>
formName.Equals("Totem", StringComparison.OrdinalIgnoreCase) ||
formName.Equals("Alola-Totem", StringComparison.OrdinalIgnoreCase) ||
formName.Equals("Large", StringComparison.OrdinalIgnoreCase);
public static bool IsCosplayPikachu(ReadOnlySpan<char> formName, ReadOnlySpan<string> formNames)
=> FormConverter.IsCosplayPikachu(formName, formNames);
/// <summary>
/// Converts the Showdown form name to PKHeX's form name.
/// </summary>
/// <param name="species">Species ID</param>
/// <param name="form">Showdown form name</param>
/// <param name="ability">Showdown ability ID</param>
public static string GetFormNameFromShowdownFormName(ushort species, string form, int ability)
{
if (form.Length != 0)
form = form.Replace(' ', '-'); // inconsistencies are great
return species switch
{
(int)Basculin when form is "Blue-Striped" => "Blue",
(int)Vivillon when form is "Pokeball" => "Poké Ball",
(int)Necrozma when form is "Dusk-Mane" => "Dusk",
(int)Necrozma when form is "Dawn-Wings" => "Dawn",
(int)Toxtricity when form is "Low-Key" => "Low Key",
(int)Darmanitan when form is "Galar-Zen" => "Galar Zen",
(int)Minior when form is not MiniorFormName => $"C-{form}",
(int)Zygarde when form is "Complete" => form,
(int)Zygarde when ability == 211 => $"{(form.Contains("10%") ? "10%" : "50%")}-C",
(int)Greninja when ability == 210 => "Ash", // Battle Bond
(int)Rockruff when ability == 020 => "Dusk", // Rockruff-1
(int)Maushold when form is "Four" => "Family of Four",
(int)Urshifu or (int)Pikachu or (int)Alcremie => form.Replace('-', ' '), // Strike and Cosplay
(int)Pumpkaboo or (int)Gourgeist when form is "Average" => "Medium",
(int)Pumpkaboo or (int)Gourgeist when form is "Super" => "Jumbo",
_ => FormInfo.HasTotemForm(species) && form.EndsWith("Totem", StringComparison.OrdinalIgnoreCase) ? "Large" : form,
};
}
/// <summary>
/// Fetches <see cref="ShowdownSet"/> data from the input <see cref="lines"/>.
/// </summary>
/// <param name="lines">Raw lines containing numerous multi-line set data.</param>
/// <param name="localization">Localization data for the set.</param>
/// <returns><see cref="ShowdownSet"/> objects until <see cref="lines"/> is consumed.</returns>
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<string> lines, BattleTemplateLocalization localization)
{
// exported sets always have >4 moves; new List will always require 1 resizing, allocate 2x to save 1 reallocation.
// intro, nature, ability, (ivs, evs, shiny, level) 4*moves
var setLines = new List<string>(DefaultListAllocation);
foreach (var line in lines)
{
if (!string.IsNullOrWhiteSpace(line))
{
setLines.Add(line);
continue;
}
if (setLines.Count == 0)
continue;
yield return new ShowdownSet(setLines, localization);
setLines.Clear();
}
if (setLines.Count != 0)
yield return new ShowdownSet(setLines, localization);
}
/// <inheritdoc cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<string> lines)
{
var setLines = new List<string>(DefaultListAllocation);
foreach (var line in lines)
{
if (!string.IsNullOrWhiteSpace(line))
{
setLines.Add(line);
continue;
}
if (setLines.Count == 0)
continue;
yield return TryParseAnyLanguage(setLines, out var set) ? set : new ShowdownSet(setLines);
setLines.Clear();
}
if (setLines.Count != 0)
yield return TryParseAnyLanguage(setLines, out var set) ? set : new ShowdownSet(setLines);
}
/// <inheritdoc cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>
public static IEnumerable<ShowdownSet> GetShowdownSets(ReadOnlyMemory<char> text, BattleTemplateLocalization localization)
{
int start = 0;
do
{
var span = text.Span;
var slice = span[start..];
var set = GetShowdownSet(slice, localization, out int length);
if (set.Species == 0)
break;
yield return set;
start += length;
}
while (start < text.Length);
}
/// <inheritdoc cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>
/// <summary>
/// Language-unknown version of <see cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>.
/// </summary>
public static IEnumerable<ShowdownSet> GetShowdownSets(ReadOnlyMemory<char> text)
{
int start = 0;
do
{
var span = text.Span;
var slice = span[start..];
var set = GetShowdownSet(slice, out int length);
if (set.Species == 0)
break;
yield return set;
start += length;
}
while (start < text.Length);
}
/// <inheritdoc cref="GetShowdownSets(ReadOnlyMemory{char},BattleTemplateLocalization)"/>
public static IEnumerable<ShowdownSet> GetShowdownSets(string text, BattleTemplateLocalization localization) => GetShowdownSets(text.AsMemory(), localization);
private static int GetLength(ReadOnlySpan<char> text)
{
// Find the end of the Showdown Set lines.
// The end is implied when:
// - we see a complete whitespace or empty line
int length = 0;
while (true)
{
var newline = text.IndexOf('\n');
if (newline == -1)
return length + text.Length;
var slice = text[..newline];
var used = newline + 1;
length += used;
if (slice.IsWhiteSpace())
return length;
text = text[used..];
}
}
/// <summary>
/// Attempts to parse the input <see cref="text"/> into a <see cref="ShowdownSet"/> object.
/// </summary>
/// <param name="text">Input string to parse.</param>
/// <param name="localization">Input localization to use.</param>
/// <param name="length">Amount of characters consumed from the input string.</param>
/// <returns>Parsed <see cref="ShowdownSet"/> object if successful, otherwise might be a best-match with some/all unparsed lines.</returns>
public static ShowdownSet GetShowdownSet(ReadOnlySpan<char> text, BattleTemplateLocalization localization, out int length)
{
length = GetLength(text);
var slice = text[..length];
var set = new ShowdownSet(slice, localization);
while (length < text.Length && text[length] is '\r' or '\n' or ' ')
length++;
return set;
}
/// <inheritdoc cref="GetShowdownSet(ReadOnlySpan{char},BattleTemplateLocalization,out int)"/>
public static ShowdownSet GetShowdownSet(ReadOnlySpan<char> text, out int length)
{
length = GetLength(text);
var slice = text[..length];
if (!TryParseAnyLanguage(slice, out var set))
set = new ShowdownSet(slice); // should never fall back
while (length < text.Length && text[length] is '\r' or '\n' or ' ')
length++;
return set;
}
/// <inheritdoc cref="GetShowdownSets(ReadOnlyMemory{char},BattleTemplateLocalization)"/>
public static IEnumerable<ShowdownSet> GetShowdownSets(string text) => GetShowdownSets(text.AsMemory());
/// <inheritdoc cref="GetShowdownText(PKM, in BattleTemplateExportSettings)"/>
public static string GetShowdownText(PKM pk) => GetShowdownText(pk, BattleTemplateExportSettings.Showdown);
/// <summary>
/// Converts the <see cref="PKM"/> data into an importable set format for Pokémon Showdown.
/// </summary>
/// <param name="pk">PKM to convert to string</param>
/// <param name="settings">Import localization/style setting</param>
/// <returns>Multi line set data</returns>
public static string GetShowdownText(PKM pk, in BattleTemplateExportSettings settings)
{
if (pk.Species == 0)
return string.Empty;
var set = new ShowdownSet(pk, settings.Localization);
set.InterpretAsPreview(pk);
return set.GetText(settings);
}
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
/// <param name="settings">Export localization/style setting</param>
/// <returns>Consumable list of <see cref="ShowdownSet.Text"/> lines.</returns>
public static IEnumerable<string> GetShowdownText(IEnumerable<PKM> data, in BattleTemplateExportSettings settings)
{
List<string> result = new(1);
var sets = GetShowdownSets(data);
foreach (var set in sets)
result.Add(set.GetText(settings));
return result;
}
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
/// <returns>Consumable list of <see cref="ShowdownSet.Text"/> lines.</returns>
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<PKM> data)
{
foreach (var pk in data)
{
if (pk.Species == 0)
continue;
yield return new ShowdownSet(pk);
}
}
/// <inheritdoc cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>
public static string GetShowdownSets(IEnumerable<PKM> data, string separator) => string.Join(separator, GetShowdownText(data, BattleTemplateExportSettings.Showdown));
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data, and combines it into one string.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
/// <param name="separator">Splitter between each set.</param>
/// <param name="settings">Import localization/style setting</param>
/// <returns>Single string containing all <see cref="ShowdownSet.Text"/> lines.</returns>
public static string GetShowdownSets(IEnumerable<PKM> data, string separator, in BattleTemplateExportSettings settings) => string.Join(separator, GetShowdownText(data, settings));
/// <summary>
/// Gets a localized string preview of the provided <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon data</param>
/// <param name="settings">Export settings</param>
/// <returns>Multi-line string</returns>
public static string GetLocalizedPreviewText(PKM pk, in BattleTemplateExportSettings settings)
{
var set = new ShowdownSet(pk, settings.Localization);
set.InterpretAsPreview(pk);
return set.GetText(settings);
}
/// <summary>
/// Tries to parse the input string into a <see cref="ShowdownSet"/> object.
/// </summary>
/// <param name="message">Input string to parse.</param>
/// <param name="set">Parsed <see cref="ShowdownSet"/> object if successful, otherwise might be a best-match with some unparsed lines.</param>
/// <returns>True if the input was parsed successfully, false otherwise.</returns>
public static bool TryParseAnyLanguage(ReadOnlySpan<char> message, [NotNullWhen(true)] out ShowdownSet? set)
{
set = null;
if (message.Length == 0)
return false;
var invalid = int.MaxValue;
var all = BattleTemplateLocalization.GetAll();
foreach (var lang in all)
{
var local = lang.Value;
var tmp = new ShowdownSet(message, local);
var bad = tmp.InvalidLines.Count;
if (bad == 0)
{
set = tmp;
return true;
}
// Check for invalid lines
if (bad >= invalid)
continue;
// Best so far.
invalid = bad;
set = tmp;
}
if (set is null)
return false;
return set.Species != 0;
}
/// <inheritdoc cref="TryParseAnyLanguage(ReadOnlySpan{char}, out ShowdownSet?)"/>
public static bool TryParseAnyLanguage(IReadOnlyList<string> setLines, [NotNullWhen(true)] out ShowdownSet? set)
{
set = null;
if (setLines.Count == 0)
return false;
var invalid = int.MaxValue;
var all = BattleTemplateLocalization.GetAll();
foreach (var lang in all)
{
var local = lang.Value;
var tmp = new ShowdownSet(setLines, local);
var bad = tmp.InvalidLines.Count;
if (bad == 0)
{
set = tmp;
return true;
}
// Check for invalid lines
if (bad >= invalid)
continue;
// Best so far.
invalid = bad;
set = tmp;
}
return false;
}
/// <summary>
/// Tries to translate the input battle template <see cref="message"/> into a localized string.
/// </summary>
/// <param name="message">Input string to parse.</param>
/// <param name="outputSettings">Export settings</param>
/// <param name="translated">Translated string if successful.</param>
/// <returns><see langword="true"/> if the input was translated successfully, <see langword="false"/> otherwise.</returns>
public static bool TryTranslate(ReadOnlySpan<char> message, BattleTemplateExportSettings outputSettings, [NotNullWhen(true)] out string? translated)
{
translated = null;
if (!TryParseAnyLanguage(message, out var set))
return false;
translated = set.GetText(outputSettings);
return true;
}
/// <inheritdoc cref="TryTranslate(ReadOnlySpan{char}, BattleTemplateExportSettings, out string?)"/>
public static bool TryTranslate(IReadOnlyList<string> message, BattleTemplateExportSettings outputSettings, [NotNullWhen(true)] out string? translated)
{
translated = null;
if (!TryParseAnyLanguage(message, out var set))
return false;
translated = set.GetText(outputSettings);
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,175 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace PKHeX.Core;
/// <summary>
/// Logic for retrieving Showdown teams from URLs.
/// </summary>
/// <remarks>
/// <see href="https://play.pokemonshowdown.com/"/>
/// </remarks>
public static class ShowdownTeam
{
/// <summary>
/// Generates the API URL for retrieving a Showdown team based on the supplied team identifier.
/// </summary>
/// <param name="team">The numeric identifier of the team.</param>
/// <returns>A string containing the full URL to access the team data via the API.</returns>
public static string GetURL(int team) => $"https://play.pokemonshowdown.com/api/getteam?teamid={team}&raw=1";
/// <summary>
/// Attempts to retrieve the Showdown team data from a specified URL, and reformats it.
/// </summary>
/// <param name="url">The URL to retrieve the team data from.</param>
/// <param name="content">When the method returns, contains the processed team data if retrieval and formatting succeed; otherwise, null.</param>
/// <returns><see langword="true"/> if the team data is successfully retrieved and reformatted; otherwise, <see langword="false"/>.</returns>
public static bool TryGetSets(string url, [NotNullWhen(true)] out string? content)
{
content = null;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uriResult) || (uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps))
return false;
content = NetUtil.GetStringFromURL(uriResult);
if (content == null)
return false;
return GetFromReply(ref content);
}
/// <summary>
/// Extracts the team data from the API reply and reformats it by replacing escaped newline
/// characters with system-specific line breaks.
/// </summary>
/// <param name="content">
/// A reference to the API response string. On successful extraction, the value is replaced
/// with the reformatted team data; otherwise, it remains unchanged.
/// </param>
/// <returns>
/// <see langword="true"/> if the team data is successfully extracted and reformatted; otherwise, <see langword="false"/>.
/// </returns>
public static bool GetFromReply(ref string content)
{
// reformat
const string startText = """
"team":"
""";
var start = content.IndexOf(startText, StringComparison.Ordinal);
if (start == -1)
return false;
start += startText.Length; // skip to the start of the team
var end = content.LastIndexOf("\\n", StringComparison.Ordinal);
if (end == -1)
return false;
var length = end - start;
if (length < 5) // arbitrary length check
return false;
var sb = new StringBuilder();
sb.Append(content, start, length);
sb.Replace("\\n", Environment.NewLine);
content = sb.ToString();
return true;
}
/// <summary>
/// Determines if the provided text is a valid Showdown team URL. If valid, returns a normalized API URL.
/// </summary>
/// <param name="text">The text to evaluate.</param>
/// <param name="url">When the method returns, contains the normalized API URL if the text represents a valid Showdown team URL; otherwise, null.</param>
/// <returns><see langword="true"/> if the text is a valid Showdown team URL; otherwise, <see langword="false"/>.</returns>
public static bool IsURL(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
{
text = text.Trim();
if (text.StartsWith("https://psim.us/t/") || // short link
text.StartsWith("https://teams.pokemonshowdown.com/"))
{
return TryCheckWeb(text, out url);
}
if (text.StartsWith("https://play.pokemonshowdown.com/api/getteam?teamid="))
return TryCheckAPI(text, out url);
url = null;
return false;
}
/// <summary>
/// Attempts to extract the team identifier from a Showdown web URL and converts it to a standard API URL.
/// </summary>
/// <param name="text">The Showdown web URL as a read-only span of characters.</param>
/// <param name="url">When the method returns, contains the standardized API URL if extraction is successful; otherwise, null.</param>
/// <returns><see langword="true"/> if the team index is successfully extracted and converted; otherwise, <see langword="false"/>.</returns>
public static bool TryCheckWeb(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
{
url = null;
if (!TryGetIndexWeb(text, out var team))
return false;
url = GetURL(team);
return true;
}
/// <summary>
/// Attempts to extract the team identifier from a Showdown API URL and returns a standardized API URL.
/// </summary>
/// <param name="text">The Showdown API URL as a read-only span of characters.</param>
/// <param name="url">When the method returns, contains the standardized API URL if extraction is successful; otherwise, null.</param>
/// <returns><see langword="true"/> if the team index is successfully extracted and the URL normalized; otherwise, <see langword="false"/>.</returns>
public static bool TryCheckAPI(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
{
url = null;
if (!TryGetIndexAPI(text, out var team))
return false;
url = GetURL(team);
return true;
}
/// <summary>
/// Extracts the team identifier from a Showdown web URL.
/// </summary>
/// <param name="text">The Showdown web URL provided as a read-only span of characters.</param>
/// <param name="team">When the method returns, contains the extracted team identifier if successful; otherwise, zero.</param>
/// <returns><see langword="true"/> if the team identifier is successfully extracted; otherwise, <see langword="false"/>.</returns>
public static bool TryGetIndexWeb(ReadOnlySpan<char> text, out int team)
{
team = 0;
if (text.EndsWith('/'))
text = text[..^1]; // remove trailing slash
if (text.EndsWith("/raw"))
text = text[..^4]; // remove trailing /raw
int start = text.LastIndexOf('/'); // seek back to =
if (start == -1)
return false;
var number = text[(start + 1)..];
if (!int.TryParse(number, out team))
return false;
return true;
}
/// <summary>
/// Extracts the team identifier from a Showdown API URL.
/// </summary>
/// <param name="text">The Showdown API URL as a read-only span of characters.</param>
/// <param name="team">When the method returns, contains the extracted team identifier if successful; otherwise, zero.</param>
/// <returns><see langword="true"/> if the team identifier is successfully extracted; otherwise, <see langword="false"/>.</returns>
public static bool TryGetIndexAPI(ReadOnlySpan<char> text, out int team)
{
team = 0;
if (!text.EndsWith("&raw=1"))
return false;
text = text[..^6];
int start = text.LastIndexOf('='); // seek back to =
if (start == -1)
return false;
var number = text[(start + 1)..];
if (!int.TryParse(number, out team))
return false;
return true;
}
}

View File

@ -0,0 +1,411 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace PKHeX.Core;
/// <summary>
/// Configuration for displaying stats.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class StatDisplayConfig
{
/// <summary>
/// Stat names are displayed without localization; H:X A:X B:X C:X D:X S:X
/// </summary>
public static readonly StatDisplayConfig HABCDS = new()
{
Names = ["H", "A", "B", "C", "D", "S"],
Separator = " ",
ValueGap = ":",
IsLeft = true,
AlwaysShow = true,
};
/// <summary>
/// Stat names are displayed without localization; X/X/X/X/X/X
/// </summary>
/// <remarks>
/// Same as <see cref="Raw00"/> but with no leading zeroes.
/// </remarks>
public static readonly StatDisplayConfig Raw = new()
{
Names = [],
Separator = "/",
ValueGap = string.Empty,
AlwaysShow = true,
};
/// <summary>
/// Stat names are displayed without localization; XX/XX/XX/XX/XX/XX
/// </summary>
/// <remarks>
/// Same as <see cref="Raw"/> but with 2 digits (leading zeroes).
/// </remarks>
public static readonly StatDisplayConfig Raw00 = new()
{
Names = [],
Separator = "/",
ValueGap = string.Empty,
AlwaysShow = true,
MinimumDigits = 2,
};
/// <summary>
/// List of stat display styles that are commonly used and not specific to a localization.
/// </summary>
public static List<StatDisplayConfig> Custom { get; } = [HABCDS, Raw]; // Raw00 parses equivalent to Raw
/// <summary>List of stat names to display</summary>
public required string[] Names { get; init; }
/// <summary>Separator between each stat+value declaration</summary>
public string Separator { get; init; } = " / ";
/// <summary>Separator between the stat name and value</summary>
public string ValueGap { get; init; } = " ";
/// <summary><see langword="true"/> if the text is displayed on the left side of the value</summary>
public bool IsLeft { get; init; }
/// <summary><see langword="true"/> if the stat is always shown, even if the value is default</summary>
public bool AlwaysShow { get; init; }
/// <summary>Minimum number of digits to show for the stat value.</summary>
public int MinimumDigits { get; init; }
/// <summary>
/// Gets the index of the displayed stat name (in visual order) via a case-insensitive search.
/// </summary>
/// <param name="stat">Stat name, trimmed.</param>
/// <returns>-1 if not found, otherwise the index of the stat name.</returns>
public int GetStatIndex(ReadOnlySpan<char> stat)
{
for (int i = 0; i < Names.Length; i++)
{
if (stat.Equals(Names[i], StringComparison.OrdinalIgnoreCase))
return i;
}
return -1;
}
public override string ToString() => string.Join(Separator, Names);
/// <summary>
/// Formats a stat value into a string builder.
/// </summary>
/// <param name="sb">Result string builder</param>
/// <param name="statIndex">Display index of the stat</param>
/// <param name="statValue">Stat value</param>
/// <param name="valueSuffix">Optional suffix for the value, to display a stat amplification request</param>
/// <param name="skipValue"><see langword="true"/> to skip the value, only displaying the stat name and amplification (if provided)</param>
public void Format<T>(StringBuilder sb, int statIndex, T statValue, ReadOnlySpan<char> valueSuffix = default, bool skipValue = false)
{
var statName = statIndex < Names.Length ? Names[statIndex] : "";
var length = GetStatSize(statName, statValue, valueSuffix, skipValue);
if (sb.Length + length > sb.Capacity)
sb.EnsureCapacity(sb.Length + length);
Append(sb, statName, statValue, valueSuffix, skipValue);
}
private void Append<T>(StringBuilder sb, ReadOnlySpan<char> statName, T statValue, ReadOnlySpan<char> valueSuffix, bool skipValue)
{
int start = sb.Length;
if (!skipValue)
{
sb.Append(statValue);
var length = sb.Length - start;
if (length < MinimumDigits)
sb.Insert(start, "0", MinimumDigits - length);
}
sb.Append(valueSuffix);
if (IsLeft)
{
sb.Insert(start, ValueGap);
sb.Insert(start, statName);
}
else
{
sb.Append(ValueGap);
sb.Append(statName);
}
}
private int GetStatSize<T>(ReadOnlySpan<char> statName, T statValue, ReadOnlySpan<char> valueSuffix, bool skipValue)
{
var length = statName.Length + ValueGap.Length + valueSuffix.Length;
if (!skipValue)
length += (int)Math.Max(MinimumDigits, Math.Floor(Math.Log10(Convert.ToDouble(statValue)) + 1));
return length;
}
/// <summary>
/// Gets the separator character used for parsing.
/// </summary>
private char GetSeparatorParse() => GetSeparatorParse(Separator);
private static char GetSeparatorParse(ReadOnlySpan<char> sep) => sep.Length switch
{
0 => ' ',
1 => sep[0],
_ => sep.Trim()[0]
};
/// <summary>
/// Imports a list of stats from a string.
/// </summary>
/// <param name="message">Input string</param>
/// <param name="result">Result storage</param>
/// <returns>Parse result</returns>
public StatParseResult TryParse(ReadOnlySpan<char> message, Span<int> result)
{
var separator = GetSeparatorParse();
var gap = ValueGap.AsSpan().Trim();
// If stats are not labeled, parse with the straightforward parser.
if (Names.Length == 0)
return TryParseRaw(message, result, separator);
else if (IsLeft)
return TryParseIsLeft(message, result, separator, gap);
else
return TryParseRight(message, result, separator, gap);
}
private StatParseResult TryParseIsLeft(ReadOnlySpan<char> message, Span<int> result, char separator, ReadOnlySpan<char> valueGap)
{
// Parse left-to-right by splitting on separator, then identifying which stat each segment contains.
// Format: "StatName Value / StatName Value / ..."
var rec = new StatParseResult();
while (message.Length != 0)
{
// Get the next segment
ReadOnlySpan<char> segment;
var indexSeparator = message.IndexOf(separator);
if (indexSeparator != -1)
{
segment = message[..indexSeparator].Trim();
message = message[(indexSeparator + 1)..].TrimStart();
}
else
{
segment = message.Trim();
message = default;
}
if (segment.Length == 0)
{
rec.MarkDirty(); // empty segment
continue;
}
// Find which stat name this segment contains (should be at the start for IsLeft)
var statIndex = TryFindStatNameAtStart(segment, out var statNameLength);
if (statIndex == -1)
{
rec.MarkDirty(); // unrecognized stat
continue;
}
// Extract the value after the stat name
var value = segment[statNameLength..].TrimStart();
if (valueGap.Length > 0 && value.StartsWith(valueGap))
value = value[valueGap.Length..].TrimStart();
if (value.Length != 0)
{
var amped = TryPeekAmp(ref value, ref rec, statIndex);
if (amped && value.Length == 0)
rec.MarkParsed(statIndex);
else
TryParse(result, ref rec, value, statIndex);
}
else if (rec.WasParsed(statIndex))
{
rec.MarkDirty(); // duplicate stat
}
}
rec.FinishParse(Names.Length);
return rec;
}
/// <summary>
/// Tries to find a stat name at the start of the segment.
/// </summary>
/// <param name="segment">Segment to search</param>
/// <param name="length">Length of the matched stat name</param>
/// <returns>Stat index if found, -1 otherwise</returns>
private int TryFindStatNameAtStart(ReadOnlySpan<char> segment, out int length)
{
for (int i = 0; i < Names.Length; i++)
{
var name = Names[i];
if (segment.StartsWith(name, StringComparison.OrdinalIgnoreCase))
{
length = name.Length;
return i;
}
}
length = 0;
return -1;
}
/// <summary>
/// Tries to find a stat name at the end of the segment.
/// </summary>
/// <param name="segment">Segment to search</param>
/// <param name="length">Length of the matched stat name</param>
/// <returns>Stat index if found, -1 otherwise</returns>
private int TryFindStatNameAtEnd(ReadOnlySpan<char> segment, out int length)
{
for (int i = 0; i < Names.Length; i++)
{
var name = Names[i];
if (segment.EndsWith(name, StringComparison.OrdinalIgnoreCase))
{
length = name.Length;
return i;
}
}
length = 0;
return -1;
}
private StatParseResult TryParseRight(ReadOnlySpan<char> message, Span<int> result, char separator, ReadOnlySpan<char> valueGap)
{
// Parse left-to-right by splitting on separator, then identifying which stat each segment contains.
// Format: "Value StatName / Value StatName / ..."
var rec = new StatParseResult();
while (message.Length != 0)
{
// Get the next segment
ReadOnlySpan<char> segment;
var indexSeparator = message.IndexOf(separator);
if (indexSeparator != -1)
{
segment = message[..indexSeparator].Trim();
message = message[(indexSeparator + 1)..].TrimStart();
}
else
{
segment = message.Trim();
message = default;
}
if (segment.Length == 0)
{
rec.MarkDirty(); // empty segment
continue;
}
// Find which stat name this segment contains (should be at the end for Right/English style)
var statIndex = TryFindStatNameAtEnd(segment, out var statNameLength);
if (statIndex == -1)
{
rec.MarkDirty(); // unrecognized stat
continue;
}
// Extract the value before the stat name
var value = segment[..^statNameLength].TrimEnd();
if (valueGap.Length > 0 && value.EndsWith(valueGap))
value = value[..^valueGap.Length].TrimEnd();
if (value.Length != 0)
{
var amped = TryPeekAmp(ref value, ref rec, statIndex);
if (amped && value.Length == 0)
rec.MarkParsed(statIndex);
else
TryParse(result, ref rec, value, statIndex);
}
else if (rec.WasParsed(statIndex))
{
rec.MarkDirty(); // duplicate stat
}
}
rec.FinishParse(Names.Length);
return rec;
}
/// <summary>
/// Parses a raw stat string.
/// </summary>
/// <param name="message">Input string</param>
/// <param name="result">Output storage</param>
/// <param name="separator">Separator character</param>
public static StatParseResult TryParseRaw(ReadOnlySpan<char> message, Span<int> result, char separator)
{
var rec = new StatParseResult();
// Expect the message to contain all entries of `result` separated by the separator and an arbitrary amount of spaces permitted.
// The message is split by the separator, and each part is trimmed of whitespace.
for (int i = 0; i < result.Length; i++)
{
var index = message.IndexOf(separator);
ReadOnlySpan<char> value;
if (index != -1)
{
value = message[..index].TrimEnd();
message = message[(index + 1)..].TrimStart();
}
else // no further iterations to be done
{
value = message;
message = default;
}
if (value.Length == 0)
{
rec.MarkDirty(); // Something is wrong with the message, as we have an empty stat.
continue; // Maybe it's a duplicate separator; keep parsing and hope that the required amount are parsed.
}
var amped = TryPeekAmp(ref value, ref rec, i);
if (amped && value.Length == 0)
rec.MarkParsed(index);
else
TryParse(result, ref rec, value, i);
}
if (!message.IsWhiteSpace()) // shouldn't be anything left in the message to parse
rec.MarkDirty();
rec.FinishParseOnly(result.Length);
return rec;
}
private static void TryParse(Span<int> result, ref StatParseResult rec, ReadOnlySpan<char> value, int statIndex)
{
if (!int.TryParse(value, out var stat) || stat < 0)
{
rec.MarkDirty();
return;
}
result[statIndex] = stat;
rec.MarkParsed(statIndex);
}
private static bool TryPeekAmp(ref ReadOnlySpan<char> value, ref StatParseResult rec, int statIndex)
{
var last = value[^1];
if (last == '+')
{
rec.Plus = (sbyte)statIndex;
value = value[..^1].TrimEnd();
return true;
}
if (last == '-')
{
rec.Minus = (sbyte)statIndex;
value = value[..^1].TrimEnd();
return true;
}
return false;
}
}

View File

@ -0,0 +1,37 @@
namespace PKHeX.Core;
/// <summary>
/// Style to display stat names.
/// </summary>
public enum StatDisplayStyle : sbyte
{
Custom = -1,
/// <summary>
/// Stat names are displayed in abbreviated (2-3 characters) localized text.
/// </summary>
Abbreviated,
/// <summary>
/// Stat names are displayed in full localized text.
/// </summary>
Full,
/// <summary>
/// Stat names are displayed as a single character.
/// </summary>
/// <remarks>
/// This is the typical format used by the Japanese community; HABCDS.
/// </remarks>
HABCDS,
/// <summary>
/// Stat names are displayed without localization; X/X/X/X/X/X
/// </summary>
Raw,
/// <summary>
/// Stat names are displayed without localization; XX/XX/XX/XX/XX/XX
/// </summary>
Raw00,
}

View File

@ -0,0 +1,132 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Value result object of parsing a stat string.
/// </summary>
public record struct StatParseResult()
{
private const uint MaxStatCount = 6; // Number of stats in the game
public const sbyte NoStatAmp = -1;
/// <summary>
/// Count of parsed stats.
/// </summary>
public byte CountParsed { get; private set; } = 0; // could potentially make this a computed value (popcnt), but it's not worth it
/// <summary>
/// Bitflag indexes of parsed stats, indexed in visual order.
/// </summary>
public byte IndexesParsed { get; private set; } = 0;
/// <summary>
/// Stat index of increased stat, indexed in visual order.
/// </summary>
public sbyte Plus { get; set; } = NoStatAmp;
/// <summary>
/// Stat index of decreased stat, indexed in visual order.
/// </summary>
public sbyte Minus { get; set; } = NoStatAmp;
/// <summary>
/// Indicates if the parsing was clean (no un-parsed text).
/// </summary>
public bool IsParseClean { get; private set; } = true;
/// <summary>
/// Indicates if all stat indexes available were parsed.
/// </summary>
public bool IsParsedAllStats { get; private set; }
/// <summary>
/// Marks the stat index as parsed, and updates the count of parsed stats.
/// </summary>
/// <param name="statIndex">Visual index of the stat to mark as parsed.</param>
/// <returns>True if the stat had not been parsed before, false if it was already parsed.</returns>
public bool MarkParsed(int statIndex)
{
// Check if the stat index is valid (0-5)
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)statIndex, MaxStatCount);
if (WasParsed(statIndex))
return false;
// Mark the stat index as parsed
IndexesParsed |= (byte)(1 << statIndex);
++CountParsed;
return true;
}
/// <summary>
/// Checks if the stat index was parsed.
/// </summary>
/// <param name="statIndex">Visual index of the stat to check.</param>
/// <returns>True if the stat was parsed, false otherwise.</returns>
public readonly bool WasParsed(int statIndex)
{
// Check if the stat index is valid (0-5)
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)statIndex, MaxStatCount);
return (IndexesParsed & (1 << statIndex)) != 0;
}
/// <summary>
/// Marks the parsing as finished, and updates the internal state to indicate if all stats were parsed.
/// </summary>
/// <remarks>
/// This is used when not all stats are required to be parsed.
/// </remarks>
/// <param name="expect"></param>
public void FinishParse(int expect)
{
if (CountParsed == 0 && !HasAmps)
MarkDirty();
IsParsedAllStats = CountParsed == expect || IsParseClean;
}
/// <summary>
/// Marks the parsing as finished, and updates the internal state to indicate if all stats were parsed.
/// </summary>
/// <remarks>
/// This is used when a specific number of stats is expected.
/// </remarks>
/// <param name="expect"></param>
public void FinishParseOnly(int expect) => IsParsedAllStats = CountParsed == expect;
/// <summary>
/// Marks the parsing as dirty, indicating that the string was not a clean input string (user modified or the syntax doesn't match the spec).
/// </summary>
public void MarkDirty() => IsParseClean = false;
/// <summary>
/// Indicates if any stat has any amplified (+/-) requested, indicative of nature.
/// </summary>
public readonly bool HasAmps => Plus != NoStatAmp || Minus != NoStatAmp;
/// <summary>
/// Reorders the speed stat to be in the middle of the stats.
/// </summary>
/// <remarks>
/// Speed is visually represented as the last stat in the list, but it is actually the 3rd stat stored.
/// </remarks>
public void TreatAmpsAsSpeedNotLast()
{
Plus = GetSpeedMiddleIndex(Plus);
Minus = GetSpeedMiddleIndex(Minus);
}
/// <summary>
/// Adjusts stat indexes from visual to stored, and ignoring HP's index.
/// </summary>
/// <param name="amp">Visual index of the stat to get the adjusted value for.</param>
/// <returns>Stored index of the stat.</returns>
private static sbyte GetSpeedMiddleIndex(sbyte amp) => amp switch
{
// 0 => NoStatAmp -- handle via default case
1 => 0, // Atk
2 => 1, // Def
3 => 3, // SpA
4 => 4, // SpD
5 => 2, // Spe
_ => NoStatAmp,
};
}

View File

@ -0,0 +1,535 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Reflection;
using static PKHeX.Core.BatchEditingUtil;
namespace PKHeX.Core;
/// <summary>
/// Base logic for editing entities with user provided <see cref="StringInstruction"/> list.
/// </summary>
/// <remarks>
/// Caches reflection results for the provided types, and provides utility methods for fetching properties and applying instructions.
/// </remarks>
public abstract class BatchEditingBase<TObject, TMeta> : IBatchEditor<TObject> where TObject : notnull
{
private readonly Type[] _types;
private readonly string[] _customProperties;
private readonly Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] _props;
private readonly Lazy<string[][]> _properties;
protected BatchEditingBase(Type[] types, string[] customProperties, int expectedMax)
{
_types = types;
_customProperties = customProperties;
_props = GetPropertyDictionaries(types, expectedMax);
_properties = new Lazy<string[][]>(() => GetPropArray(_props, customProperties));
}
/// <summary>
/// Property names, indexed by <see cref="Types"/>.
/// </summary>
public string[][] Properties => _properties.Value;
/// <summary>
/// Gets the list of supported entity types.
/// </summary>
public IReadOnlyList<Type> Types => _types;
protected abstract TMeta CreateMeta(TObject entity);
protected abstract bool ShouldModify(TObject entity);
protected abstract bool TryHandleSetOperation(StringInstruction cmd, TMeta info, TObject entity, out ModifyResult result);
protected abstract bool TryHandleFilter(StringInstruction cmd, TMeta info, TObject entity, out bool isMatch);
/// <summary>
/// Tries to fetch the entity property from the cache of available properties.
/// </summary>
public bool TryGetHasProperty(TObject entity, ReadOnlySpan<char> name, [NotNullWhen(true)] out PropertyInfo? pi)
=> TryGetHasProperty(entity.GetType(), name, out pi);
/// <summary>
/// Tries to fetch the entity property from the cache of available properties.
/// </summary>
public bool TryGetHasProperty(Type type, ReadOnlySpan<char> name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var index = _types.IndexOf(type);
if (index < 0)
{
pi = null;
return false;
}
var localProps = _props[index];
return localProps.TryGetValue(name, out pi);
}
/// <summary>
/// Gets a list of entity types that implement the requested property.
/// </summary>
public IEnumerable<string> GetTypesImplementing(string property)
{
for (int i = 0; i < _types.Length; i++)
{
var type = _types[i];
var localProps = _props[i];
if (!localProps.TryGetValue(property, out var pi))
continue;
yield return $"{type.Name}: {pi.PropertyType.Name}";
}
}
/// <summary>
/// Gets the type of the entity property using the saved cache of properties.
/// </summary>
public bool TryGetPropertyType(string propertyName, [NotNullWhen(true)] out string? result, int typeIndex = 0)
{
if (_customProperties.Contains(propertyName))
{
result = "Custom";
return true;
}
result = null;
if (typeIndex == 0)
{
foreach (var p in _props)
{
if (!p.TryGetValue(propertyName, out var pi))
continue;
result = pi.PropertyType.Name;
return true;
}
return false;
}
int index = typeIndex - 1;
if ((uint)index >= _props.Length)
index = 0;
var pr = _props[index];
if (!pr.TryGetValue(propertyName, out var info))
return false;
result = info.PropertyType.Name;
return true;
}
/// <summary>
/// Checks if the entity is filtered by the provided filters.
/// </summary>
public bool IsFilterMatch(IEnumerable<StringInstruction> filters, TObject entity)
{
var info = CreateMeta(entity);
var localProps = GetProps(entity);
foreach (var filter in filters)
{
if (!IsFilterMatch(filter, info, entity, localProps))
return false;
}
return true;
}
/// <summary>
/// Tries to modify the entity.
/// </summary>
public bool TryModifyIsSuccess(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null)
=> TryModify(entity, filters, modifications, modifier) is ModifyResult.Modified;
/// <summary>
/// Tries to modify the entity using instructions and a custom modifier delegate.
/// </summary>
public ModifyResult TryModify(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null)
{
if (!ShouldModify(entity))
return ModifyResult.Skipped;
var info = CreateMeta(entity);
var localProps = GetProps(entity);
foreach (var cmd in filters)
{
try
{
if (!IsFilterMatch(cmd, info, entity, localProps))
return ModifyResult.Filtered;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return ModifyResult.Error;
}
}
var error = false;
var result = ModifyResult.Skipped;
if (modifier is { } func)
{
try
{
if (!func(entity))
return ModifyResult.Skipped;
result = ModifyResult.Modified;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return ModifyResult.Error;
}
}
foreach (var cmd in modifications)
{
try
{
var tmp = SetProperty(cmd, entity, info, localProps);
if (tmp == ModifyResult.Error)
error = true;
else if (tmp != ModifyResult.Skipped)
result = tmp;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
error = true;
}
}
if (error)
result |= ModifyResult.Error;
return result;
}
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(ReadOnlySpan<Type> types, int expectedMax)
{
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;
}
private static Dictionary<string, PropertyInfo> GetPropertyDictionary(Type type, Func<Type, IEnumerable<PropertyInfo>> selector, int expectedMax)
{
var dict = new Dictionary<string, PropertyInfo>(expectedMax);
var localProps = selector(type);
foreach (var p in localProps)
dict.TryAdd(p.Name, p);
return dict;
}
private static string[][] GetPropArray<T>(Dictionary<string, T>.AlternateLookup<ReadOnlySpan<char>>[] types, ReadOnlySpan<string> extra)
{
var result = new string[types.Length + 2][];
var p = result.AsSpan(1, types.Length);
for (int i = 0; i < p.Length; i++)
{
var type = types[i].Dictionary;
string[] combine = [..type.Keys, ..extra];
combine.Sort();
p[i] = combine;
}
var first = p[0];
var any = new HashSet<string>(first);
var all = new HashSet<string>(first);
foreach (var set in p[1..])
{
any.UnionWith(set);
all.IntersectWith(set);
}
var arrAny = any.ToArray();
arrAny.Sort();
result[0] = arrAny;
var arrAll = all.ToArray();
arrAll.Sort();
result[^1] = arrAll;
return result;
}
private Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> GetProps(TObject entity)
{
var type = entity.GetType();
var typeIndex = _types.IndexOf(type);
return _props[typeIndex];
}
private bool IsFilterMatch(StringInstruction cmd, TMeta info, TObject entity, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps)
{
if (TryHandleFilter(cmd, info, entity, out var isMatch))
return isMatch;
return IsPropertyFiltered(cmd, entity, localProps);
}
private static bool IsPropertyFiltered(StringInstruction cmd, TObject entity, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps)
{
if (!localProps.TryGetValue(cmd.PropertyName, out var pi))
return false;
if (!pi.CanRead)
return false;
var val = cmd.PropertyValue;
if (val.StartsWith(PointerToken) && localProps.TryGetValue(val.AsSpan(1), out var opi))
{
var result = opi.GetValue(entity) ?? throw new NullReferenceException();
return cmd.Comparer.IsCompareOperator(pi.CompareTo(entity, result));
}
return cmd.Comparer.IsCompareOperator(pi.CompareTo(entity, val));
}
private ModifyResult SetProperty(StringInstruction cmd, TObject entity, TMeta info, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps)
{
if (cmd.Operation == InstructionOperation.Set && TryHandleSetOperation(cmd, info, entity, out var result))
return result;
if (!localProps.TryGetValue(cmd.PropertyName, out var pi))
return ModifyResult.Error;
if (!pi.CanWrite)
return ModifyResult.Error;
if (cmd.Operation != InstructionOperation.Set)
return ApplyNumericOperation(entity, cmd, pi, localProps);
if (!TryResolveOperandValue(cmd, entity, localProps, out var value))
return ModifyResult.Error;
ReflectUtil.SetValue(pi, entity, value);
return ModifyResult.Modified;
}
private static ModifyResult ApplyNumericOperation(TObject entity, StringInstruction cmd, PropertyInfo pi, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps)
{
if (!pi.CanRead)
return ModifyResult.Error;
if (!TryGetNumericType(pi.PropertyType, out var numericType))
return ModifyResult.Error;
var currentValue = pi.GetValue(entity);
if (currentValue is null)
return ModifyResult.Error;
if (!TryResolveOperandValue(cmd, entity, localProps, out var operandValue))
return ModifyResult.Error;
if (!TryApplyNumericOperation(numericType, cmd.Operation, currentValue, operandValue, out var value))
return ModifyResult.Error;
ReflectUtil.SetValue(pi, entity, value);
return ModifyResult.Modified;
}
private static bool TryResolveOperandValue(StringInstruction cmd, TObject entity, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps, [NotNullWhen(true)] out object? value)
{
if (cmd.Random)
{
value = cmd.RandomValue;
return true;
}
var propertyValue = cmd.PropertyValue;
if (propertyValue.StartsWith(PointerToken) && localProps.TryGetValue(propertyValue.AsSpan(1), out var opi))
{
value = opi.GetValue(entity);
return value is not null;
}
value = propertyValue;
return true;
}
private static bool TryGetNumericType(Type type, out Type numericType)
{
numericType = Nullable.GetUnderlyingType(type) ?? type;
// bool isNullable = type != numericType;
return numericType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(INumber<>));
}
private static bool TryApplyNumericOperation(Type numericType, InstructionOperation operation, object currentValue, object operandValue, [NotNullWhen(true)] out object? result)
{
result = null;
if (numericType == typeof(byte))
return ApplyBinaryInteger<byte>(currentValue, operandValue, operation, out result);
if (numericType == typeof(sbyte))
return ApplyBinaryInteger<sbyte>(currentValue, operandValue, operation, out result);
if (numericType == typeof(short))
return ApplyBinaryInteger<short>(currentValue, operandValue, operation, out result);
if (numericType == typeof(ushort))
return ApplyBinaryInteger<ushort>(currentValue, operandValue, operation, out result);
if (numericType == typeof(int))
return ApplyBinaryInteger<int>(currentValue, operandValue, operation, out result);
if (numericType == typeof(uint))
return ApplyBinaryInteger<uint>(currentValue, operandValue, operation, out result);
if (numericType == typeof(long))
return ApplyBinaryInteger<long>(currentValue, operandValue, operation, out result);
if (numericType == typeof(ulong))
return ApplyBinaryInteger<ulong>(currentValue, operandValue, operation, out result);
if (numericType == typeof(nint))
return ApplyBinaryInteger<nint>(currentValue, operandValue, operation, out result);
if (numericType == typeof(nuint))
return ApplyBinaryInteger<nuint>(currentValue, operandValue, operation, out result);
if (numericType == typeof(BigInteger))
return ApplyBinaryInteger<BigInteger>(currentValue, operandValue, operation, out result);
if (numericType == typeof(float))
return ApplyNumeric<float>(currentValue, operandValue, operation, out result);
if (numericType == typeof(double))
return ApplyNumeric<double>(currentValue, operandValue, operation, out result);
if (numericType == typeof(decimal))
return ApplyNumeric<decimal>(currentValue, operandValue, operation, out result);
return false;
}
private static bool ApplyNumeric<T>(object currentValue, object operandValue, InstructionOperation operation, [NotNullWhen(true)] out object? result)
where T : INumber<T>
{
if (operation.IsBitwise)
{
result = null;
return false;
}
var success = TryApplyNumericOperationCore<T>(operation, currentValue, operandValue, out var typed);
result = typed;
return success;
}
private static bool ApplyBinaryInteger<T>(object currentValue, object operandValue, InstructionOperation operation, [NotNullWhen(true)] out object? result)
where T : IBinaryInteger<T>
{
var success = operation.IsBitwise
? TryApplyBinaryIntegerOperationCore<T>(operation, currentValue, operandValue, out var typed)
: TryApplyNumericOperationCore(operation, currentValue, operandValue, out typed);
result = typed;
return success;
}
private static bool TryApplyNumericOperationCore<T>(InstructionOperation operation, object currentValue, object operandValue, [NotNullWhen(true)] out T? result)
where T : INumber<T>
{
if (!TryConvertNumeric<T>(currentValue, out var left) || !TryConvertNumeric<T>(operandValue, out var right))
{
result = default;
return false;
}
return TryApplyNumericOperationCore(operation, left, right, out result);
}
private static bool TryApplyNumericOperationCore<T>(InstructionOperation operation, T left, T right, [NotNullWhen(true)] out T? result)
where T : INumber<T>
{
try
{
result = operation switch
{
InstructionOperation.Add => left + right,
InstructionOperation.Subtract => left - right,
InstructionOperation.Multiply => left * right,
InstructionOperation.Divide => left / right,
InstructionOperation.Modulo => left % right,
_ => right,
};
return true;
}
catch (DivideByZeroException)
{
result = default;
return false;
}
}
private static bool TryApplyBinaryIntegerOperationCore<T>(InstructionOperation operation, object currentValue, object operandValue, [NotNullWhen(true)] out T? result)
where T : IBinaryInteger<T>
{
if (!TryConvertNumeric<T>(currentValue, out var left) || !TryConvertNumeric<T>(operandValue, out var right))
{
result = default;
return false;
}
return TryApplyBinaryIntegerOperationCore(operation, left, right, out result);
}
private static bool TryApplyBinaryIntegerOperationCore<T>(InstructionOperation operation, T left, T right, [NotNullWhen(true)] out T? result)
where T : IBinaryInteger<T>
{
try
{
switch (operation)
{
case InstructionOperation.BitwiseAnd:
result = left & right;
return true;
case InstructionOperation.BitwiseOr:
result = left | right;
return true;
case InstructionOperation.BitwiseXor:
result = left ^ right;
return true;
case InstructionOperation.BitwiseShiftLeft:
result = left << int.CreateChecked(right);
return true;
case InstructionOperation.BitwiseShiftRight:
result = left >> int.CreateChecked(right);
return true;
default:
result = default;
return false;
}
}
catch (OverflowException)
{
result = default;
return false;
}
}
private static bool TryConvertNumeric<T>(object value, [NotNullWhen(true)] out T? result) where T : INumber<T>
{
if (value is T typed)
{
result = typed;
return true;
}
if (value is string text)
{
if (T.TryParse(text, CultureInfo.InvariantCulture, out var parsed))
{
result = parsed;
return true;
}
result = default;
return false;
}
if (value is IConvertible)
{
try
{
var converted = Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
if (converted is T convertedValue)
{
result = convertedValue;
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
result = default;
return false;
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace PKHeX.Core;
public static class BatchEditingUtil
{
public const string PROP_TYPENAME = "ObjectType";
public const char PointerToken = '*';
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <remarks>
/// Does not use cached reflection; less performant than a cached <see cref="BatchEditingBase{TObject,TMeta}"/> implementation.
/// </remarks>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="obj">Object to check.</param>
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
public static bool IsFilterMatch<T>(IEnumerable<StringInstruction> filters, T obj) where T : notnull
{
foreach (var cmd in filters)
{
var name = cmd.PropertyName;
var value = cmd.PropertyValue;
if (name is PROP_TYPENAME)
{
var type = obj.GetType();
var typeName = type.Name;
if (!cmd.Comparer.IsCompareEquivalence(value == typeName))
return false;
continue;
}
if (!ReflectUtil.HasProperty(obj, name, out var pi))
return false;
try
{
if (cmd.Comparer.IsCompareOperator(pi.CompareTo(obj, value)))
continue;
}
// User provided inputs can mismatch the type's required value format, and fail to be compared.
catch (Exception e)
{
Debug.WriteLine($"Unable to compare {name} to {value}.");
Debug.WriteLine(e.Message);
}
return false;
}
return true;
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace PKHeX.Core;
/// <summary>
/// Provides batch editing helpers for an entity type.
/// </summary>
public interface IBatchEditor<TObject> where TObject : notnull
{
/// <summary>
/// Gets the list of supported entity types.
/// </summary>
IReadOnlyList<Type> Types { get; }
/// <summary>
/// Gets the property names, indexed by <see cref="Types"/>.
/// </summary>
string[][] Properties { get; }
/// <summary>
/// Tries to fetch the entity property from the cache of available properties.
/// </summary>
bool TryGetHasProperty(TObject entity, ReadOnlySpan<char> name, [NotNullWhen(true)] out PropertyInfo? pi);
/// <summary>
/// Tries to fetch the entity property from the cache of available properties.
/// </summary>
bool TryGetHasProperty(Type type, ReadOnlySpan<char> name, [NotNullWhen(true)] out PropertyInfo? pi);
/// <summary>
/// Gets a list of entity types that implement the requested property.
/// </summary>
IEnumerable<string> GetTypesImplementing(string property);
/// <summary>
/// Gets the type of the entity property using the saved cache of properties.
/// </summary>
bool TryGetPropertyType(string propertyName, [NotNullWhen(true)] out string? result, int typeIndex = 0);
/// <summary>
/// Checks if the entity is filtered by the provided filters.
/// </summary>
bool IsFilterMatch(IEnumerable<StringInstruction> filters, TObject entity);
/// <summary>
/// Tries to modify the entity.
/// </summary>
bool TryModifyIsSuccess(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null);
/// <summary>
/// Tries to modify the entity using instructions and a custom modifier delegate.
/// </summary>
ModifyResult TryModify(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null);
}

View File

@ -0,0 +1,71 @@
using System;
using static PKHeX.Core.InstructionComparer;
namespace PKHeX.Core;
/// <summary>
/// Value comparison type
/// </summary>
public enum InstructionComparer : byte
{
None,
IsEqual,
IsNotEqual,
IsGreaterThan,
IsGreaterThanOrEqual,
IsLessThan,
IsLessThanOrEqual,
}
/// <summary>
/// Extension methods for <see cref="InstructionComparer"/>
/// </summary>
public static class InstructionComparerExtensions
{
extension(InstructionComparer comparer)
{
/// <summary>
/// Indicates if the <see cref="comparer"/> is supported by the logic.
/// </summary>
/// <returns>True if supported, false if unsupported.</returns>
public bool IsSupported => comparer switch
{
IsEqual => true,
IsNotEqual => true,
IsGreaterThan => true,
IsGreaterThanOrEqual => true,
IsLessThan => true,
IsLessThanOrEqual => true,
_ => false,
};
/// <summary>
/// Checks if the compare operator is satisfied by a boolean comparison result.
/// </summary>
/// <param name="compareResult">Result from Equals comparison</param>
/// <returns>True if satisfied</returns>
/// <remarks>Only use this method if the comparison is boolean only. Use the <see cref="IsCompareOperator"/> otherwise.</remarks>
public bool IsCompareEquivalence(bool compareResult) => comparer switch
{
IsEqual => compareResult,
IsNotEqual => !compareResult,
_ => false,
};
/// <summary>
/// Checks if the compare operator is satisfied by the <see cref="IComparable{T}.CompareTo"/> result.
/// </summary>
/// <param name="compareResult">Result from CompareTo</param>
/// <returns>True if satisfied</returns>
public bool IsCompareOperator(int compareResult) => comparer switch
{
IsEqual => compareResult is 0,
IsNotEqual => compareResult is not 0,
IsGreaterThan => compareResult > 0,
IsGreaterThanOrEqual => compareResult >= 0,
IsLessThan => compareResult < 0,
IsLessThanOrEqual => compareResult <= 0,
_ => false,
};
}
}

View File

@ -0,0 +1,37 @@
using static PKHeX.Core.InstructionOperation;
namespace PKHeX.Core;
/// <summary>
/// Operation type for applying a modification.
/// </summary>
public enum InstructionOperation : byte
{
Set,
Add,
Subtract,
Multiply,
Divide,
Modulo,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
BitwiseShiftRight,
BitwiseShiftLeft,
}
public static class InstructionOperationExtensions
{
extension(InstructionOperation operation)
{
public bool IsBitwise => operation switch
{
BitwiseAnd => true,
BitwiseOr => true,
BitwiseXor => true,
BitwiseShiftRight => true,
BitwiseShiftLeft => true,
_ => false,
};
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Batch Editor Modification result for an individual processing operation.
/// </summary>
[Flags]
public enum ModifyResult
{
/// <summary>
/// No modifications were performed as a filter excluded it.
/// </summary>
Filtered,
/// <summary>
/// Not a suitable candidate for modification.
/// </summary>
Skipped,
/// <summary>
/// One or more modifications was successfully applied.
/// </summary>
Modified,
/// <summary>
/// An error was occurred while attempting modifications.
/// </summary>
Error = 0x80,
}

View File

@ -16,33 +16,63 @@ namespace PKHeX.Core;
/// <see cref="FilterNotEqual"/>
/// <see cref="FilterEqual"/>
/// <see cref="Apply"/>
public sealed class StringInstruction
/// <param name="PropertyName">Property to modify.</param>
/// <param name="PropertyValue">Value to set to the property.</param>
/// <param name="Comparer">Filter Comparison Type</param>
public sealed record StringInstruction(string PropertyName, string PropertyValue, InstructionComparer Comparer, InstructionOperation Operation = InstructionOperation.Set)
{
/// <summary> Property to modify. </summary>
public string PropertyName { get; }
/// <summary> Value to set to the property. </summary>
public string PropertyValue { get; private set; }
/// <summary> Filter Comparison Type </summary>
public InstructionComparer Comparer { get; private init; }
public string PropertyValue { get; private set; } = PropertyValue;
public StringInstruction(string name, string value)
{
PropertyName = name;
PropertyValue = value;
}
public void SetScreenedValue(ReadOnlySpan<string> arr)
/// <summary>
/// Sets the <see cref="PropertyValue"/> to the index of the value in the input <see cref="arr"/>, if it exists.
/// </summary>
/// <param name="arr">List of values to search for the <see cref="PropertyValue"/>.</param>
/// <returns>True if the value was found and set, false otherwise.</returns>
public bool SetScreenedValue(ReadOnlySpan<string> arr)
{
int index = arr.IndexOf(PropertyValue);
if ((uint)index < arr.Length)
PropertyValue = index.ToString();
if ((uint)index >= arr.Length)
return false;
PropertyValue = index.ToString();
return true;
}
/// <summary>
/// Valid prefixes that are recognized for <see cref="InstructionComparer"/> value comparison types.
/// </summary>
public static ReadOnlySpan<char> Prefixes => new[] { Apply, FilterEqual, FilterNotEqual, FilterGreaterThan, FilterGreaterThanOrEqual, FilterLessThan, FilterLessThanOrEqual };
public static ReadOnlySpan<char> Prefixes =>
[
Apply,
FilterEqual, FilterNotEqual, FilterGreaterThan, FilterGreaterThanOrEqual, FilterLessThan, FilterLessThanOrEqual,
ApplyAdd, ApplySubtract, ApplyMultiply, ApplyDivide, ApplyModulo,
ApplyBitwiseAnd, ApplyBitwiseOr, ApplyBitwiseXor, ApplyBitwiseShiftRight, ApplyBitwiseShiftLeft,
];
public static bool IsFilterInstruction(char c) => c switch
{
FilterEqual => true,
FilterNotEqual => true,
FilterGreaterThan => true,
FilterLessThan => true,
FilterGreaterThanOrEqual => true,
FilterLessThanOrEqual => true,
_ => false,
};
public static bool IsMutationInstruction(char c) => !IsFilterInstruction(c);
private const char Apply = '.';
private const char ApplyAdd = '+';
private const char ApplySubtract = '-';
private const char ApplyMultiply = '*';
private const char ApplyDivide = '/';
private const char ApplyModulo = '%';
private const char ApplyBitwiseAnd = '&';
private const char ApplyBitwiseOr = '|';
private const char ApplyBitwiseXor = '^';
private const char ApplyBitwiseShiftRight = '»';
private const char ApplyBitwiseShiftLeft = '«';
private const char SplitRange = ',';
private const char FilterEqual = '=';
@ -92,8 +122,7 @@ public static bool IsRandomRange(ReadOnlySpan<char> str)
public void SetRandomRange(ReadOnlySpan<char> str)
{
var index = str.IndexOf(SplitRange);
if (index <= 0)
throw new ArgumentException($"Invalid Random Range: {str.ToString()}", nameof(str));
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(index);
var min = str[..index];
var max = str[(index + 1)..];
@ -242,7 +271,7 @@ public static bool TryParseFilter(ReadOnlySpan<char> line, [NotNullWhen(true)] o
if (line.Length is 0)
return false;
var comparer = GetComparer(line[0]);
if (!comparer.IsSupportedComparer())
if (!comparer.IsSupported)
return false;
return TryParseSplitTuple(line[1..], ref entry, comparer);
}
@ -253,19 +282,19 @@ public static bool TryParseFilter(ReadOnlySpan<char> line, [NotNullWhen(true)] o
public static bool TryParseInstruction(ReadOnlySpan<char> line, [NotNullWhen(true)] out StringInstruction? entry)
{
entry = null;
if (line.Length is 0 || line[0] is not Apply)
if (line.Length is 0 || !TryGetOperation(line[0], out var operation))
return false;
return TryParseSplitTuple(line[1..], ref entry);
return TryParseSplitTuple(line[1..], ref entry, default, operation);
}
/// <summary>
/// Tries to split a <see cref="StringInstruction"/> tuple from the input <see cref="tuple"/>.
/// </summary>
public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, [NotNullWhen(true)] ref StringInstruction? entry, InstructionComparer eval = default)
public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, [NotNullWhen(true)] ref StringInstruction? entry, InstructionComparer eval = default, InstructionOperation operation = InstructionOperation.Set)
{
if (!TryParseSplitTuple(tuple, out var name, out var value))
return false;
entry = new StringInstruction(name.ToString(), value.ToString()) { Comparer = eval };
entry = new StringInstruction(name.ToString(), value.ToString(), eval, operation);
return true;
}
@ -292,8 +321,6 @@ public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, out ReadOnlySpan
/// <summary>
/// Gets the <see cref="InstructionComparer"/> from the input <see cref="opCode"/>.
/// </summary>
/// <param name="opCode"></param>
/// <returns></returns>
public static InstructionComparer GetComparer(char opCode) => opCode switch
{
FilterEqual => IsEqual,
@ -304,71 +331,50 @@ public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, out ReadOnlySpan
FilterLessThanOrEqual => IsLessThanOrEqual,
_ => None,
};
}
/// <summary>
/// Value comparison type
/// </summary>
public enum InstructionComparer : byte
{
None,
IsEqual,
IsNotEqual,
IsGreaterThan,
IsGreaterThanOrEqual,
IsLessThan,
IsLessThanOrEqual,
}
/// <summary>
/// Extension methods for <see cref="InstructionComparer"/>
/// </summary>
public static class InstructionComparerExtensions
{
/// <summary>
/// Indicates if the <see cref="comparer"/> is supported by the logic.
/// </summary>
/// <param name="comparer">Type of comparison requested</param>
/// <returns>True if supported, false if unsupported.</returns>
public static bool IsSupportedComparer(this InstructionComparer comparer) => comparer switch
{
IsEqual => true,
IsNotEqual => true,
IsGreaterThan => true,
IsGreaterThanOrEqual => true,
IsLessThan => true,
IsLessThanOrEqual => true,
_ => false,
};
/// <summary>
/// Checks if the compare operator is satisfied by a boolean comparison result.
/// Gets the <see cref="InstructionOperation"/> from the input <see cref="opCode"/>.
/// </summary>
/// <param name="comparer">Type of comparison requested</param>
/// <param name="compareResult">Result from Equals comparison</param>
/// <returns>True if satisfied</returns>
/// <remarks>Only use this method if the comparison is boolean only. Use the <see cref="IsCompareOperator"/> otherwise.</remarks>
public static bool IsCompareEquivalence(this InstructionComparer comparer, bool compareResult) => comparer switch
public static bool TryGetOperation(char opCode, out InstructionOperation operation)
{
IsEqual => compareResult,
IsNotEqual => !compareResult,
_ => false,
};
/// <summary>
/// Checks if the compare operator is satisfied by the <see cref="IComparable.CompareTo"/> result.
/// </summary>
/// <param name="comparer">Type of comparison requested</param>
/// <param name="compareResult">Result from CompareTo</param>
/// <returns>True if satisfied</returns>
public static bool IsCompareOperator(this InstructionComparer comparer, int compareResult) => comparer switch
{
IsEqual => compareResult is 0,
IsNotEqual => compareResult is not 0,
IsGreaterThan => compareResult > 0,
IsGreaterThanOrEqual => compareResult >= 0,
IsLessThan => compareResult < 0,
IsLessThanOrEqual => compareResult <= 0,
_ => false,
};
switch (opCode)
{
case Apply:
operation = InstructionOperation.Set;
return true;
case ApplyAdd:
operation = InstructionOperation.Add;
return true;
case ApplySubtract:
operation = InstructionOperation.Subtract;
return true;
case ApplyMultiply:
operation = InstructionOperation.Multiply;
return true;
case ApplyDivide:
operation = InstructionOperation.Divide;
return true;
case ApplyModulo:
operation = InstructionOperation.Modulo;
return true;
case ApplyBitwiseAnd:
operation = InstructionOperation.BitwiseAnd;
return true;
case ApplyBitwiseOr:
operation = InstructionOperation.BitwiseOr;
return true;
case ApplyBitwiseXor:
operation = InstructionOperation.BitwiseXor;
return true;
case ApplyBitwiseShiftRight:
operation = InstructionOperation.BitwiseShiftRight;
return true;
case ApplyBitwiseShiftLeft:
operation = InstructionOperation.BitwiseShiftLeft;
return true;
default:
operation = default;
return false;
}
}
}

View File

@ -153,7 +153,7 @@ public static int GetInstructionSetLength(ReadOnlySpan<string> lines)
while (start < lines.Length)
{
var line = lines[start++];
if (line.Length != 0 && line[0] == SetSeparatorChar)
if (line.StartsWith(SetSeparatorChar))
return start;
}
return start;

View File

@ -1,550 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using static PKHeX.Core.MessageStrings;
using static PKHeX.Core.BatchModifications;
namespace PKHeX.Core;
/// <summary>
/// Logic for editing many <see cref="PKM"/> with user provided <see cref="StringInstruction"/> list.
/// </summary>
public static class BatchEditing
{
public static readonly Type[] Types =
{
typeof (PK9),
typeof (PK8), typeof (PA8), typeof (PB8),
typeof (PB7),
typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4), typeof(RK4),
typeof (PK3), typeof (XK3), typeof (CK3),
typeof (PK2), typeof (SK2), typeof (PK1),
};
/// <summary>
/// Extra properties to show in the list of selectable properties (GUI)
/// </summary>
public static readonly List<string> CustomProperties = new()
{
PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS, PROP_CONTESTSTATS, PROP_MOVEMASTERY,
PROP_TYPE1, PROP_TYPE2, PROP_TYPEEITHER,
IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box),
};
/// <summary>
/// Property names, indexed by <see cref="Types"/>.
/// </summary>
public static string[][] Properties => GetProperties.Value;
private static readonly Lazy<string[][]> GetProperties = new(() => GetPropArray(Types, CustomProperties));
private static readonly Dictionary<string, PropertyInfo>[] Props = GetPropertyDictionaries(Types);
private static Dictionary<string, PropertyInfo>[] GetPropertyDictionaries(IReadOnlyList<Type> types)
{
var result = new Dictionary<string, PropertyInfo>[types.Count];
for (int i = 0; i < types.Count; i++)
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic);
return result;
}
private static Dictionary<string, PropertyInfo> GetPropertyDictionary(Type type, Func<Type, IEnumerable<PropertyInfo>> selector)
{
const int expectedMax = 0x200; // currently 0x160 as of 2022
var dict = new Dictionary<string, PropertyInfo>(expectedMax);
var props = selector(type);
foreach (var p in props)
dict.TryAdd(p.Name, p);
return dict;
}
internal const string CONST_RAND = "$rand";
internal const string CONST_SHINY = "$shiny";
internal const string CONST_SUGGEST = "$suggest";
private const string CONST_BYTES = "$[]";
private const char CONST_POINTER = '*';
internal const char CONST_SPECIAL = '$';
internal const string PROP_LEGAL = "Legal";
internal const string PROP_TYPENAME = "ObjectType";
internal const string PROP_TYPEEITHER = "HasType";
internal const string PROP_TYPE1 = "PersonalType1";
internal const string PROP_TYPE2 = "PersonalType2";
internal const string PROP_RIBBONS = "Ribbons";
internal const string PROP_EVS = "EVs";
internal const string PROP_CONTESTSTATS = "ContestStats";
internal const string PROP_MOVEMASTERY = "MoveMastery";
internal const string IdentifierContains = nameof(IdentifierContains);
private static string[][] GetPropArray(IReadOnlyList<Type> types, IReadOnlyList<string> extra)
{
var result = new string[types.Count + 2][];
var p = result.AsSpan(1, types.Count);
for (int i = 0; i < p.Length; i++)
{
var type = types[i];
var props = ReflectUtil.GetPropertiesPublic(type);
var combine = props.Concat(extra).ToArray();
Array.Sort(combine);
p[i] = combine;
}
// Properties for any PKM
// Properties shared by all PKM
var first = p[0];
var any = new HashSet<string>(first);
var all = new HashSet<string>(first);
foreach (var set in p[1..])
{
any.UnionWith(set);
all.IntersectWith(set);
}
var arrAny = any.ToArray();
Array.Sort(arrAny);
result[0] = arrAny;
var arrAll = all.ToArray();
Array.Sort(arrAll);
result[^1] = arrAll;
return result;
}
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="pk">Pokémon to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public static bool TryGetHasProperty(PKM pk, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var type = pk.GetType();
return TryGetHasProperty(type, name, out pi);
}
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="type">Type to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public static bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var index = Array.IndexOf(Types, type);
if (index < 0)
{
pi = null;
return false;
}
var props = Props[index];
return props.TryGetValue(name, out pi);
}
/// <summary>
/// Gets a list of <see cref="PKM"/> types that implement the requested <see cref="property"/>.
/// </summary>
public static IEnumerable<string> GetTypesImplementing(string property)
{
for (int i = 0; i < Types.Length; i++)
{
var type = Types[i];
var props = Props[i];
if (!props.TryGetValue(property, out var pi))
continue;
yield return $"{type.Name}: {pi.PropertyType.Name}";
}
}
/// <summary>
/// Gets the type of the <see cref="PKM"/> property using the saved cache of properties.
/// </summary>
/// <param name="propertyName">Property Name to fetch the type for</param>
/// <param name="typeIndex">Type index (within <see cref="Types"/>. Leave empty (0) for a nonspecific format.</param>
/// <returns>Short name of the property's type.</returns>
public static string? GetPropertyType(string propertyName, int typeIndex = 0)
{
if (CustomProperties.Contains(propertyName))
return "Custom";
if (typeIndex == 0) // Any
{
foreach (var p in Props)
{
if (p.TryGetValue(propertyName, out var pi))
return pi.PropertyType.Name;
}
return null;
}
int index = typeIndex - 1;
if ((uint)index >= Props.Length)
index = 0; // All vs Specific
var pr = Props[index];
if (!pr.TryGetValue(propertyName, out var info))
return null;
return info.PropertyType.Name;
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="il">Instructions to initialize.</param>
public static void ScreenStrings(IEnumerable<StringInstruction> il)
{
foreach (var i in il)
{
var pv = i.PropertyValue;
if (pv.All(char.IsDigit))
continue;
if (pv.StartsWith(CONST_SPECIAL) && !pv.StartsWith(CONST_BYTES, StringComparison.Ordinal))
{
var str = pv.AsSpan(1);
if (StringInstruction.IsRandomRange(str))
{
i.SetRandomRange(str);
continue;
}
}
SetInstructionScreenedValue(i);
}
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="i">Instruction to initialize.</param>
private static void SetInstructionScreenedValue(StringInstruction i)
{
switch (i.PropertyName)
{
case nameof(PKM.Species): i.SetScreenedValue(GameInfo.Strings.specieslist); return;
case nameof(PKM.HeldItem): i.SetScreenedValue(GameInfo.Strings.itemlist); return;
case nameof(PKM.Ability): i.SetScreenedValue(GameInfo.Strings.abilitylist); return;
case nameof(PKM.Nature): i.SetScreenedValue(GameInfo.Strings.natures); return;
case nameof(PKM.Ball): i.SetScreenedValue(GameInfo.Strings.balllist); return;
case nameof(PKM.Move1) or nameof(PKM.Move2) or nameof(PKM.Move3) or nameof(PKM.Move4):
case nameof(PKM.RelearnMove1) or nameof(PKM.RelearnMove2) or nameof(PKM.RelearnMove3) or nameof(PKM.RelearnMove4):
i.SetScreenedValue(GameInfo.Strings.movelist); return;
}
}
private static Dictionary<string, PropertyInfo> GetProps(PKM pk)
{
var type = pk.GetType();
var typeIndex = Array.IndexOf(Types, type);
return Props[typeIndex];
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, PKM pk)
{
var props = GetProps(pk);
foreach (var filter in filters)
{
if (!IsFilterMatch(filter, pk, props))
return false;
}
return true;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatchMeta(IEnumerable<StringInstruction> filters, SlotCache pk)
{
foreach (var i in filters)
{
foreach (var filter in BatchFilters.FilterMeta)
{
if (!filter.IsMatch(i.PropertyName))
continue;
if (!filter.IsFiltered(pk, i))
return false;
break;
}
}
return true;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="obj">Object to check.</param>
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object obj)
{
foreach (var cmd in filters)
{
if (cmd.PropertyName is PROP_TYPENAME)
{
var type = obj.GetType();
var typeName = type.Name;
if (!cmd.Comparer.IsCompareEquivalence(cmd.PropertyValue == typeName))
return false;
continue;
}
if (!ReflectUtil.HasProperty(obj, cmd.PropertyName, out var pi))
return false;
try
{
if (cmd.Comparer.IsCompareOperator(pi.CompareTo(obj, cmd.PropertyValue)))
continue;
}
// User provided inputs can mismatch the type's required value format, and fail to be compared.
catch (Exception e)
{
Debug.WriteLine($"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}.");
Debug.WriteLine(e.Message);
}
return false;
}
return true;
}
/// <summary>
/// Tries to modify the <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
public static bool TryModify(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
var result = TryModifyPKM(pk, filters, modifications);
return result == ModifyResult.Modified;
}
/// <summary>
/// Tries to modify the <see cref="BatchInfo"/>.
/// </summary>
/// <param name="pk">Command Filter</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
if (!pk.ChecksumValid || pk.Species == 0)
return ModifyResult.Invalid;
var info = new BatchInfo(pk);
var props = GetProps(pk);
foreach (var cmd in filters)
{
try
{
if (!IsFilterMatch(cmd, info, props))
return ModifyResult.Filtered;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine(MsgBEModifyFailCompare + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
return ModifyResult.Error;
}
}
ModifyResult result = ModifyResult.Modified;
foreach (var cmd in modifications)
{
try
{
var tmp = SetPKMProperty(cmd, info, props);
if (tmp != ModifyResult.Modified)
result = tmp;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine(MsgBEModifyFail + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
}
}
return result;
}
/// <summary>
/// Sets the if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="info">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filtered, else false.</returns>
private static ModifyResult SetPKMProperty(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
{
var pk = info.Entity;
if (cmd.PropertyValue.StartsWith(CONST_BYTES, StringComparison.Ordinal))
return SetByteArrayProperty(pk, cmd);
if (cmd.PropertyValue.StartsWith(CONST_SUGGEST, StringComparison.OrdinalIgnoreCase))
return SetSuggestedPKMProperty(cmd.PropertyName, info, cmd.PropertyValue);
if (cmd is { PropertyValue: CONST_RAND, PropertyName: nameof(PKM.Moves) })
return SetSuggestedMoveset(info, true);
if (SetComplexProperty(pk, cmd))
return ModifyResult.Modified;
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return ModifyResult.Error;
if (!pi.CanWrite)
return ModifyResult.Error;
object val;
if (cmd.Random)
val = cmd.RandomValue;
else if (cmd.PropertyValue.StartsWith(CONST_POINTER) && props.TryGetValue(cmd.PropertyValue[1..], out var opi))
val = opi.GetValue(pk) ?? throw new NullReferenceException();
else
val = cmd.PropertyValue;
ReflectUtil.SetValue(pi, pk, val);
return ModifyResult.Modified;
}
/// <summary>
/// Checks if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="info">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
{
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match != null)
return match.IsFiltered(info, cmd);
return IsPropertyFiltered(cmd, info.Entity, props);
}
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="pk">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
{
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match != null)
return match.IsFiltered(pk, cmd);
return IsPropertyFiltered(cmd, pk, props);
}
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="pk">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache</param>
/// <returns>True if filtered, else false.</returns>
private static bool IsPropertyFiltered(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
{
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return false;
if (!pi.CanRead)
return false;
string val = cmd.PropertyValue;
if (val.StartsWith(CONST_POINTER) && props.TryGetValue(val[1..], out var opi))
{
var result = opi.GetValue(pk) ?? throw new NullReferenceException();
return cmd.Comparer.IsCompareOperator(pi.CompareTo(pk, result));
}
return cmd.Comparer.IsCompareOperator(pi.CompareTo(pk, val));
}
/// <summary>
/// Sets the <see cref="PKM"/> data with a suggested value based on its <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="name">Property to modify.</param>
/// <param name="info">Cached info storing Legal data.</param>
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
private static ModifyResult SetSuggestedPKMProperty(string name, BatchInfo info, string propValue)
{
var first = BatchMods.SuggestionMods.Find(z => z.IsMatch(name, propValue, info));
if (first != null)
return first.Modify(name, propValue, info);
return ModifyResult.Error;
}
/// <summary>
/// Sets the <see cref="PKM"/> byte array property to a specified value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static ModifyResult SetByteArrayProperty(PKM pk, StringInstruction cmd)
{
switch (cmd.PropertyName)
{
case nameof(PKM.Nickname_Trash): StringUtil.LoadHexBytesTo(cmd.PropertyValue.AsSpan(CONST_BYTES.Length), pk.Nickname_Trash, 3); return ModifyResult.Modified;
case nameof(PKM.OT_Trash): StringUtil.LoadHexBytesTo(cmd.PropertyValue.AsSpan(CONST_BYTES.Length), pk.OT_Trash, 3); return ModifyResult.Modified;
case nameof(PKM.HT_Trash): StringUtil.LoadHexBytesTo(cmd.PropertyValue.AsSpan(CONST_BYTES.Length), pk.HT_Trash, 3); return ModifyResult.Modified;
default:
return ModifyResult.Error;
}
}
/// <summary>
/// Sets the <see cref="PKM"/> property to a non-specific smart value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
/// <returns>True if modified, false if no modifications done.</returns>
private static bool SetComplexProperty(PKM pk, StringInstruction cmd)
{
if (cmd.PropertyName.StartsWith("IV", StringComparison.Ordinal) && cmd.PropertyValue == CONST_RAND)
{
SetRandomIVs(pk, cmd);
return true;
}
var match = BatchMods.ComplexMods.Find(z => z.IsMatch(cmd.PropertyName, cmd.PropertyValue));
if (match == null)
return false;
match.Modify(pk, cmd);
return true;
}
/// <summary>
/// Sets the <see cref="PKM"/> IV(s) to a random value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static void SetRandomIVs(PKM pk, StringInstruction cmd)
{
if (cmd.PropertyName == nameof(PKM.IVs))
{
var la = new LegalityAnalysis(pk);
var flawless = la.EncounterMatch is IFlawlessIVCount fc ? fc.FlawlessIVCount : 0;
pk.SetRandomIVs(flawless);
return;
}
if (TryGetHasProperty(pk, cmd.PropertyName, out var pi))
ReflectUtil.SetValue(pi, pk, Util.Rand.Next(pk.MaxIV + 1));
}
}

View File

@ -1,18 +0,0 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Information wrapper used for Batch Editing to apply suggested values.
/// </summary>
public sealed class BatchInfo
{
internal PKM Entity { get; }
internal BatchInfo(PKM pk) => Entity = pk;
private LegalityAnalysis? la;
internal LegalityAnalysis Legality => la ??= new LegalityAnalysis(Entity);
public bool Legal => Legality.Valid;
internal void SuggestedRelearn(Span<ushort> moves) => Legality.GetSuggestedRelearnMoves(moves, Legality.EncounterOriginal);
}

View File

@ -0,0 +1,23 @@
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <summary>
/// Default property provider that uses an <see cref="IBatchEditor{TObject}"/> for reflection.
/// </summary>
public class BatchPropertyProvider<TEditor, TObject>(TEditor editor) : IPropertyProvider<TObject> where TObject : notnull where TEditor : IBatchEditor<TObject>
{
/// <summary>
/// Initializes a new instance of the <see cref="BatchPropertyProvider{TEditor, TObject}"/> class with the specified editor.
/// </summary>
public bool TryGetProperty(TObject obj, string prop, [NotNullWhen(true)] out string? result)
{
result = null;
if (!editor.TryGetHasProperty(obj, prop, out var pi))
return false;
var value = pi.GetValue(obj);
result = value?.ToString();
return result is not null;
}
}

View File

@ -1,25 +0,0 @@
using System;
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexFilter"/>
public sealed class ComplexFilter : IComplexFilter
{
private readonly string Property;
private readonly Func<PKM, StringInstruction, bool> FilterPKM;
private readonly Func<BatchInfo, StringInstruction, bool> FilterBulk;
public ComplexFilter(
string property,
Func<PKM, StringInstruction, bool> filterPkm,
Func<BatchInfo, StringInstruction, bool> filterBulk)
{
Property = property;
FilterPKM = filterPkm;
FilterBulk = filterBulk;
}
public bool IsMatch(string prop) => prop == Property;
public bool IsFiltered(PKM pk, StringInstruction value) => FilterPKM(pk, value);
public bool IsFiltered(BatchInfo info, StringInstruction value) => FilterBulk(info, value);
}

View File

@ -1,23 +0,0 @@
using System;
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexSet"/>
public sealed class ComplexSet : IComplexSet
{
public readonly string PropertyName;
public readonly Func<string, bool> IsValueCompatible = _ => true;
private readonly Action<PKM, StringInstruction> Action;
public ComplexSet(string propertyName, Action<PKM, StringInstruction> modify)
{
PropertyName = propertyName;
Action = modify;
}
public ComplexSet(string propertyName, Func<string, bool> criteria, Action<PKM, StringInstruction> modify) : this(propertyName, modify) => IsValueCompatible = criteria;
public bool IsMatch(string name, string value) => name == PropertyName && IsValueCompatible(value);
public void Modify(PKM pk, StringInstruction instr) => Action(pk, instr);
}

View File

@ -1,21 +0,0 @@
using System;
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexFilter"/>
public sealed class MetaFilter : IComplexFilterMeta
{
private readonly string Property;
private readonly Func<object, StringInstruction, bool> FilterPKM;
public MetaFilter(
string property,
Func<object, StringInstruction, bool> filterPkm)
{
Property = property;
FilterPKM = filterPkm;
}
public bool IsMatch(string prop) => prop == Property;
public bool IsFiltered(object pk, StringInstruction value) => FilterPKM(pk, value);
}

View File

@ -1,5 +1,5 @@
using System.Collections.Generic;
using static PKHeX.Core.BatchEditing;
using static PKHeX.Core.EntityBatchEditor;
namespace PKHeX.Core;
@ -9,15 +9,15 @@ namespace PKHeX.Core;
public static class BatchFilters
{
/// <summary>
/// Filters to use for <see cref="BatchEditing"/> that are derived from the <see cref="PKM"/> data.
/// Filters to use for <see cref="EntityBatchEditor"/> that are derived from the <see cref="PKM"/> data.
/// </summary>
public static readonly List<IComplexFilter> FilterMods = new()
{
public static readonly List<IComplexFilter> FilterMods =
[
new ComplexFilter(PROP_LEGAL,
(pk, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(b == new LegalityAnalysis(pk).Valid),
(info, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(b == info.Legality.Valid)),
new ComplexFilter(PROP_TYPENAME,
new ComplexFilter(BatchEditingUtil.PROP_TYPENAME,
(pk, cmd) => cmd.Comparer.IsCompareEquivalence(pk.GetType().Name == cmd.PropertyValue),
(info, cmd) => cmd.Comparer.IsCompareEquivalence(info.Entity.GetType().Name == cmd.PropertyValue)),
@ -32,13 +32,13 @@ public static class BatchFilters
new ComplexFilter(PROP_TYPEEITHER,
(pk, cmd) => byte.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(pk.PersonalInfo.IsType(b)),
(info, cmd) => byte.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(info.Entity.PersonalInfo.IsType(b))),
};
];
/// <summary>
/// Filters to use for <see cref="BatchEditing"/> that are derived from the <see cref="PKM"/> source.
/// Filters to use for <see cref="EntityBatchEditor"/> that are derived from the <see cref="PKM"/> source.
/// </summary>
public static readonly List<IComplexFilterMeta> FilterMeta = new()
{
public static readonly List<IComplexFilterMeta> FilterMeta =
[
new MetaFilter(IdentifierContains,
(obj, cmd) => obj is SlotCache s && cmd.Comparer.IsCompareEquivalence(s.Identify().Contains(cmd.PropertyValue))),
@ -47,5 +47,5 @@ public static class BatchFilters
new MetaFilter(nameof(ISlotInfo.Slot),
(obj, cmd) => obj is SlotCache s && int.TryParse(cmd.PropertyValue, out var slot) && cmd.Comparer.IsCompareOperator((s.Source.Slot + 1).CompareTo(slot))),
};
];
}

View File

@ -0,0 +1,19 @@
namespace PKHeX.Core;
/// <summary>
/// Information wrapper used for Batch Editing to apply suggested values.
/// </summary>
/// <param name="Entity"> Entity to be modified. </param>
public sealed record BatchInfo(PKM Entity)
{
/// <summary>
/// Legality analysis of the entity.
/// </summary>
/// <remarks>
/// Eagerly evaluate on ctor, so that the initial state is remembered before any modifications may disturb matching.
/// </remarks>
public readonly LegalityAnalysis Legality = new(Entity);
/// <inheritdoc cref="LegalityAnalysis.Valid"/>
public bool Legal => Legality.Valid;
}

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using static PKHeX.Core.BatchEditing;
using static PKHeX.Core.EntityBatchEditor;
namespace PKHeX.Core;
@ -10,8 +10,8 @@ namespace PKHeX.Core;
/// </summary>
public static class BatchMods
{
public static readonly List<ISuggestModification> SuggestionMods = new()
{
public static readonly List<ISuggestModification> SuggestionMods =
[
// Interface Specific
new TypeSuggestion<ICombatPower>(nameof(ICombatPower.Stat_CP), p => p.ResetCP()),
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.HeightAbsolute), p => p.ResetHeight()),
@ -36,55 +36,56 @@ public static class BatchMods
new TypeSuggestion<PKM>(nameof(PKM.HealPP), p => p.HealPP()),
new TypeSuggestion<PKM>(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()),
new TypeSuggestion<PKM>(nameof(PKM.Move1_PP), p => p.SetSuggestedMovePP(0)),
new TypeSuggestion<PKM>(nameof(PKM.Move2_PP), p => p.SetSuggestedMovePP(1)),
new TypeSuggestion<PKM>(nameof(PKM.Move3_PP), p => p.SetSuggestedMovePP(2)),
new TypeSuggestion<PKM>(nameof(PKM.Move4_PP), p => p.SetSuggestedMovePP(3)),
new TypeSuggestion<PKM>(nameof(PKM.Move1_PP), p => p.HealPPIndex(0)),
new TypeSuggestion<PKM>(nameof(PKM.Move2_PP), p => p.HealPPIndex(1)),
new TypeSuggestion<PKM>(nameof(PKM.Move3_PP), p => p.HealPPIndex(2)),
new TypeSuggestion<PKM>(nameof(PKM.Move4_PP), p => p.HealPPIndex(3)),
new ComplexSuggestion(nameof(PKM.CurrentFriendship), (_, _, info) => BatchModifications.SetSuggestedCurrentFriendship(info)),
new ComplexSuggestion(nameof(PKM.OriginalTrainerFriendship), (_, _, info) => BatchModifications.SetSuggestedOriginalTrainerFriendship(info)),
new ComplexSuggestion(nameof(PKM.HandlingTrainerFriendship), (_, _, info) => BatchModifications.SetSuggestedHandlingTrainerFriendship(info)),
new ComplexSuggestion(nameof(PKM.Moves), (_, _, info) => BatchModifications.SetSuggestedMoveset(info)),
new ComplexSuggestion(PROP_EVS, (_, _, info) => BatchModifications.SetEVs(info.Entity)),
new ComplexSuggestion(nameof(PKM.RelearnMoves), (_, value, info) => BatchModifications.SetSuggestedRelearnData(info, value)),
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => BatchModifications.SetSuggestedRibbons(info, value)),
new ComplexSuggestion(nameof(PKM.Met_Location), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
new ComplexSuggestion(nameof(PKM.MetLocation), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
new ComplexSuggestion(nameof(PKM.CurrentLevel), (_, _, info) => BatchModifications.SetMinimumCurrentLevel(info)),
new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStats, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality, value)),
new ComplexSuggestion(PROP_MOVEMASTERY, (_, value, info) => BatchModifications.SetSuggestedMasteryData(info, value)),
};
new ComplexSuggestion(PROP_MOVEPLUS, (_, value, info) => BatchModifications.SetSuggestedMovePlusData(info, value)),
];
private static DateOnly ParseDate(ReadOnlySpan<char> val) => DateOnly.ParseExact(val, "yyyyMMdd", CultureInfo.InvariantCulture);
public static readonly List<IComplexSet> ComplexMods = new()
{
// Date
public static readonly List<IComplexSet> ComplexMods =
[
new ComplexSet(nameof(PKM.MetDate), (pk, cmd) => pk.MetDate = ParseDate(cmd.PropertyValue)),
new ComplexSet(nameof(PKM.EggMetDate), (pk, cmd) => pk.EggMetDate = ParseDate(cmd.PropertyValue)),
// Value Swap
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == nameof(PKM.PID), (pk, _) => pk.EncryptionConstant = pk.PID),
new ComplexSet(nameof(PKM.PID), value => value == nameof(PKM.EncryptionConstant), (pk, _) => pk.PID = pk.EncryptionConstant),
// Realign to Derived Value
new ComplexSet(nameof(PKM.Ability), value => value.Length == 2 && value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
new ComplexSet(nameof(PKM.AbilityNumber), value => value.Length == 2 && value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
new ComplexSet(nameof(PKM.Ability), value => value.Length == 2 && value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(cmd.PropertyValue[1] - 0x30)),
new ComplexSet(nameof(PKM.AbilityNumber), value => value.Length == 2 && value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(cmd.PropertyValue[1] - 0x30)),
// Random
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == CONST_RAND, (pk, _) => pk.EncryptionConstant = Util.Rand32()),
new ComplexSet(nameof(PKM.PID), value => value == CONST_RAND, (pk, _) => pk.PID = Util.Rand32()),
new ComplexSet(nameof(PKM.Gender), value => value == CONST_RAND, (pk, _) => pk.SetPIDGender(pk.Gender)),
new ComplexSet(PROP_EVS, value => value == CONST_RAND, (pk, _) => SetRandomEVs(pk)),
new ComplexSet(nameof(ITeraType.TeraTypeOverride), value => value == CONST_RAND, (pk, _) => SetRandomTeraType(pk)),
new ComplexSet(nameof(PKM.PID), value => value is CONST_RAND, (pk, _) => pk.PID = Util.Rand32()),
new ComplexSet(nameof(PKM.Gender), value => value is CONST_RAND, (pk, _) => pk.SetPIDGender(pk.Gender)),
new ComplexSet(PROP_EVS, value => value is CONST_RAND, (pk, _) => SetRandomEVs(pk)),
new ComplexSet(nameof(ITeraType.TeraTypeOverride), value => value is CONST_RAND, (pk, _) => SetRandomTeraType(pk)),
// Shiny
new ComplexSet(nameof(PKM.PID),
value => value.StartsWith(CONST_SHINY, true, CultureInfo.CurrentCulture),
(pk, cmd) => CommonEdits.SetShiny(pk, GetRequestedShinyState(cmd.PropertyValue))),
value => value.StartsWith(CONST_SHINY),
(pk, cmd) => pk.SetShiny(GetRequestedShinyState(cmd.PropertyValue))),
new ComplexSet(nameof(PKM.Species), value => value == "0", (pk, _) => Array.Clear(pk.Data, 0, pk.Data.Length)),
new ComplexSet(nameof(PKM.IsNicknamed), value => string.Equals(value, "false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
new ComplexSet(nameof(PKM.Species), value => value is "0", (pk, _) => pk.Data.Clear()),
new ComplexSet(nameof(PKM.IsNicknamed), value => value.Equals("false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
// Complicated
new ComplexSet(nameof(PKM.EncryptionConstant), value => value.StartsWith(CONST_RAND), (pk, cmd) => pk.EncryptionConstant = CommonEdits.GetComplicatedEC(pk, option: cmd.PropertyValue[^1])),
};
new ComplexSet(nameof(PKM.EncryptionConstant), value => value.StartsWith(CONST_RAND), (pk, cmd) => pk.EncryptionConstant = CommonEdits.GetComplicatedEC(pk, option: GetOptionSuffix(cmd.PropertyValue, CONST_RAND))),
];
private static char GetOptionSuffix(ReadOnlySpan<char> str, ReadOnlySpan<char> prefix)
=> str.Length == prefix.Length ? CommonEdits.OptionNone : str[^1];
private static void SetRandomTeraType(PKM pk)
{

View File

@ -0,0 +1,16 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexFilter"/>
public sealed class ComplexFilter(
[ConstantExpected] string Property,
Func<PKM, StringInstruction, bool> FilterPKM,
Func<BatchInfo, StringInstruction, bool> FilterBulk)
: IComplexFilter
{
public bool IsMatch(ReadOnlySpan<char> prop) => prop.SequenceEqual(Property);
public bool IsFiltered(PKM pk, StringInstruction value) => FilterPKM(pk, value);
public bool IsFiltered(BatchInfo info, StringInstruction value) => FilterBulk(info, value);
}

View File

@ -0,0 +1,19 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexSet"/>
public sealed class ComplexSet([ConstantExpected] string PropertyName, Action<PKM, StringInstruction> Action) : IComplexSet
{
public readonly string PropertyName = PropertyName;
public readonly Func<ReadOnlySpan<char>, bool> IsValueCompatible = _ => true;
public ComplexSet([ConstantExpected] string PropertyName, Func<ReadOnlySpan<char>, bool> criteria, Action<PKM, StringInstruction> Action)
: this(PropertyName, Action) => IsValueCompatible = criteria;
public bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value)
=> name.SequenceEqual(PropertyName) && IsValueCompatible(value);
public void Modify(PKM pk, StringInstruction instr) => Action(pk, instr);
}

View File

@ -1,11 +1,13 @@
namespace PKHeX.Core;
using System;
namespace PKHeX.Core;
/// <summary>
/// Complex filter of data based on a string value.
/// </summary>
public interface IComplexFilter
{
bool IsMatch(string prop);
bool IsMatch(ReadOnlySpan<char> prop);
bool IsFiltered(PKM pk, StringInstruction value);
bool IsFiltered(BatchInfo info, StringInstruction value);
}

View File

@ -1,10 +1,12 @@
namespace PKHeX.Core;
using System;
namespace PKHeX.Core;
/// <summary>
/// Complex filter of data based on a string value.
/// </summary>
public interface IComplexFilterMeta
{
bool IsMatch(string prop);
bool IsMatch(ReadOnlySpan<char> prop);
bool IsFiltered(object cache, StringInstruction value);
}

View File

@ -1,10 +1,12 @@
namespace PKHeX.Core;
using System;
namespace PKHeX.Core;
/// <summary>
/// Complex modification of data to a string value.
/// </summary>
public interface IComplexSet
{
bool IsMatch(string name, string value);
bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value);
void Modify(PKM pk, StringInstruction instr);
}

View File

@ -0,0 +1,14 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexFilter"/>
public sealed class MetaFilter(
[ConstantExpected] string Property,
Func<object, StringInstruction, bool> FilterPKM)
: IComplexFilterMeta
{
public bool IsMatch(ReadOnlySpan<char> prop) => prop.SequenceEqual(Property);
public bool IsFiltered(object pk, StringInstruction value) => FilterPKM(pk, value);
}

View File

@ -0,0 +1,304 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.BatchModifications;
namespace PKHeX.Core;
/// <summary>
/// Logic for editing many <see cref="PKM"/> with user provided <see cref="StringInstruction"/> list.
/// </summary>
public sealed class EntityBatchEditor() : BatchEditingBase<PKM, BatchInfo>(EntityTypes, EntityCustomProperties, expectedMax: 0x200)
{
private static readonly Type[] EntityTypes =
[
typeof (PK9), typeof (PA9),
typeof (PK8), typeof (PA8), typeof (PB8),
typeof (PB7),
typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4), typeof(RK4),
typeof (PK3), typeof (XK3), typeof (CK3),
typeof (PK2), typeof (SK2), typeof (PK1),
];
/// <summary>
/// Extra properties to show in the list of selectable properties (GUI) with special handling.
/// </summary>
/// <remarks>
/// These are not necessarily properties of the <see cref="PKM"/> themselves,
/// but can be any context-sensitive value related to the <see cref="PKM"/> or its legality,
/// such as "Legal" or "HasType". The handling of these properties must be implemented in the <see cref="TryHandleSetOperation"/> and <see cref="TryHandleFilter"/> methods.
/// </remarks>
private static readonly string[] EntityCustomProperties =
[
// General
BatchEditingUtil.PROP_TYPENAME,
// Entity/PersonalInfo
PROP_LEGAL, PROP_RIBBONS, PROP_EVS, PROP_CONTESTSTATS, PROP_MOVEMASTERY, PROP_MOVEPLUS,
PROP_TYPE1, PROP_TYPE2, PROP_TYPEEITHER,
// SlotCache
IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box),
];
public static EntityBatchEditor Instance { get; } = new();
// Custom Identifiers for special handling.
private const string CONST_BYTES = "$[]"; // Define a byte array with separated hex byte values, e.g. "$[]FF,02,03" or "$[]A0 02 0A FF"
// Custom Values to apply.
internal const string CONST_RAND = "$rand";
internal const string CONST_SHINY = "$shiny";
internal const string CONST_SUGGEST = "$suggest";
internal const char CONST_SPECIAL = '$';
// Custom Properties to change.
internal const string PROP_LEGAL = "Legal";
internal const string PROP_TYPEEITHER = "HasType";
internal const string PROP_TYPE1 = "PersonalType1";
internal const string PROP_TYPE2 = "PersonalType2";
internal const string PROP_RIBBONS = "Ribbons";
internal const string PROP_EVS = "EVs";
internal const string PROP_CONTESTSTATS = "ContestStats";
internal const string PROP_MOVEMASTERY = "MoveMastery";
internal const string PROP_MOVEPLUS = "PlusMoves";
internal const string IdentifierContains = nameof(IdentifierContains);
/// <summary>
/// Initializes the <see cref="StringInstruction"/> list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="il">Instructions to initialize.</param>
public static void ScreenStrings(IEnumerable<StringInstruction> il)
{
foreach (var i in il)
{
var pv = i.PropertyValue;
if (pv.All(char.IsDigit))
continue;
if (pv.StartsWith(CONST_SPECIAL) && !pv.StartsWith(CONST_BYTES, StringComparison.Ordinal))
{
var str = pv.AsSpan(1);
if (StringInstruction.IsRandomRange(str))
{
i.SetRandomRange(str);
continue;
}
}
SetInstructionScreenedValue(i);
}
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="i">Instruction to initialize.</param>
private static void SetInstructionScreenedValue(StringInstruction i)
{
ReadOnlySpan<string> set;
switch (i.PropertyName)
{
case nameof(PKM.Species): set = GameInfo.Strings.specieslist; break;
case nameof(PKM.HeldItem): set = GameInfo.Strings.itemlist; break;
case nameof(PKM.Ability): set = GameInfo.Strings.abilitylist; break;
case nameof(PKM.Nature): set = GameInfo.Strings.natures; break;
case nameof(PKM.Ball): set = GameInfo.Strings.balllist; break;
case nameof(PKM.Move1) or nameof(PKM.Move2) or nameof(PKM.Move3) or nameof(PKM.Move4):
case nameof(PKM.RelearnMove1) or nameof(PKM.RelearnMove2) or nameof(PKM.RelearnMove3) or nameof(PKM.RelearnMove4):
set = GameInfo.Strings.movelist; break;
default:
return;
}
i.SetScreenedValue(set);
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatchMeta(IEnumerable<StringInstruction> filters, SlotCache pk)
{
foreach (var i in filters)
{
foreach (var filter in BatchFilters.FilterMeta)
{
if (!filter.IsMatch(i.PropertyName))
continue;
if (!filter.IsFiltered(pk, i))
return false;
break;
}
}
return true;
}
protected override BatchInfo CreateMeta(PKM entity) => new(entity);
protected override bool ShouldModify(PKM entity) => entity.ChecksumValid && entity.Species != 0;
protected override bool TryHandleSetOperation(StringInstruction cmd, BatchInfo info, PKM entity, out ModifyResult result)
{
if (cmd.PropertyValue.StartsWith(CONST_BYTES, StringComparison.Ordinal))
{
result = SetByteArrayProperty(entity, cmd);
return true;
}
if (cmd.PropertyValue.StartsWith(CONST_SUGGEST, StringComparison.OrdinalIgnoreCase))
{
result = SetSuggestedProperty(cmd.PropertyName, info, cmd.PropertyValue);
return true;
}
if (cmd is { PropertyValue: CONST_RAND, PropertyName: nameof(PKM.Moves) })
{
result = SetSuggestedMoveset(info, true);
return true;
}
if (SetComplexProperty(info, cmd))
{
result = ModifyResult.Modified;
return true;
}
result = ModifyResult.Skipped;
return false;
}
protected override bool TryHandleFilter(StringInstruction cmd, BatchInfo info, PKM entity, out bool isMatch)
{
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match is null)
{
isMatch = false;
return false;
}
isMatch = match.IsFiltered(info, cmd);
return true;
}
/// <summary>
/// Sets the <see cref="PKM"/> data with a suggested value based on its <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="name">Property to modify.</param>
/// <param name="info">Cached info storing Legal data.</param>
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
private static ModifyResult SetSuggestedProperty(ReadOnlySpan<char> name, BatchInfo info, ReadOnlySpan<char> propValue)
{
foreach (var mod in BatchMods.SuggestionMods)
{
if (mod.IsMatch(name, propValue, info))
return mod.Modify(name, propValue, info);
}
return ModifyResult.Error;
}
/// <summary>
/// Sets the <see cref="PKM"/> byte array property to a specified value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static ModifyResult SetByteArrayProperty(PKM pk, StringInstruction cmd)
{
Span<byte> dest;
switch (cmd.PropertyName)
{
case nameof(PKM.NicknameTrash) or nameof(PKM.Nickname): dest = pk.NicknameTrash; break;
case nameof(PKM.OriginalTrainerTrash): dest = pk.OriginalTrainerTrash; break;
case nameof(PKM.HandlingTrainerTrash): dest = pk.HandlingTrainerTrash; break;
default:
return ModifyResult.Error;
}
var src = cmd.PropertyValue.AsSpan(CONST_BYTES.Length); // skip prefix
StringUtil.LoadHexBytesTo(src, dest, 3);
return ModifyResult.Modified;
}
/// <summary>
/// Sets the <see cref="PKM"/> property to a non-specific smart value.
/// </summary>
/// <param name="info">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
/// <returns>True if modified, false if no modifications done.</returns>
private bool SetComplexProperty(BatchInfo info, StringInstruction cmd)
{
ReadOnlySpan<char> name = cmd.PropertyName;
ReadOnlySpan<char> value = cmd.PropertyValue;
if (name.StartsWith("IV") && value is CONST_RAND)
{
SetRandomIVs(info, name);
return true;
}
foreach (var mod in BatchMods.ComplexMods)
{
if (!mod.IsMatch(name, value))
continue;
mod.Modify(info.Entity, cmd);
return true;
}
return false;
}
/// <summary>
/// Sets the <see cref="PKM"/> IV(s) to a random value.
/// </summary>
/// <param name="info">Pokémon to modify.</param>
/// <param name="propertyName">Property to modify</param>
private void SetRandomIVs(BatchInfo info, ReadOnlySpan<char> propertyName)
{
var pk = info.Entity;
if (propertyName is nameof(PKM.IVs))
{
var la = info.Legality;
var enc = la.EncounterMatch;
if (enc is IFlawlessIVCount { FlawlessIVCount: not 0 } fc)
pk.SetRandomIVs(fc.FlawlessIVCount);
else if (enc is IFixedIVSet { IVs: {IsSpecified: true} iv})
pk.SetRandomIVs(iv);
else if (enc is IFlawlessIVCountConditional c && c.GetFlawlessIVCount(pk) is { Max: not 0 } x)
pk.SetRandomIVs(Util.Rand.Next(x.Min, x.Max + 1));
else
pk.SetRandomIVs();
return;
}
if (TryGetHasProperty(pk, propertyName, out var pi))
{
const string IV32 = nameof(PK9.IV32);
if (propertyName is IV32)
{
var value = (uint)Util.Rand.Next(0x3FFFFFFF + 1);
if (pk is BK4 bk) // Big Endian, reverse IV ordering
{
value <<= 2; // flags are the lowest bits, and our random value is still fine.
value |= bk.IV32 & 3; // preserve the flags
bk.IV32 = value;
return;
}
var exist = ReflectUtil.GetValue(pk, IV32);
value |= exist switch
{
uint iv => iv & (3u << 30), // preserve the flags
_ => 0,
};
ReflectUtil.SetValue(pi, pk, value);
}
else
{
var value = Util.Rand.Next(pk.MaxIV + 1);
ReflectUtil.SetValue(pi, pk, value);
}
}
}
}

View File

@ -8,20 +8,23 @@ namespace PKHeX.Core;
/// <summary>
/// Carries out a batch edit and contains information summarizing the results.
/// </summary>
public sealed class BatchEditor
public sealed class EntityBatchProcessor
{
private int Modified { get; set; }
private int Iterated { get; set; }
private int Failed { get; set; }
private static EntityBatchEditor Editor => EntityBatchEditor.Instance;
/// <summary>
/// Tries to modify the <see cref="PKM"/>.
/// Tries to modify the <see cref="PKM"/> using instructions and a custom modifier delegate.
/// </summary>
/// <param name="pk">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <param name="modifier">Custom modifier delegate.</param>
/// <returns>Result of the attempted modification.</returns>
public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<PKM, bool>? modifier = null)
{
if (pk.Species == 0)
return false;
@ -33,11 +36,14 @@ public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<
return false;
}
var result = BatchEditing.TryModifyPKM(pk, filters, modifications);
if (result != ModifyResult.Invalid)
var result = Editor.TryModify(pk, filters, modifications, modifier);
if (result != ModifyResult.Skipped)
Iterated++;
if (result == ModifyResult.Error)
if (result.HasFlag(ModifyResult.Error))
{
Failed++;
result &= ~ModifyResult.Error;
}
if (result != ModifyResult.Modified)
return false;
@ -69,15 +75,16 @@ public string GetEditorResults(IReadOnlyCollection<StringInstructionSet> sets)
/// </summary>
/// <param name="lines">Batch instruction line(s)</param>
/// <param name="data">Entities to modify</param>
/// <returns>Editor object if follow up modifications are desired.</returns>
public static BatchEditor Execute(ReadOnlySpan<string> lines, IEnumerable<PKM> data)
/// <param name="modifier">Custom modifier delegate.</param>
/// <returns>Editor object if follow-up modifications are desired.</returns>
public static EntityBatchProcessor Execute(ReadOnlySpan<string> lines, IEnumerable<PKM> data, Func<PKM, bool>? modifier = null)
{
var editor = new BatchEditor();
var editor = new EntityBatchProcessor();
var sets = StringInstructionSet.GetBatchSets(lines);
foreach (var pk in data)
{
foreach (var set in sets)
editor.Process(pk, set.Filters, set.Instructions);
editor.Process(pk, set.Filters, set.Instructions, modifier);
}
return editor;

View File

@ -28,17 +28,12 @@ public static ModifyResult SetSuggestedRelearnData(BatchInfo info, ReadOnlySpan<
var pk = info.Entity;
if (pk is ITechRecord t)
{
t.ClearRecordFlags();
if (IsAll(propValue))
{
t.SetRecordFlagsAll(info.Legality.Info.EvoChainsAllGens.Get(pk.Context)); // all
}
else if (!IsNone(propValue))
{
Span<ushort> moves = stackalloc ushort[4];
pk.GetMoves(moves);
t.SetRecordFlags(moves, info.Legality.Info.EvoChainsAllGens.Get(pk.Context)); // whatever fit the current moves
}
if (IsNone(propValue))
t.SetRecordFlags(pk, TechnicalRecordApplicatorOption.None);
else if (IsAll(propValue))
t.SetRecordFlags(pk, TechnicalRecordApplicatorOption.LegalAll, info.Legality);
else
t.SetRecordFlags(pk, TechnicalRecordApplicatorOption.LegalCurrent, info.Legality);
}
pk.SetRelearnMoves(info.Legality);
@ -53,19 +48,46 @@ public static ModifyResult SetSuggestedMasteryData(BatchInfo info, ReadOnlySpan<
{
var pk = info.Entity;
if (pk is not IMoveShop8Mastery t)
return ModifyResult.Invalid;
return ModifyResult.Skipped;
t.ClearMoveShopFlags();
if (IsNone(propValue))
return ModifyResult.Modified;
var e = info.Legality.EncounterMatch;
if (e is IMasteryInitialMoveShop8 enc)
enc.SetInitialMastery(pk);
var enc = info.Legality.EncounterMatch;
if (enc is IMasteryInitialMoveShop8 shop)
shop.SetInitialMastery(pk, enc);
if (IsAll(propValue))
{
t.SetPurchasedFlagsAll(pk);
t.SetMoveShopFlagsAll(pk);
}
else
{
t.SetMoveShopFlags(pk);
}
return ModifyResult.Modified;
}
/// <summary>
/// Sets all legal Plus Move flag data for the Entity.
/// </summary>
/// <remarks>Only applicable for <see cref="IPlusRecord"/>.</remarks>
public static ModifyResult SetSuggestedMovePlusData(BatchInfo info, ReadOnlySpan<char> value)
{
var pk = info.Entity;
if (pk is not IPlusRecord t || pk.PersonalInfo is not IPermitPlus p)
return ModifyResult.Skipped;
PlusRecordApplicatorOption option;
if (IsNone(value))
option = PlusRecordApplicatorOption.None;
else if (IsAll(value))
option = PlusRecordApplicatorOption.LegalSeedTM;
else
option = PlusRecordApplicatorOption.LegalCurrent;
t.SetPlusFlags(p, option, info.Legality);
return ModifyResult.Modified;
}
@ -77,8 +99,10 @@ public static ModifyResult SetSuggestedRibbons(BatchInfo info, ReadOnlySpan<char
{
if (IsNone(value))
RibbonApplicator.RemoveAllValidRibbons(info.Legality);
else // All
else if (IsAll(value))
RibbonApplicator.SetAllValidRibbons(info.Legality);
else // Only for current context
RibbonApplicator.SetAllValidRibbons(info.Entity, info.Legality.EncounterMatch, info.Legality.Info.EvoChainsAllGens.AsSingle(info.Entity.Context));
return ModifyResult.Modified;
}
@ -89,16 +113,20 @@ public static ModifyResult SetSuggestedMetData(BatchInfo info)
{
var pk = info.Entity;
var encounter = EncounterSuggestion.GetSuggestedMetInfo(pk);
if (encounter == null)
if (encounter is null)
return ModifyResult.Error;
int level = encounter.LevelMin;
int location = encounter.Location;
int minimumLevel = EncounterSuggestion.GetLowestLevel(pk, encounter.LevelMin);
var location = encounter.Location;
var level = encounter.LevelMin;
var minimumLevel = EncounterSuggestion.GetLowestLevel(pk, level);
var current = Math.Max(minimumLevel, level);
pk.Met_Level = level;
pk.Met_Location = location;
pk.CurrentLevel = Math.Max(minimumLevel, level);
if (pk.MetLevel == level && pk.MetLocation == location && pk.CurrentLevel == current)
return ModifyResult.Skipped;
pk.MetLevel = level;
pk.MetLocation = location;
pk.CurrentLevel = current;
return ModifyResult.Modified;
}
@ -109,7 +137,7 @@ public static ModifyResult SetSuggestedMetData(BatchInfo info)
public static ModifyResult SetMinimumCurrentLevel(BatchInfo info)
{
var result = EncounterSuggestion.IterateMinimumCurrentLevel(info.Entity, info.Legal);
return result ? ModifyResult.Modified : ModifyResult.Filtered;
return result ? ModifyResult.Modified : ModifyResult.Skipped;
}
/// <summary>
@ -119,6 +147,10 @@ public static ModifyResult SetMinimumCurrentLevel(BatchInfo info)
/// <param name="moves">Moves to apply.</param>
public static ModifyResult SetMoves(PKM pk, ReadOnlySpan<ushort> moves)
{
Span<ushort> current = stackalloc ushort[4];
pk.GetMoves(current);
if (current.SequenceEqual(moves))
return ModifyResult.Skipped;
pk.SetMoves(moves);
return ModifyResult.Modified;
}
@ -127,6 +159,11 @@ public static ModifyResult SetEVs(PKM pk)
{
Span<int> evs = stackalloc int[6];
EffortValues.SetMax(evs, pk);
Span<int> current = stackalloc int[6];
pk.GetEVs(current);
if (current.SequenceEqual(evs))
return ModifyResult.Skipped;
pk.SetEVs(evs);
return ModifyResult.Modified;
}
@ -139,10 +176,43 @@ public static ModifyResult SetEVs(PKM pk)
/// <param name="option">Option to apply with</param>
public static ModifyResult SetContestStats(PKM pk, LegalityAnalysis la, ReadOnlySpan<char> option)
{
if (option.Length != 0 && option[BatchEditing.CONST_SUGGEST.Length..] is not "0")
if (option.Length != 0 && option[EntityBatchEditor.CONST_SUGGEST.Length..] is not "0")
pk.SetMaxContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
else
pk.SetSuggestedContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
return ModifyResult.Modified;
}
public static ModifyResult SetSuggestedCurrentFriendship(BatchInfo info)
{
var pk = info.Entity;
var value = HistoryVerifier.GetSuggestedFriendshipCurrent(pk, info.Legality.EncounterMatch);
if (pk.CurrentFriendship == value)
return ModifyResult.Skipped;
pk.CurrentFriendship = value;
return ModifyResult.Modified;
}
public static ModifyResult SetSuggestedOriginalTrainerFriendship(BatchInfo info)
{
var pk = info.Entity;
var value = HistoryVerifier.GetSuggestedFriendshipOT(pk, info.Legality.EncounterMatch);
if (pk.OriginalTrainerFriendship == value)
return ModifyResult.Skipped;
pk.OriginalTrainerFriendship = value;
return ModifyResult.Modified;
}
public static ModifyResult SetSuggestedHandlingTrainerFriendship(BatchInfo info)
{
var pk = info.Entity;
var value = HistoryVerifier.GetSuggestedFriendshipHT(pk);
if (pk.HandlingTrainerFriendship == value)
return ModifyResult.Skipped;
pk.HandlingTrainerFriendship = value;
return ModifyResult.Modified;
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <inheritdoc cref="ISuggestModification"/>
public sealed class ComplexSuggestion(
[ConstantExpected] string Keyword,
Func<ReadOnlySpan<char>, ReadOnlySpan<char>, BatchInfo, ModifyResult> Action)
: ISuggestModification
{
public readonly string Keyword = Keyword;
public readonly Func<PKM, bool> Criteria = _ => true;
public readonly Func<ReadOnlySpan<char>, ReadOnlySpan<char>, BatchInfo, ModifyResult> Action = Action;
public ComplexSuggestion(
[ConstantExpected] string Keyword,
Func<PKM, bool> criteria,
Func<ReadOnlySpan<char>, ReadOnlySpan<char>, BatchInfo, ModifyResult> action) : this(Keyword, action)
{
Criteria = criteria;
}
public bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info)
{
return name.SequenceEqual(Keyword) && Criteria(info.Entity);
}
public ModifyResult Modify(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info)
{
return Action(name, value, info);
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Modifies a property to have a "correct" value based on derived legality.
/// </summary>
public interface ISuggestModification
{
public bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info);
public ModifyResult Modify(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info);
}

View File

@ -0,0 +1,34 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <inheritdoc cref="ISuggestModification"/>
/// <typeparam name="T">Specific (or not) type</typeparam>
public sealed class TypeSuggestion<T>([ConstantExpected] string Keyword, Action<T> Action) : ISuggestModification
{
public readonly string Keyword = Keyword;
public readonly Action<T, ReadOnlySpan<char>> Action = (pk, _) => Action(pk);
public readonly Func<T, bool> Criteria = _ => true;
public TypeSuggestion([ConstantExpected] string Keyword, Func<T, bool> criteria, Action<T> action) : this(Keyword, action)
{
Criteria = criteria;
}
public bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info)
{
return name.SequenceEqual(Keyword) && info.Entity is T;
}
public ModifyResult Modify(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info)
{
var pk = info.Entity;
if (pk is not T x)
return ModifyResult.Skipped;
if (!Criteria(x))
return ModifyResult.Skipped;
Action(x, value);
return ModifyResult.Modified;
}
}

View File

@ -0,0 +1,18 @@
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <summary>
/// Interface for retrieving properties from a <see cref="T"/>.
/// </summary>
public interface IPropertyProvider<in T> where T : notnull
{
/// <summary>
/// Attempts to retrieve a property's value (as string) from an entity instance.
/// </summary>
/// <param name="obj">Entity to retrieve the property from.</param>
/// <param name="prop">Property name to retrieve.</param>
/// <param name="result">Property value as string.</param>
/// <returns><see langword="true"/> if the property was found and retrieved successfully; otherwise, <see langword="false"/>.</returns>
bool TryGetProperty(T obj, string prop, [NotNullWhen(true)] out string? result);
}

View File

@ -1,27 +0,0 @@
namespace PKHeX.Core;
/// <summary>
/// Batch Editor Modification result for an individual <see cref="PKM"/>.
/// </summary>
public enum ModifyResult
{
/// <summary>
/// The <see cref="PKM"/> has invalid data and is not a suitable candidate for modification.
/// </summary>
Invalid,
/// <summary>
/// An error was occurred while iterating modifications for this <see cref="PKM"/>.
/// </summary>
Error,
/// <summary>
/// The <see cref="PKM"/> was skipped due to a matching Filter.
/// </summary>
Filtered,
/// <summary>
/// The <see cref="PKM"/> was modified.
/// </summary>
Modified,
}

View File

@ -1,37 +0,0 @@
using System;
namespace PKHeX.Core;
/// <inheritdoc cref="ISuggestModification"/>
public sealed class ComplexSuggestion : ISuggestModification
{
public readonly string Keyword;
public readonly Func<PKM, bool> Criteria = _ => true;
public readonly Func<string, string, BatchInfo, ModifyResult> Action;
public ComplexSuggestion(
string keyword,
Func<PKM, bool> criteria,
Func<string, string, BatchInfo, ModifyResult> action) : this(keyword, action)
{
Criteria = criteria;
}
public ComplexSuggestion(
string keyword,
Func<string, string, BatchInfo, ModifyResult> action)
{
Keyword = keyword;
Action = action;
}
public bool IsMatch(string name, string value, BatchInfo info)
{
return name == Keyword && Criteria(info.Entity);
}
public ModifyResult Modify(string name, string value, BatchInfo info)
{
return Action(name, value, info);
}
}

View File

@ -1,10 +0,0 @@
namespace PKHeX.Core;
/// <summary>
/// Modifies a property to have a "correct" value based on derived legality.
/// </summary>
public interface ISuggestModification
{
public bool IsMatch(string name, string value, BatchInfo info);
public ModifyResult Modify(string name, string value, BatchInfo info);
}

View File

@ -1,39 +0,0 @@
using System;
namespace PKHeX.Core;
/// <inheritdoc cref="ISuggestModification"/>
/// <typeparam name="T">Specific (or not) type</typeparam>
public sealed class TypeSuggestion<T> : ISuggestModification
{
public readonly string Keyword;
public readonly Action<T, string> Action;
public readonly Func<T, bool> Criteria = _ => true;
public TypeSuggestion(string keyword, Action<T> action)
{
Keyword = keyword;
Action = (pk, _) => action(pk);
}
public TypeSuggestion(string keyword, Func<T, bool> criteria, Action<T> action) : this(keyword, action)
{
Criteria = criteria;
}
public bool IsMatch(string name, string value, BatchInfo info)
{
return name == Keyword && info.Entity is T;
}
public ModifyResult Modify(string name, string value, BatchInfo info)
{
var pk = info.Entity;
if (pk is not T x)
return ModifyResult.Invalid;
if (!Criteria(x))
return ModifyResult.Invalid;
Action(x, value);
return ModifyResult.Modified;
}
}

View File

@ -8,7 +8,7 @@ namespace PKHeX.Core;
public static class CommonEdits
{
/// <summary>
/// Setting which enables/disables automatic manipulation of <see cref="PKM.MarkValue"/> when importing from a <see cref="IBattleTemplate"/>.
/// Setting which enables/disables automatic manipulation of <see cref="IAppliedMarkings"/> when importing from a <see cref="IBattleTemplate"/>.
/// </summary>
public static bool ShowdownSetIVMarkings { get; set; } = true;
@ -17,473 +17,467 @@ public static class CommonEdits
/// </summary>
public static bool ShowdownSetBehaviorNature { get; set; }
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to the provided value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="nick"><see cref="PKM.Nickname"/> to set. If no nickname is provided, the <see cref="PKM.Nickname"/> is set to the default value for its current language and format.</param>
public static void SetNickname(this PKM pk, string nick)
extension(PKM pk)
{
if (nick.Length == 0)
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to the provided value.
/// </summary>
/// <param name="nick"><see cref="PKM.Nickname"/> to set. If no nickname is provided, the <see cref="PKM.Nickname"/> is set to the default value for its current language and format.</param>
public void SetNickname(string nick)
{
pk.ClearNickname();
return;
if (nick.Length == 0)
{
pk.ClearNickname();
return;
}
pk.PrepareNickname();
pk.Nickname = nick;
pk.IsNicknamed = true;
}
pk.IsNicknamed = true;
pk.Nickname = nick;
}
/// <summary>
/// Clears the <see cref="PKM.Nickname"/> to the default value.
/// </summary>
/// <param name="pk"></param>
public static string ClearNickname(this PKM pk)
{
pk.IsNicknamed = false;
string nick = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format);
pk.Nickname = nick;
if (pk is GBPKM pk12)
pk12.SetNotNicknamed();
return nick;
}
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value by sanity checking the provided <see cref="PKM.Ability"/> against the possible pool of abilities.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="abil">Desired <see cref="Ability"/> value to set.</param>
public static void SetAbility(this PKM pk, int abil)
{
if (abil < 0)
return;
var index = pk.PersonalInfo.GetIndexOfAbility(abil);
index = Math.Max(0, index);
pk.SetAbilityIndex(index);
}
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value based on the provided ability index (0-2)
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Desired <see cref="PKM.AbilityNumber"/> (shifted by 1) to set.</param>
public static void SetAbilityIndex(this PKM pk, int index)
{
if (pk is PK5 pk5 && index == 2)
pk5.HiddenAbility = true;
else if (pk.Format <= 5)
pk.PID = EntityPID.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001));
pk.RefreshAbility(index);
}
/// <summary>
/// Sets a Random <see cref="PKM.EncryptionConstant"/> value. The <see cref="PKM.EncryptionConstant"/> is not updated if the value should match the <see cref="PKM.PID"/> instead.
/// </summary>
/// <remarks>Accounts for Wurmple evolutions.</remarks>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRandomEC(this PKM pk)
{
int gen = pk.Generation;
if (gen is 3 or 4 or 5)
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to the default value of the current species and language.
/// </summary>
/// <returns>Default nickname for the current species and language.</returns>
public string ClearNickname()
{
pk.EncryptionConstant = pk.PID;
return;
pk.IsNicknamed = false;
string nick = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format);
pk.SetString(pk.NicknameTrash, nick, nick.Length, StringConverterOption.None);
if (pk is GBPKM pk12)
pk12.SetNotNicknamed();
return nick;
}
pk.EncryptionConstant = GetComplicatedEC(pk);
}
/// <summary>
/// Sets the <see cref="PKM.IsShiny"/> derived value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="shiny">Desired <see cref="PKM.IsShiny"/> state to set.</param>
public static bool SetIsShiny(this PKM pk, bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny();
/// <summary>
/// Makes a <see cref="PKM"/> shiny.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="type">Shiny type to force. Only use Always* or Random</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public static bool SetShiny(PKM pk, Shiny type = Shiny.Random)
{
if (pk.IsShiny && type.IsValid(pk))
return false;
if (type == Shiny.Random || pk.FatefulEncounter || pk.Version == (int)GameVersion.GO || pk.Format <= 2)
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value by sanity checking the provided <see cref="PKM.Ability"/> against the possible pool of abilities.
/// </summary>
/// <param name="abilityID">Desired <see cref="Ability"/> value to set.</param>
public void SetAbility(int abilityID)
{
pk.SetShiny();
if (abilityID < 0)
return;
var index = pk.PersonalInfo.GetIndexOfAbility(abilityID);
if (index < 0)
return; // leave original value
pk.SetAbilityIndex(index);
}
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value based on the provided ability index (0-2)
/// </summary>
/// <param name="index">Desired <see cref="PKM.AbilityNumber"/> (shifted by 1) to set.</param>
public void SetAbilityIndex(int index)
{
if (pk is PK5 pk5 && index == 2)
pk5.HiddenAbility = true;
else if (pk.Format <= 5)
pk.PID = EntityPID.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001));
pk.RefreshAbility(index);
}
/// <summary>
/// Sets a Random <see cref="PKM.EncryptionConstant"/> value. The <see cref="PKM.EncryptionConstant"/> is not updated if the value should match the <see cref="PKM.PID"/> instead.
/// </summary>
/// <remarks>Accounts for Wurmple evolutions.</remarks>
public void SetRandomEC()
{
var gen = pk.Generation;
if (gen is 3 or 4 or 5)
{
pk.EncryptionConstant = pk.PID;
return;
}
pk.EncryptionConstant = GetComplicatedEC(pk);
}
/// <summary>
/// Sets the <see cref="PKM.IsShiny"/> derived value.
/// </summary>
/// <param name="shiny">Desired <see cref="PKM.IsShiny"/> state to set.</param>
public bool SetIsShiny(bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny();
/// <summary>
/// Makes a <see cref="PKM"/> shiny.
/// </summary>
/// <param name="type">Shiny type to force. Only use Always* or Random</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public bool SetShiny(Shiny type = Shiny.Random)
{
if (pk.IsShiny && type.IsValid(pk))
return false;
if (type == Shiny.Random || pk.FatefulEncounter || pk.Version == GameVersion.GO || pk.Format <= 2)
{
pk.SetShiny();
return true;
}
do { pk.SetShiny(); }
while (!type.IsValid(pk));
return true;
}
do { pk.SetShiny(); }
while (!type.IsValid(pk));
return true;
}
/// <summary>
/// Makes a <see cref="PKM"/> not-shiny.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public static bool SetUnshiny(this PKM pk)
{
if (!pk.IsShiny)
return false;
pk.SetPIDGender(pk.Gender);
return true;
}
/// <summary>
/// Sets the <see cref="PKM.Nature"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Nature"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
public static void SetNature(this PKM pk, int nature)
{
var value = Math.Clamp(nature, (int)Nature.Hardy, (int)Nature.Quirky);
var format = pk.Format;
if (format >= 8)
pk.StatNature = value;
else if (format is 3 or 4)
pk.SetPIDNature(value);
else
pk.Nature = value;
}
/// <summary>
/// Copies <see cref="IBattleTemplate"/> details to the <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="Set"><see cref="IBattleTemplate"/> details to copy from.</param>
public static void ApplySetDetails(this PKM pk, IBattleTemplate Set)
{
pk.Species = Math.Min(pk.MaxSpeciesID, Set.Species);
pk.Form = Set.Form;
if (Set.Moves[0] != 0)
pk.SetMoves(Set.Moves, true);
pk.ApplyHeldItem(Set.HeldItem, Set.Context);
pk.CurrentLevel = Set.Level;
pk.CurrentFriendship = Set.Friendship;
pk.SetIVs(Set.IVs);
if (pk is GBPKM gb)
/// <summary>
/// Makes a <see cref="PKM"/> not-shiny.
/// </summary>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public bool SetUnshiny()
{
// In Generation 1/2 Format sets, when IVs are not specified with a Hidden Power set, we might not have the hidden power type.
// Under this scenario, just force the Hidden Power type.
if (Array.IndexOf(Set.Moves, (ushort)Move.HiddenPower) != -1 && pk.HPType != Set.HiddenPowerType)
{
if (Set.IVs.AsSpan().IndexOfAny(30, 31) >= 0)
pk.SetHiddenPower(Set.HiddenPowerType);
}
if (!pk.IsShiny)
return false;
// In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead!
// Under this scenario, just apply maximum EVs (65535).
if (Set.EVs.AsSpan().IndexOfAnyExcept(0) == -1)
gb.MaxEVs();
pk.SetPIDGender(pk.Gender);
return true;
}
/// <summary>
/// Sets the <see cref="PKM.Nature"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Nature"/> value.
/// </summary>
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
public void SetNature(Nature nature)
{
if (!nature.IsFixed)
nature = 0; // default valid
var format = pk.Format;
if (format >= 8)
pk.StatNature = nature;
else if (format is 3 or 4)
pk.SetPIDNature(nature);
else
pk.SetEVs(Set.EVs);
}
else
{
pk.SetEVs(Set.EVs);
pk.Nature = nature;
}
// IVs have no side effects such as hidden power type in gen 8
// therefore all specified IVs are deliberate and should not be Hyper Trained for pokemon met in gen 8
if (pk.Generation < 8)
pk.SetSuggestedHyperTrainingData(Set.IVs);
if (ShowdownSetIVMarkings)
pk.SetMarkings();
pk.SetNickname(Set.Nickname);
pk.SetSaneGender(Set.Gender);
if (Legal.IsPPUpAvailable(pk))
pk.SetMaximumPPUps(Set.Moves);
if (pk.Format >= 3)
/// <summary>
/// Copies <see cref="IBattleTemplate"/> details to the <see cref="PKM"/>.
/// </summary>
/// <param name="set"><see cref="IBattleTemplate"/> details to copy from.</param>
public void ApplySetDetails(IBattleTemplate set)
{
pk.SetAbility(Set.Ability);
pk.SetNature(Set.Nature);
}
pk.Species = Math.Min(pk.MaxSpeciesID, set.Species);
pk.Form = set.Form;
pk.SetIsShiny(Set.Shiny);
pk.SetRandomEC();
ReadOnlySpan<ushort> moves = set.Moves;
if (moves[0] != 0)
pk.SetMoves(moves, true);
if (Legal.IsPPUpAvailable(pk))
pk.SetMaximumPPUps(moves);
if (pk is IAwakened a)
{
a.SetSuggestedAwakenedValues(pk);
if (pk is PB7 b)
pk.ApplyHeldItem(set.HeldItem, set.Context);
pk.CurrentLevel = set.Level;
pk.CurrentFriendship = set.Friendship;
ReadOnlySpan<int> ivs = set.IVs;
ReadOnlySpan<int> evs = set.EVs;
pk.SetIVs(ivs);
if (pk is GBPKM gb)
{
for (int i = 0; i < 6; i++)
b.SetEV(i, 0);
b.ResetCalculatedValues();
// In Generation 1/2 Format sets, when IVs are not specified with a Hidden Power set, we might not have the hidden power type.
// Under this scenario, just force the Hidden Power type.
if (moves.Contains((ushort)Move.HiddenPower) && gb.HPType != set.HiddenPowerType)
{
if (ivs.ContainsAny(30, 31))
gb.SetHiddenPower(set.HiddenPowerType);
}
// In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead!
// Under this scenario, just apply maximum EVs (65535).
if (!evs.ContainsAnyExcept(0))
gb.MaxEVs();
else if (evs.ContainsAnyExceptInRange(0, 252)) // Any specified above 252
gb.SetEVs(evs);
else
gb.SetSqrtEVs(evs);
}
else
{
pk.SetEVs(evs);
}
}
if (pk is IGanbaru g)
g.SetSuggestedGanbaruValues(pk);
if (pk is IGigantamax c)
c.CanGigantamax = Set.CanGigantamax;
if (pk is IDynamaxLevel d)
d.DynamaxLevel = d.GetSuggestedDynamaxLevel(pk, requested: Set.DynamaxLevel);
if (pk is ITeraType tera)
// IVs have no side effects such as hidden power type in gen 8
// therefore all specified IVs are deliberate and should not be Hyper Trained for Pokémon met in gen 8
if (pk.Generation < 8)
pk.SetSuggestedHyperTrainingData(ivs);
if (ShowdownSetIVMarkings)
pk.SetMarkings();
pk.SetNickname(set.Nickname);
pk.SetSaneGender(set.Gender);
if (pk.Format >= 3)
{
pk.SetAbility(set.Ability);
pk.SetNature(set.Nature);
}
pk.SetIsShiny(set.Shiny);
pk.SetRandomEC();
if (pk is IAwakened a)
{
a.SetSuggestedAwakenedValues(pk);
if (pk is PB7 b)
{
for (int i = 0; i < 6; i++)
b.SetEV(i, 0);
b.ResetCalculatedValues();
}
}
if (pk is IGanbaru g)
g.SetSuggestedGanbaruValues(pk);
if (pk is IGigantamax c)
c.CanGigantamax = set.CanGigantamax;
if (pk is IDynamaxLevel d)
d.DynamaxLevel = d.GetSuggestedDynamaxLevel(pk, requested: set.DynamaxLevel);
if (pk is ITeraType tera)
{
var type = set.TeraType == MoveType.Any ? (MoveType)pk.PersonalInfo.Type1 : set.TeraType;
tera.SetTeraType(type);
}
if (pk is IMoveShop8Mastery s)
s.SetMoveShopFlags(set.Moves, pk);
if (ShowdownSetBehaviorNature && pk.Format >= 8)
pk.Nature = pk.StatNature;
var legal = new LegalityAnalysis(pk);
if (pk is ITechRecord t)
{
t.ClearRecordFlags();
t.SetRecordFlags(set.Moves, legal.Info.EvoChainsAllGens.Get(pk.Context));
}
if (pk is IPlusRecord plus && pk.PersonalInfo is IPermitPlus permit)
{
plus.ClearPlusFlags(permit.PlusCountTotal);
plus.SetPlusFlags(permit, legal, true, true);
}
if (legal.Parsed && !MoveResult.AllValid(legal.Info.Relearn))
pk.SetRelearnMoves(legal);
pk.ResetPartyStats();
pk.RefreshChecksum();
}
/// <summary>
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index &amp; format.
/// </summary>
/// <param name="item">Held Item to apply</param>
/// <param name="context">Format required for importing</param>
public void ApplyHeldItem(int item, EntityContext context)
{
var type = Set.TeraType == MoveType.Any ? (MoveType)pk.PersonalInfo.Type1 : Set.TeraType;
tera.SetTeraType(type);
item = ItemConverter.GetItemForFormat(item, context, pk.Context);
pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item;
}
if (pk is IMoveShop8Mastery s)
s.SetMoveShopFlags(Set.Moves, pk);
if (ShowdownSetBehaviorNature && pk.Format >= 8)
pk.Nature = pk.StatNature;
var legal = new LegalityAnalysis(pk);
if (pk is ITechRecord t)
/// <summary>
/// Sets one of the <see cref="EffortValues"/> based on its index within the array.
/// </summary>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public int SetEV(int index, int value) => index switch
{
t.ClearRecordFlags();
t.SetRecordFlags(Set.Moves, legal.Info.EvoChainsAllGens.Get(pk.Context));
0 => pk.EV_HP = value,
1 => pk.EV_ATK = value,
2 => pk.EV_DEF = value,
3 => pk.EV_SPE = value,
4 => pk.EV_SPA = value,
5 => pk.EV_SPD = value,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
/// <summary>
/// Sets one of the <see cref="PKM.IVs"/> based on its index within the array.
/// </summary>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public int SetIV(int index, int value) => index switch
{
0 => pk.IV_HP = value,
1 => pk.IV_ATK = value,
2 => pk.IV_DEF = value,
3 => pk.IV_SPE = value,
4 => pk.IV_SPA = value,
5 => pk.IV_SPD = value,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
/// <summary>
/// Fetches the highest value the provided <see cref="EffortValues"/> index can be while considering others.
/// </summary>
/// <param name="index">Index to fetch for</param>
/// <returns>Highest value the value can be.</returns>
public int GetMaximumEV(int index)
{
if (pk.Format < 3)
return EffortValues.Max12;
var sum = pk.EVTotal - pk.GetEV(index);
int remaining = EffortValues.Max510 - sum;
return Math.Clamp(remaining, 0, EffortValues.Max252);
}
if (legal.Parsed && !MoveResult.AllValid(legal.Info.Relearn))
pk.SetRelearnMoves(legal);
pk.ResetPartyStats();
pk.RefreshChecksum();
}
/// <summary>
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index &amp; format.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="item">Held Item to apply</param>
/// <param name="context">Format required for importing</param>
public static void ApplyHeldItem(this PKM pk, int item, EntityContext context)
{
item = ItemConverter.GetItemForFormat(item, context, pk.Context);
pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item;
}
/// <summary>
/// Fetches the highest value the provided <see cref="PKM.IVs"/>.
/// </summary>
/// <param name="index">Index to fetch for</param>
/// <param name="allow30">Causes the returned value to be dropped down -1 if the value is already at a maximum.</param>
/// <returns>Highest value the value can be.</returns>
public int GetMaximumIV(int index, bool allow30 = false)
{
if (pk.GetIV(index) == pk.MaxIV && allow30)
return pk.MaxIV - 1;
return pk.MaxIV;
}
/// <summary>
/// Sets one of the <see cref="EffortValues"/> based on its index within the array.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public static int SetEV(this PKM pk, int index, int value) => index switch
{
0 => pk.EV_HP = value,
1 => pk.EV_ATK = value,
2 => pk.EV_DEF = value,
3 => pk.EV_SPE = value,
4 => pk.EV_SPA = value,
5 => pk.EV_SPD = value,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
/// <summary>
/// Sets one of the <see cref="PKM.IVs"/> based on its index within the array.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public static int SetIV(this PKM pk, int index, int value) => index switch
{
0 => pk.IV_HP = value,
1 => pk.IV_ATK = value,
2 => pk.IV_DEF = value,
3 => pk.IV_SPE = value,
4 => pk.IV_SPA = value,
5 => pk.IV_SPD = value,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
/// <summary>
/// Fetches the highest value the provided <see cref="EffortValues"/> index can be while considering others.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to fetch for</param>
/// <returns>Highest value the value can be.</returns>
public static int GetMaximumEV(this PKM pk, int index)
{
if (pk.Format < 3)
return ushort.MaxValue;
var sum = pk.EVTotal - pk.GetEV(index);
int remaining = 510 - sum;
return Math.Clamp(remaining, 0, 252);
}
/// <summary>
/// Fetches the highest value the provided <see cref="PKM.IVs"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to fetch for</param>
/// <param name="allow30">Causes the returned value to be dropped down -1 if the value is already at a maximum.</param>
/// <returns>Highest value the value can be.</returns>
public static int GetMaximumIV(this PKM pk, int index, bool allow30 = false)
{
if (pk.GetIV(index) == pk.MaxIV && allow30)
return pk.MaxIV - 1;
return pk.MaxIV;
}
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
/// <param name="tr">Trainer to force hatch with if Version is not currently set.</param>
/// <param name="reHatch">Re-hatch already hatched <see cref="PKM"/> inputs</param>
public static void ForceHatchPKM(this PKM pk, ITrainerInfo? tr = null, bool reHatch = false)
{
if (!pk.IsEgg && !reHatch)
return;
pk.IsEgg = false;
pk.ClearNickname();
pk.CurrentFriendship = pk.PersonalInfo.BaseFriendship;
if (pk.IsTradedEgg)
pk.Egg_Location = pk.Met_Location;
if (pk.Version == 0)
pk.Version = (int)EggStateLegality.GetEggHatchVersion(pk, (GameVersion)(tr?.Game ?? RecentTrainerCache.Game));
var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pk);
if (loc >= 0)
pk.Met_Location = loc;
pk.MetDate = DateOnly.FromDateTime(DateTime.Today);
if (pk.Gen6)
pk.SetHatchMemory6();
}
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
/// <param name="origin">Game the egg originated from</param>
/// <param name="dest">Game the egg is currently present on</param>
public static void SetEggMetData(this PKM pk, GameVersion origin, GameVersion dest)
{
bool traded = origin != dest;
var today = pk.MetDate = DateOnly.FromDateTime(DateTime.Today);
pk.Egg_Location = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(pk.Generation, origin, traded);
pk.EggMetDate = today;
}
/// <summary>
/// Maximizes the <see cref="PKM.CurrentFriendship"/>. If the <see cref="PKM.IsEgg"/>, the hatch counter is set to 1.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
public static void MaximizeFriendship(this PKM pk)
{
if (pk.IsEgg)
pk.OT_Friendship = 1;
else
pk.CurrentFriendship = byte.MaxValue;
if (pk is ICombatPower pb)
pb.ResetCP();
}
/// <summary>
/// Maximizes the <see cref="PKM.CurrentLevel"/>. If the <see cref="PKM.IsEgg"/>, the <see cref="PKM"/> is ignored.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
public static void MaximizeLevel(this PKM pk)
{
if (pk.IsEgg)
return;
pk.CurrentLevel = 100;
if (pk is ICombatPower pb)
pb.ResetCP();
}
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="la">Precomputed optional</param>
public static void SetDefaultNickname(this PKM pk, LegalityAnalysis la)
{
if (la is { Parsed: true, EncounterOriginal: IFixedNickname {IsFixedNickname: true} t })
pk.SetNickname(t.GetNickname(pk.Language));
else
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="tr">Trainer to force hatch with if Version is not currently set.</param>
/// <param name="reHatch">Re-hatch already hatched <see cref="PKM"/> inputs</param>
public void ForceHatchPKM(ITrainerInfo? tr = null, bool reHatch = false)
{
if (!pk.IsEgg && !reHatch)
return;
pk.IsEgg = false;
pk.ClearNickname();
}
pk.OriginalTrainerFriendship = Math.Min(pk.OriginalTrainerFriendship, EggStateLegality.GetEggHatchFriendship(pk.Context));
if (pk.IsTradedEgg)
pk.EggLocation = pk.MetLocation;
if (pk.Version == 0)
pk.Version = EggStateLegality.GetEggHatchVersion(pk, tr?.Version ?? RecentTrainerCache.Version);
var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pk);
if (loc != EncounterSuggestion.LocationNone)
pk.MetLocation = loc;
if (pk.Format >= 4)
pk.MetDate = EncounterDate.GetDate(pk.Context.Console);
if (pk.Gen6)
pk.SetHatchMemory6();
}
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetDefaultNickname(this PKM pk) => pk.SetDefaultNickname(new LegalityAnalysis(pk));
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="origin">Game the egg originated from</param>
/// <param name="dest">Game the egg is currently present on</param>
public void SetEggMetData(GameVersion origin, GameVersion dest)
{
if (pk.Format < 4)
return;
private static string GetPotentialUnicode(int rating) => rating switch
{
0 => "★☆☆☆",
1 => "★★☆☆",
2 => "★★★☆",
_ => "★★★★",
};
var console = pk.Context.Console;
var date = EncounterDate.GetDate(console);
var today = pk.MetDate = date;
bool traded = origin != dest;
pk.EggLocation = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(pk.Generation, origin, traded);
pk.EggMetDate = today;
}
private static string GetPotentialASCII(int rating) => rating switch
{
0 => "+",
1 => "++",
2 => "+++",
_ => "++++",
};
/// <summary>
/// Maximizes the <see cref="PKM.CurrentFriendship"/>. If the <see cref="PKM.IsEgg"/>, the hatch counter is set to 1.
/// </summary>
public void MaximizeFriendship()
{
if (pk.IsEgg)
pk.OriginalTrainerFriendship = 1;
else
pk.CurrentFriendship = byte.MaxValue;
if (pk is ICombatPower pb)
pb.ResetCP();
}
/// <summary>
/// Gets the Potential evaluation of the input <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to analyze.</param>
/// <param name="unicode">Returned value is unicode or not</param>
/// <returns>Potential string</returns>
public static string GetPotentialString(this PKM pk, bool unicode = true)
{
var rating = pk.PotentialRating;
if (unicode)
return GetPotentialUnicode(rating);
return GetPotentialASCII(rating);
/// <summary>
/// Maximizes the <see cref="PKM.CurrentLevel"/>. If the <see cref="PKM.IsEgg"/>, the <see cref="PKM"/> is ignored.
/// </summary>
public void MaximizeLevel()
{
if (pk.IsEgg)
return;
pk.CurrentLevel = Experience.MaxLevel;
if (pk is ICombatPower pb)
pb.ResetCP();
}
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="la">Precomputed optional</param>
public void SetDefaultNickname(LegalityAnalysis la)
{
if (la is { Parsed: true, EncounterOriginal: IFixedNickname {IsFixedNickname: true} t })
pk.SetNickname(t.GetNickname(pk.Language));
else
pk.ClearNickname();
}
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
public void SetDefaultNickname() => pk.SetDefaultNickname(new LegalityAnalysis(pk));
/// <summary>
/// Gets the Location Name for the <see cref="PKM"/>
/// </summary>
/// <param name="eggmet">Location requested is the egg obtained location, not met location.</param>
/// <returns>Location string</returns>
public string GetLocationString(bool eggmet)
{
if (pk.Format < 2)
return string.Empty;
ushort location = eggmet ? pk.EggLocation : pk.MetLocation;
return GameInfo.GetLocationName(eggmet, location, pk.Format, pk.Generation, pk.Version);
}
}
// Extensions
/// <summary>
/// Gets the Location Name for the <see cref="PKM"/>
/// </summary>
/// <param name="pk">PKM to fetch data for</param>
/// <param name="eggmet">Location requested is the egg obtained location, not met location.</param>
/// <returns>Location string</returns>
public static string GetLocationString(this PKM pk, bool eggmet)
{
if (pk.Format < 2)
return string.Empty;
int location = eggmet ? pk.Egg_Location : pk.Met_Location;
return GameInfo.GetLocationName(eggmet, location, pk.Format, pk.Generation, (GameVersion)pk.Version);
}
public const char OptionNone = '\0';
/// <summary>
/// Gets a <see cref="PKM.EncryptionConstant"/> to match the requested option.
/// </summary>
public static uint GetComplicatedEC(ISpeciesForm pk, char option = ' ')
public static uint GetComplicatedEC(ISpeciesForm pk, char option = OptionNone)
{
var species = pk.Species;
var form = pk.Form;
return GetComplicatedEC(species, form, option);
}
/// <inheritdoc cref="GetComplicatedEC(ISpeciesForm,char)"/>
public static uint GetComplicatedEC(ushort species, byte form, char option = OptionNone)
{
var rng = Util.Rand;
uint rand = rng.Rand32();
uint mod = 1, noise = 0;
if (pk.Species is >= (int)Species.Wurmple and <= (int)Species.Dustox)
uint mod, noise;
if (species is >= (int)Species.Wurmple and <= (int)Species.Dustox)
{
mod = 10;
bool lower = option is '0' or 'B' or 'S' || WurmpleUtil.GetWurmpleEvoGroup(pk.Species) == 0;
bool lower = option is '0' or 'B' or 'S' || WurmpleUtil.GetWurmpleEvoGroup(species) == 0;
noise = (lower ? 0u : 5u) + (uint)rng.Next(0, 5);
}
else if (pk.Species is (int)Species.Dunsparce or (int)Species.Dudunsparce or (int)Species.Tandemaus or (int)Species.Maushold)
else if (species is (int)Species.Dunsparce or (int)Species.Dudunsparce or (int)Species.Tandemaus or (int)Species.Maushold)
{
mod = 100;
noise = option switch
noise = species switch
{
'0' or '3' => 0u,
_ when pk.Species is (int)Species.Dudunsparce && pk.Form == 1 => 0, // 3 Segment
_ when pk.Species is (int)Species.Maushold && pk.Form == 0 => 0, // Family of 3
_ => (uint)rng.Next(1, 100),
// Retain requisite correlation to allow for evolving into this species too.
(int)Species.Dudunsparce => form == 1 ? 0 : (uint)rng.Next(1, 100), // 3 Segment
(int)Species.Maushold => form == 0 ? 0 : (uint)rng.Next(1, 100), // Family of 3
// Otherwise, check if one is preferred, and if not, just make it the more common outcome.
_ => option switch
{
'0' or '3' => 0u,
_ => (uint)rng.Next(1, 100),
},
};
}
else if (option is >= '0' and <= '5')
@ -491,6 +485,10 @@ public static uint GetComplicatedEC(ISpeciesForm pk, char option = ' ')
mod = 6;
noise = (uint)(option - '0');
}
else
{
return rand;
}
return unchecked(rand - (rand % mod) + noise);
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace PKHeX.Core;
@ -8,7 +10,34 @@ namespace PKHeX.Core;
/// </summary>
public sealed class TrainerDatabase
{
private readonly Dictionary<GameVersion, List<ITrainerInfo>> Database = new();
private readonly Dictionary<GameVersion, List<ITrainerInfo>> Database = [];
/// <summary>
/// Gets the number of unique versions in the database.
/// </summary>
public int CountVersions => Database.Count;
/// <summary>
/// Gets the number of trainers in the database.
/// </summary>
public int CountTrainers => Database.Sum(z => z.Value.Count);
/// <summary>
/// Checks if the database contains any trainers for the specified <see cref="version"/>.
/// </summary>
/// <param name="version"></param>
public bool HasVersion(GameVersion version) => Database.ContainsKey(version);
/// <summary>
/// Gets all trainers from the database for the specified saved <see cref="version"/>.
/// </summary>
/// <param name="version">Saved Version to fetch trainers for</param>
public ReadOnlySpan<ITrainerInfo> GetTrainers(GameVersion version)
{
if (Database.TryGetValue(version, out var list))
return CollectionsMarshal.AsSpan(list);
return default;
}
/// <summary>
/// Fetches an appropriate trainer based on the requested <see cref="version"/>.
@ -16,81 +45,77 @@ public sealed class TrainerDatabase
/// <param name="version">Version the trainer should originate from</param>
/// <param name="language">Language to request for</param>
/// <returns>Null if no trainer found for this version.</returns>
public ITrainerInfo? GetTrainer(int version, LanguageID? language = null) => GetTrainer((GameVersion)version, language);
/// <summary>
/// Fetches an appropriate trainer based on the requested <see cref="ver"/>.
/// </summary>
/// <param name="ver">Version the trainer should originate from</param>
/// <param name="language">Language to request for</param>
/// <returns>Null if no trainer found for this version.</returns>
public ITrainerInfo? GetTrainer(GameVersion ver, LanguageID? language = null)
public ITrainerInfo? GetTrainer(GameVersion version, LanguageID? language = null)
{
if (ver <= 0)
if (version <= 0)
return null;
if (!ver.IsValidSavedVersion())
return GetTrainerFromGroup(ver, language);
if (!version.IsValidSavedVersion())
return GetTrainerFromGroup(version, language);
if (Database.TryGetValue(ver, out var list))
return GetRandomChoice(list);
if (Database.TryGetValue(version, out var list))
return list[GetRandomIndex(list.Count)];
return null;
}
private static T GetRandomChoice<T>(IReadOnlyList<T> list)
{
if (list.Count == 1)
return list[0];
return list[Util.Rand.Next(list.Count)];
}
private static int GetRandomIndex(int count) => count == 1 ? 0 : Util.Rand.Next(count);
/// <summary>
/// Fetches an appropriate trainer based on the requested <see cref="ver"/> group.
/// Fetches an appropriate trainer based on the requested <see cref="version"/> group.
/// </summary>
/// <param name="ver">Version the trainer should originate from</param>
/// <param name="version">Version the trainer should originate from</param>
/// <param name="lang">Language to request for</param>
/// <returns>Null if no trainer found for this version.</returns>
private ITrainerInfo? GetTrainerFromGroup(GameVersion ver, LanguageID? lang = null)
{
var possible = Database.Where(z => ver.Contains(z.Key)).ToList();
if (lang != null)
{
possible = possible.Select(z =>
{
var filtered = z.Value.Where(x => x.Language == (int)lang).ToList();
return new KeyValuePair<GameVersion, List<ITrainerInfo>>(z.Key, filtered);
}).Where(z => z.Value.Count != 0).ToList();
}
return GetRandomTrainer(possible);
}
/// <summary>
/// Fetches an appropriate trainer based on the requested <see cref="generation"/>.
/// </summary>
/// <param name="generation">Generation the trainer should inhabit</param>
/// <param name="lang">Language to request for</param>
/// <returns>Null if no trainer found for this version.</returns>
public ITrainerInfo? GetTrainerFromGen(int generation, LanguageID? lang = null)
{
var possible = Database.Where(z => z.Key.GetGeneration() == generation).ToList();
if (lang != null)
{
possible = possible.Select(z =>
{
var filtered = z.Value.Where(x => x.Language == (int)lang).ToList();
return new KeyValuePair<GameVersion, List<ITrainerInfo>>(z.Key, filtered);
}).Where(z => z.Value.Count != 0).ToList();
}
return GetRandomTrainer(possible);
}
private static ITrainerInfo? GetRandomTrainer(IReadOnlyList<KeyValuePair<GameVersion, List<ITrainerInfo>>> possible)
private ITrainerInfo? GetTrainerFromGroup(GameVersion version, LanguageID? lang = null)
{
var possible = Database.Where(z => version.Contains(z.Key)).ToList();
if (possible.Count == 0)
return null;
var group = GetRandomChoice(possible);
return GetRandomChoice(group.Value);
if (lang is not null)
{
possible = possible.Select(z =>
{
var filtered = z.Value.Where(x => x.Language == (int)lang).ToList();
return new KeyValuePair<GameVersion, List<ITrainerInfo>>(z.Key, filtered);
}).Where(z => z.Value.Count != 0).ToList();
}
var span = CollectionsMarshal.AsSpan(possible);
return GetRandomTrainer(span);
}
/// <summary>
/// Fetches an appropriate trainer based on the requested <see cref="context"/>.
/// </summary>
/// <param name="context">Generation the trainer should inhabit</param>
/// <param name="lang">Language to request for</param>
/// <returns>Null if no trainer found for this version.</returns>
public ITrainerInfo? GetTrainerFromContext(EntityContext context, LanguageID? lang = null)
{
var possible = Database.Where(z => z.Key.Context == context).ToList();
if (possible.Count == 0)
return null;
if (lang is not null)
{
possible = possible.Select(z =>
{
var filtered = z.Value.Where(x => x.Language == (int)lang).ToList();
return new KeyValuePair<GameVersion, List<ITrainerInfo>>(z.Key, filtered);
}).Where(z => z.Value.Count != 0).ToList();
}
var span = CollectionsMarshal.AsSpan(possible);
return GetRandomTrainer(span);
}
private static ITrainerInfo? GetRandomTrainer(ReadOnlySpan<KeyValuePair<GameVersion, List<ITrainerInfo>>> possible)
{
if (possible.Length == 0)
return null;
var group = possible[GetRandomIndex(possible.Length)];
var span = group.Value;
return span[GetRandomIndex(span.Count)];
}
/// <summary>
@ -99,12 +124,10 @@ private static T GetRandomChoice<T>(IReadOnlyList<T> list)
/// <param name="trainer">Trainer details to add.</param>
public void Register(ITrainerInfo trainer)
{
var ver = (GameVersion)trainer.Game;
if (ver <= 0 && trainer is SaveFile s)
ver = s.Version;
if (!Database.TryGetValue(ver, out var list))
var version = trainer.Version;
if (!Database.TryGetValue(version, out var list))
{
Database.Add(ver, new List<ITrainerInfo> { trainer });
Database.Add(version, [trainer]);
return;
}
@ -127,21 +150,36 @@ public void Register(ITrainerInfo trainer)
/// <remarks>A copy of the object will be made to prevent modifications, just in case.</remarks>
public void RegisterCopy(ITrainerInfo info) => Register(new SimpleTrainerInfo(info));
private static ITrainerInfo GetTrainerReference(PKM pk)
private static SimpleTrainerInfo GetTrainerReference(PKM pk)
{
var result = new SimpleTrainerInfo((GameVersion)pk.Version)
var (cr, c, r) = GetRegion3DS(pk);
return GetTrainerReference(pk, cr, c, r);
}
private static SimpleTrainerInfo GetTrainerReference(PKM pk, byte cr, byte c, byte r) => new(pk.Version)
{
TID16 = pk.TID16,
SID16 = pk.SID16,
OT = pk.OriginalTrainerName,
Gender = pk.OriginalTrainerGender,
Language = pk.Language,
Generation = pk.Generation,
ConsoleRegion = cr,
Country = c,
Region = r,
};
private static (byte ConsoleRegion, byte Country, byte Region) GetRegion3DS(PKM pk)
{
if (pk is IRegionOriginReadOnly x)
return (x.ConsoleRegion, x.Country, x.Region);
if (pk.Version.IsGen6() || pk.Version.IsGen7())
{
TID16 = pk.TID16, SID16 = pk.SID16, OT = pk.OT_Name, Gender = pk.OT_Gender,
Language = pk.Language,
Generation = pk.Generation,
};
if (pk is IRegionOrigin r)
r.CopyRegionOrigin(result);
else
result.SetDefaultRegionOrigins(result.Language);
return result;
if (pk.Language == (int)LanguageID.Japanese)
return (0, 1, 0);
return (1, 7, 49);
}
return default;
}
/// <summary>

View File

@ -15,7 +15,7 @@ public static class HiddenPower
/// <param name="context">Generation format</param>
public static int GetType(ReadOnlySpan<int> IVs, EntityContext context)
{
if (context.Generation() <= 2)
if (context.IsEraGameBoy)
return GetTypeGB(IVs);
return GetType(IVs);
}
@ -33,8 +33,70 @@ public static int GetType(ReadOnlySpan<int> IVs)
return SixBitType[hp];
}
private static ReadOnlySpan<byte> SixBitType => new byte[]
/// <summary>
/// Gets the current Hidden Power Type of the input IVs for Generations 3+
/// </summary>
/// <param name="u32">32-bit value of the IVs</param>
/// <returns>Hidden Power Type of the IVs</returns>
public static int GetType(uint u32)
{
uint hp = 0;
for (int i = 0; i < 6; i++)
{
hp |= (u32 & 1) << i;
u32 >>= 5;
}
return SixBitType[(int)hp];
}
/// <summary>
/// Gets the current Hidden Power Type of the input IVs for Generations 3+
/// </summary>
/// <param name="u32">32-bit value of the IVs</param>
/// <remarks>IVs are stored in reverse order in the 32-bit value</remarks>
/// <returns>Hidden Power Type of the IVs</returns>
public static int GetTypeBigEndian(uint u32)
{
uint hp = 0;
for (int i = 0; i < 6; i++)
{
hp |= (u32 & 1) << (5 - i);
u32 >>= 5;
}
return SixBitType[(int)hp];
}
/// <summary>
/// Count of unique Hidden Power Types
/// </summary>
public const int TypeCount = 16;
/// <summary>
/// Checks if the input Hidden Power Type is not one of the 15 valid types.
/// </summary>
/// <param name="type">Hidden Power Type</param>
/// <returns><see langword="true"/> if the input Hidden Power Type is not one of the 15 valid types.</returns>
public static bool IsInvalidType(int type) => (uint)type >= TypeCount;
/// <summary>
/// Gets the Type Name index of the input Hidden Power Type
/// </summary>
/// <param name="type">Fetched Hidden Power Type</param>
/// <param name="index">Type Name index</param>
/// <returns>True if the input Hidden Power Type is valid</returns>
public static bool TryGetTypeIndex(int type, out byte index)
{
if (IsInvalidType(type))
{
index = 0;
return false;
}
index = (byte)(type + 1); // Normal type is not a valid Hidden Power type
return true;
}
private static ReadOnlySpan<byte> SixBitType =>
[
// (low-bit mash) * 15 / 63
00, 00, 00, 00, 00, 01, 01, 01,
01, 02, 02, 02, 02, 03, 03, 03,
@ -44,7 +106,7 @@ public static int GetType(ReadOnlySpan<int> IVs)
09, 09, 10, 10, 10, 10, 10, 11,
11, 11, 11, 12, 12, 12, 12, 13,
13, 13, 13, 14, 14, 14, 14, 15,
};
];
/// <summary>
/// Gets the current Hidden Power Type of the input <see cref="IVs"/> for Generations 1 &amp; 2
@ -58,6 +120,9 @@ public static int GetTypeGB(ReadOnlySpan<int> IVs)
return ((atk & 3) << 2) | (def & 3);
}
/// <inheritdoc cref="GetTypeGB(ReadOnlySpan{int})"/>
public static int GetTypeGB(ushort u16) => ((u16 >> 10) & 0b1100) | ((u16 >> 8) & 0b11);
/// <summary>
/// Modifies the provided <see cref="IVs"/> to have the requested <see cref="hiddenPowerType"/> for Generations 1 &amp; 2
/// </summary>
@ -71,6 +136,14 @@ public static bool SetTypeGB(int hiddenPowerType, Span<int> IVs)
return true;
}
/// <inheritdoc cref="SetTypeGB(int, Span{int})"/>
public static ushort SetTypeGB(int hiddenPowerType, ushort current)
{
// Extract bits from ATK and DEF.
var u16 = ((hiddenPowerType & 0b1100) << 10) | ((hiddenPowerType & 0b11) << 8);
return (ushort)((current & 0b1100_1100_1111_1111) | u16);
}
/// <summary>
/// Modifies the provided <see cref="IVs"/> to have the requested <see cref="hiddenPowerType"/>.
/// </summary>
@ -80,7 +153,7 @@ public static bool SetTypeGB(int hiddenPowerType, Span<int> IVs)
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
public static bool SetIVsForType(int hiddenPowerType, Span<int> IVs, EntityContext context)
{
if (context.Generation() <= 2)
if (context.IsEraGameBoy)
return SetTypeGB(hiddenPowerType, IVs);
return SetIVsForType(hiddenPowerType, IVs);
}
@ -163,9 +236,9 @@ private static int GetFlawedBitCount(ReadOnlySpan<int> ivs, int bitValue)
/// <param name="type">Hidden Power Type</param>
/// <param name="ivs">Individual Values (H/A/B/S/C/D)</param>
/// <param name="context">Generation specific format</param>
public static void SetIVs(int type, Span<int> ivs, EntityContext context = PKX.Context)
public static void SetIVs(int type, Span<int> ivs, EntityContext context = Latest.Context)
{
if (context.Generation() <= 2)
if (context.IsEraGameBoy)
{
ivs[1] = (ivs[1] & 0b1100) | (type >> 2);
ivs[2] = (ivs[2] & 0b1100) | (type & 3);
@ -182,6 +255,41 @@ private static void ForceLowBits(Span<int> ivs, byte bits)
ivs[i] = (ivs[i] & 0b11110) | ((bits >> i) & 1);
}
/// <inheritdoc cref="SetIVs(int,Span{int},EntityContext)"/>
public static uint SetIVs(int type, uint ivs)
{
var bits = DefaultLowBits[type];
for (int i = 0; i < 6; i++)
{
var bit = (bits >> i) & 1;
var bitIndex = i * 5;
var mask = (1u << bitIndex);
if (bit == 0)
ivs &= ~mask;
else
ivs |= mask;
}
return ivs;
}
/// <inheritdoc cref="SetIVs(int,uint)"/>
/// <remarks>IVs are stored in reverse order in the 32-bit value</remarks>
public static uint SetIVsBigEndian(int type, uint ivs)
{
var bits = DefaultLowBits[type];
for (int i = 0; i < 6; i++)
{
var bit = (bits >> i) & 1;
var bitIndex = (5 - i) * 5;
var mask = (1u << bitIndex);
if (bit == 0)
ivs &= ~mask;
else
ivs |= mask;
}
return ivs;
}
/// <summary>
/// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type
/// </summary>
@ -190,8 +298,8 @@ private static void ForceLowBits(Span<int> ivs, byte bits)
/// These are just precomputed for fast modification.
/// Individual Values (H/A/B/S/C/D)
/// </remarks>
public static ReadOnlySpan<byte> DefaultLowBits => new byte[]
{
public static ReadOnlySpan<byte> DefaultLowBits =>
[
0b000011, // Fighting
0b001000, // Flying
0b001011, // Poison
@ -208,5 +316,14 @@ private static void ForceLowBits(Span<int> ivs, byte bits)
0b111001, // Ice
0b111101, // Dragon
0b111111, // Dark
};
];
/// <summary>
/// Gets the suggested low-bits for the input Hidden Power Type
/// </summary>
public static byte GetLowBits(int type)
{
var arr = DefaultLowBits;
return (uint)type < arr.Length ? arr[type] : (byte)0;
}
}

View File

@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary>
/// Simple interface representing a <see cref="PKM"/> viewer.
@ -44,4 +44,10 @@ public interface IPKMView
/// <param name="focus">Cause the viewer to give focus to itself.</param>
/// <param name="skipConversionCheck">Cause the viewer to skip converting the data. Faster if it is known that the format is the same as the previous format.</param>
void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false);
/// <summary>
/// Messages back that the entity has been saved.
/// </summary>
/// <param name="pk">Pokémon data that was saved.</param>
void NotifyWasExported(PKM pk);
}

View File

@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary>
/// Plugin interface used by an editor to notify third-party code providers.
@ -26,6 +26,13 @@ public interface IPlugin
/// </summary>
void NotifySaveLoaded();
/// <summary>
/// Notifies the plugin that the display language has changed.
/// </summary>
/// <param name="language">Short code for language name</param>
/// <remarks>Useful to translate controls if any added.</remarks>
void NotifyDisplayLanguageChanged(string language) { }
/// <summary>
/// Attempts to load a file using the plugin.
/// </summary>

View File

@ -9,7 +9,7 @@ public interface ISpriteBuilder<T>
/// <summary>
/// Gets a sprite using the requested parameters.
/// </summary>
T GetSprite(ushort species, byte form, int gender, uint formarg, int heldItem, bool isEgg, Shiny shiny,
T GetSprite(ushort species, byte form, byte gender, uint formarg, int heldItem, bool isEgg, Shiny shiny,
EntityContext context = EntityContext.None);
/// <summary>

View File

@ -8,12 +8,12 @@ public static class LocationEdits
/// <summary>
/// Gets the "None" location index for a specific <see cref="PKM"/> context.
/// </summary>
public static int GetNoneLocation(PKM pk) => GetNoneLocation(pk.Context);
public static ushort GetNoneLocation(PKM pk) => GetNoneLocation(pk.Context);
/// <summary>
/// Gets the "None" location index for a specific <see cref="PKM"/> context.
/// </summary>
public static int GetNoneLocation(EntityContext context) => context switch
public static ushort GetNoneLocation(EntityContext context) => context switch
{
EntityContext.Gen8b => Locations.Default8bNone,
_ => 0,

View File

@ -8,43 +8,92 @@ namespace PKHeX.Core;
/// </summary>
public static class NatureAmp
{
/// <summary>
/// Mutate the nature amp indexes to match the request
/// </summary>
/// <param name="type">Request type to modify the provided <see cref="statIndex"/></param>
/// <param name="statIndex">Stat Index to mutate</param>
/// <param name="currentNature">Current nature to derive the current amps from</param>
/// <returns>New nature value</returns>
public static int GetNewNature(this NatureAmpRequest type, int statIndex, int currentNature)
extension(NatureAmpRequest type)
{
if (currentNature > (int)Nature.Quirky)
return -1;
var (up, dn) = GetNatureModification(currentNature);
return GetNewNature(type, statIndex, up, dn);
}
/// <inheritdoc cref="GetNewNature(NatureAmpRequest,int,int)"/>
public static int GetNewNature(NatureAmpRequest type, int statIndex, int up, int dn)
{
//
switch (type)
/// <summary>
/// Mutate the nature amp indexes to match the request
/// </summary>
/// <param name="statIndex">Stat Index to mutate</param>
/// <param name="currentNature">Current nature to derive the current amps from</param>
/// <returns>New nature value</returns>
public Nature GetNewNature(int statIndex, Nature currentNature)
{
case Increase when up != statIndex:
up = statIndex;
break;
case Decrease when dn != statIndex:
dn = statIndex;
break;
case Neutral when up != statIndex && dn != statIndex:
up = dn = statIndex;
break;
default:
return -1; // failure
if ((uint)currentNature >= NatureCount)
return Nature.Random;
var (up, dn) = currentNature.GetNatureModification();
return type.GetNewNature(statIndex, up, dn);
}
return CreateNatureFromAmps(up, dn);
/// <inheritdoc cref="GetNewNature(NatureAmpRequest,int,Nature)"/>
public Nature GetNewNature(int statIndex, int up, int dn)
{
//
switch (type)
{
case Increase when up != statIndex:
up = statIndex;
break;
case Decrease when dn != statIndex:
dn = statIndex;
break;
case Neutral when up != statIndex && dn != statIndex:
up = dn = statIndex;
break;
default:
return Nature.Random; // failure
}
return CreateNatureFromAmps(up, dn);
}
}
extension(Nature nature)
{
/// <summary>
/// Decompose the nature to the two stat indexes that are modified
/// </summary>
public (int up, int dn) GetNatureModification()
{
var up = ((byte)nature / 5);
var dn = ((byte)nature % 5);
return (up, dn);
}
/// <inheritdoc cref="IsNeutralOrInvalid(Nature, int, int)"/>
public bool IsNeutralOrInvalid()
{
var (up, dn) = nature.GetNatureModification();
return nature.IsNeutralOrInvalid(up, dn);
}
/// <summary>
/// Checks if the nature is out of range or the stat amplifications are not neutral.
/// </summary>
/// <param name="up">Increased stat</param>
/// <param name="dn">Decreased stat</param>
/// <returns>True if nature modification values are equal or the Nature is out of range.</returns>
public bool IsNeutralOrInvalid(int up, int dn)
{
return up == dn || (byte)nature >= 25; // invalid
}
/// <summary>
/// Updates stats according to the specified nature.
/// </summary>
/// <param name="stats">Current stats to amplify if appropriate</param>
public void ModifyStatsForNature(Span<ushort> stats)
{
var (up, dn) = nature.GetNatureModification();
if (nature.IsNeutralOrInvalid(up, dn))
return;
ref var upStat = ref stats[up + 1];
ref var dnStat = ref stats[dn + 1];
upStat = (ushort)((upStat * 11) / 10);
dnStat = (ushort)((dnStat * 9) / 10);
}
}
/// <summary>
@ -53,57 +102,69 @@ public static int GetNewNature(NatureAmpRequest type, int statIndex, int up, int
/// <param name="up">Increased stat</param>
/// <param name="dn">Decreased stat</param>
/// <returns>Nature</returns>
public static int CreateNatureFromAmps(int up, int dn)
public static Nature CreateNatureFromAmps(int up, int dn)
{
if ((uint)up > 5 || (uint)dn > 5)
return Nature.Random;
return (Nature)((up * 5) + dn);
}
/// <summary>
/// Nature Amplification Table
/// </summary>
/// <remarks>-1 is 90%, 0 is 100%, 1 is 110%.</remarks>
public static ReadOnlySpan<sbyte> Table =>
[
0, 0, 0, 0, 0, // Hardy
1,-1, 0, 0, 0, // Lonely
1, 0, 0, 0,-1, // Brave
1, 0,-1, 0, 0, // Adamant
1, 0, 0,-1, 0, // Naughty
-1, 1, 0, 0, 0, // Bold
0, 0, 0, 0, 0, // Docile
0, 1, 0, 0,-1, // Relaxed
0, 1,-1, 0, 0, // Impish
0, 1, 0,-1, 0, // Lax
-1, 0, 0, 0, 1, // Timid
0,-1, 0, 0, 1, // Hasty
0, 0, 0, 0, 0, // Serious
0, 0,-1, 0, 1, // Jolly
0, 0, 0,-1, 1, // Naive
-1, 0, 1, 0, 0, // Modest
0,-1, 1, 0, 0, // Mild
0, 0, 1, 0,-1, // Quiet
0, 0, 0, 0, 0, // Bashful
0, 0, 1,-1, 0, // Rash
-1, 0, 0, 1, 0, // Calm
0,-1, 0, 1, 0, // Gentle
0, 0, 0, 1,-1, // Sassy
0, 0,-1, 1, 0, // Careful
0, 0, 0, 0, 0, // Quirky
];
private const byte NatureCount = 25;
private const int AmpWidth = 5;
public static int AmplifyStat(Nature nature, int index, int initial) => GetNatureAmp(nature, index) switch
{
1 => 110 * initial / 100, // 110%
-1 => 90 * initial / 100, // 90%
_ => initial,
};
private static sbyte GetNatureAmp(Nature nature, int index)
{
if ((uint)nature >= NatureCount)
return -1;
return (up * 5) + dn;
var amps = GetAmps(nature);
return amps[index];
}
/// <summary>
/// Decompose the nature to the two stat indexes that are modified
/// </summary>
public static (int up, int dn) GetNatureModification(int nature)
public static ReadOnlySpan<sbyte> GetAmps(Nature nature)
{
var up = (nature / 5);
var dn = (nature % 5);
return (up, dn);
}
/// <summary>
/// Checks if the nature is out of range or the stat amplifications are not neutral.
/// </summary>
/// <param name="nature">Nature</param>
/// <param name="up">Increased stat</param>
/// <param name="dn">Decreased stat</param>
/// <returns>True if nature modification values are equal or the Nature is out of range.</returns>
public static bool IsNeutralOrInvalid(int nature, int up, int dn)
{
return up == dn || nature >= 25; // invalid
}
/// <inheritdoc cref="IsNeutralOrInvalid(int, int, int)"/>
public static bool IsNeutralOrInvalid(int nature)
{
var (up, dn) = GetNatureModification(nature);
return IsNeutralOrInvalid(nature, up, dn);
}
/// <summary>
/// Updates stats according to the specified nature.
/// </summary>
/// <param name="stats">Current stats to amplify if appropriate</param>
/// <param name="nature">Nature</param>
public static void ModifyStatsForNature(Span<ushort> stats, int nature)
{
var (up, dn) = GetNatureModification(nature);
if (IsNeutralOrInvalid(nature, up, dn))
return;
ref var upStat = ref stats[up + 1];
ref var dnStat = ref stats[dn + 1];
upStat = (ushort)((upStat * 11) / 10);
dnStat = (ushort)((dnStat * 9) / 10);
if ((uint)nature >= NatureCount)
nature = 0;
return Table.Slice(AmpWidth * (byte)nature, AmpWidth);
}
}

View File

@ -9,12 +9,12 @@ namespace PKHeX.Core;
/// </summary>
public static class EntitySuggestionUtil
{
public static List<string> GetMetLocationSuggestionMessage(PKM pk, int level, int location, int minimumLevel, IEncounterable? enc)
public static List<string> GetMetLocationSuggestionMessage(PKM pk, byte level, ushort location, int minimumLevel, IEncounterable? enc)
{
var suggestion = new List<string> { MsgPKMSuggestionStart };
if (pk.Format >= 3)
{
var metList = GameInfo.GetLocationList((GameVersion)pk.Version, pk.Context, egg: false);
var metList = GameInfo.GetLocationList(pk.Version, pk.Context, egg: false);
var locationName = metList.First(loc => loc.Value == location).Text;
suggestion.Add($"{MsgPKMSuggestionMetLocation} {locationName}");
suggestion.Add($"{MsgPKMSuggestionMetLevel} {level}");

View File

@ -14,106 +14,109 @@ public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inh
private readonly GameStrings Strings;
private readonly ushort[] Stats;
protected readonly PKM pk; // protected for children generating extra properties
public readonly PKM Entity; // protected for children generating extra properties
public virtual string Position => "???";
public string Nickname => pk.Nickname;
public string Species => Get(Strings.specieslist, pk.Species);
public string Nature => Get(Strings.natures, pk.StatNature);
public string Gender => Get(GenderSymbols, pk.Gender);
public string ESV => pk.PSV.ToString("0000");
public string HP_Type => Get(Strings.types, pk.HPType + 1);
public string Ability => Get(Strings.abilitylist, pk.Ability);
public string Move1 => Get(Strings.movelist, pk.Move1);
public string Move2 => Get(Strings.movelist, pk.Move2);
public string Move3 => Get(Strings.movelist, pk.Move3);
public string Move4 => Get(Strings.movelist, pk.Move4);
public string HeldItem => GetSpan(Strings.GetItemStrings(pk.Context), pk.HeldItem);
public string Nickname => Entity.Nickname;
public string Species => Get(Strings.specieslist, Entity.Species);
public string Nature => Get(Strings.natures, (byte)Entity.StatNature);
public string Gender => Get(GenderSymbols, Entity.Gender);
public string ESV => Entity.PSV.ToString("0000");
public string HP_Type => GetSpan(Strings.HiddenPowerTypes, Entity.HPType);
public string Ability => Get(Strings.abilitylist, Entity.Ability);
public string Move1 => Get(Strings.movelist, Entity.Move1);
public string Move2 => Get(Strings.movelist, Entity.Move2);
public string Move3 => Get(Strings.movelist, Entity.Move3);
public string Move4 => Get(Strings.movelist, Entity.Move4);
public string HeldItem => GetSpan(Strings.GetItemStrings(Entity.Context), Entity.HeldItem);
public string HP => Stats[0].ToString();
public string ATK => Stats[1].ToString();
public string DEF => Stats[2].ToString();
public string SPA => Stats[4].ToString();
public string SPD => Stats[5].ToString();
public string SPE => Stats[3].ToString();
public string MetLoc => pk.GetLocationString(eggmet: false);
public string EggLoc => pk.GetLocationString(eggmet: true);
public string Ball => Get(Strings.balllist, pk.Ball);
public string OT => pk.OT_Name;
public string Version => Get(Strings.gamelist, pk.Version);
public string OTLang => ((LanguageID)pk.Language).ToString();
public string Legal { get { var la = new LegalityAnalysis(pk); return la.Parsed ? la.Valid.ToString() : "-"; } }
public string MetLoc => Entity.GetLocationString(eggmet: false);
public string EggLoc => Entity.GetLocationString(eggmet: true);
public string Ball => Get(Strings.balllist, Entity.Ball);
public string OT => Entity.OriginalTrainerName;
public string Version => Get(Strings.gamelist, (int)Entity.Version);
public string OTLang => ((LanguageID)Entity.Language).ToString();
public string Legal => Legality.Parsed ? Legality.Valid.ToString() : "-";
public string EncounterType => Legality.EncounterMatch.LongName;
private LegalityAnalysis Legality { get; }
#region Extraneous
public string EC => pk.EncryptionConstant.ToString("X8");
public string PID => pk.PID.ToString("X8");
public int HP_IV => pk.IV_HP;
public int ATK_IV => pk.IV_ATK;
public int DEF_IV => pk.IV_DEF;
public int SPA_IV => pk.IV_SPA;
public int SPD_IV => pk.IV_SPD;
public int SPE_IV => pk.IV_SPE;
public uint EXP => pk.EXP;
public int Level => pk.CurrentLevel;
public int HP_EV => pk.EV_HP;
public int ATK_EV => pk.EV_ATK;
public int DEF_EV => pk.EV_DEF;
public int SPA_EV => pk.EV_SPA;
public int SPD_EV => pk.EV_SPD;
public int SPE_EV => pk.EV_SPE;
public int Cool => pk is IContestStatsReadOnly s ? s.CNT_Cool : 0;
public int Beauty => pk is IContestStatsReadOnly s ? s.CNT_Beauty : 0;
public int Cute => pk is IContestStatsReadOnly s ? s.CNT_Cute : 0;
public int Smart => pk is IContestStatsReadOnly s ? s.CNT_Smart : 0;
public int Tough => pk is IContestStatsReadOnly s ? s.CNT_Tough : 0;
public int Sheen => pk is IContestStatsReadOnly s ? s.CNT_Sheen : 0;
public int Markings => pk.MarkValue;
public string EC => Entity.EncryptionConstant.ToString("X8");
public string PID => Entity.PID.ToString("X8");
public int IV_HP => Entity.IV_HP;
public int IV_ATK => Entity.IV_ATK;
public int IV_DEF => Entity.IV_DEF;
public int IV_SPA => Entity.IV_SPA;
public int IV_SPD => Entity.IV_SPD;
public int IV_SPE => Entity.IV_SPE;
public uint EXP => Entity.EXP;
public byte Level => Entity.CurrentLevel;
public int EV_HP => Entity.EV_HP;
public int EV_ATK => Entity.EV_ATK;
public int EV_DEF => Entity.EV_DEF;
public int EV_SPA => Entity.EV_SPA;
public int EV_SPD => Entity.EV_SPD;
public int EV_SPE => Entity.EV_SPE;
public int Cool => Entity is IContestStatsReadOnly s ? s.ContestCool : 0;
public int Beauty => Entity is IContestStatsReadOnly s ? s.ContestBeauty : 0;
public int Cute => Entity is IContestStatsReadOnly s ? s.ContestCute : 0;
public int Smart => Entity is IContestStatsReadOnly s ? s.ContestSmart : 0;
public int Tough => Entity is IContestStatsReadOnly s ? s.ContestTough : 0;
public int Sheen => Entity is IContestStatsReadOnly s ? s.ContestSheen : 0;
public string NotOT => pk.Format > 5 ? pk.HT_Name : "N/A";
public string NotOT => Entity.Format > 5 ? Entity.HandlingTrainerName : "N/A";
public int AbilityNum => pk.Format > 5 ? pk.AbilityNumber : -1;
public int GenderFlag => pk.Gender;
public byte Form => pk.Form;
public int PKRS_Strain => pk.PKRS_Strain;
public int PKRS_Days => pk.PKRS_Days;
public int MetLevel => pk.Met_Level;
public int OT_Gender => pk.OT_Gender;
public int AbilityNum => Entity.Format > 5 ? Entity.AbilityNumber : -1;
public byte GenderFlag => Entity.Gender;
public byte Form => Entity.Form;
public int PokerusStrain => Entity.PokerusStrain;
public int PokerusDays => Entity.PokerusDays;
public byte MetLevel => Entity.MetLevel;
public byte OriginalTrainerGender => Entity.OriginalTrainerGender;
public bool FatefulEncounter => pk.FatefulEncounter;
public bool IsEgg => pk.IsEgg;
public bool IsNicknamed => pk.IsNicknamed;
public bool IsShiny => pk.IsShiny;
public bool FatefulEncounter => Entity.FatefulEncounter;
public bool IsEgg => Entity.IsEgg;
public bool IsNicknamed => Entity.IsNicknamed;
public bool IsShiny => Entity.IsShiny;
public ushort TID16 => pk.TID16;
public ushort SID16 => pk.SID16;
public uint TSV => pk.TSV;
public int Move1_PP => pk.Move1_PP;
public int Move2_PP => pk.Move2_PP;
public int Move3_PP => pk.Move3_PP;
public int Move4_PP => pk.Move4_PP;
public int Move1_PPUp => pk.Move1_PPUps;
public int Move2_PPUp => pk.Move2_PPUps;
public int Move3_PPUp => pk.Move3_PPUps;
public int Move4_PPUp => pk.Move4_PPUps;
public string Relearn1 => Get(Strings.movelist, pk.RelearnMove1);
public string Relearn2 => Get(Strings.movelist, pk.RelearnMove2);
public string Relearn3 => Get(Strings.movelist, pk.RelearnMove3);
public string Relearn4 => Get(Strings.movelist, pk.RelearnMove4);
public ushort Checksum => pk is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(pk.Data.AsSpan(pk.SIZE_STORED));
public int Friendship => pk.OT_Friendship;
public int Egg_Year => pk.EggMetDate.GetValueOrDefault().Year;
public int Egg_Month => pk.EggMetDate.GetValueOrDefault().Month;
public int Egg_Day => pk.EggMetDate.GetValueOrDefault().Day;
public int Met_Year => pk.MetDate.GetValueOrDefault().Year;
public int Met_Month => pk.MetDate.GetValueOrDefault().Month;
public int Met_Day => pk.MetDate.GetValueOrDefault().Day;
public ushort TID16 => Entity.TID16;
public ushort SID16 => Entity.SID16;
public uint TSV => Entity.TSV;
public int Move1_PP => Entity.Move1_PP;
public int Move2_PP => Entity.Move2_PP;
public int Move3_PP => Entity.Move3_PP;
public int Move4_PP => Entity.Move4_PP;
public int Move1_PPUp => Entity.Move1_PPUps;
public int Move2_PPUp => Entity.Move2_PPUps;
public int Move3_PPUp => Entity.Move3_PPUps;
public int Move4_PPUp => Entity.Move4_PPUps;
public string Relearn1 => Get(Strings.movelist, Entity.RelearnMove1);
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 int Friendship => Entity.OriginalTrainerFriendship;
public int EggYear => Entity.EggMetDate.GetValueOrDefault().Year;
public int EggMonth => Entity.EggMetDate.GetValueOrDefault().Month;
public int EggDay => Entity.EggMetDate.GetValueOrDefault().Day;
public int MetYear => Entity.MetDate.GetValueOrDefault().Year;
public int MetMonth => Entity.MetDate.GetValueOrDefault().Month;
public int MetDay => Entity.MetDate.GetValueOrDefault().Day;
#endregion
protected EntitySummary(PKM p, GameStrings strings)
protected EntitySummary(PKM pk, GameStrings strings)
{
pk = p;
Entity = pk;
Strings = strings;
Stats = pk.GetStats(pk.PersonalInfo);
Stats = Entity.GetStats(Entity.PersonalInfo);
Legality = new LegalityAnalysis(Entity);
}
/// <summary>

View File

@ -1,5 +1,3 @@
using System;
namespace PKHeX.Core;
/// <summary>
@ -17,21 +15,20 @@ public static void TemplateFields(PKM pk, ITrainerInfo tr)
pk.Move1 = (int)Move.Pound;
pk.HealPP();
pk.Ball = 4;
pk.MetDate = DateOnly.FromDateTime(DateTime.Today);
if (tr.Game >= 0)
pk.Version = tr.Game;
if (pk.Format >= 4)
pk.MetDate = EncounterDate.GetDate(pk.Context.Console);
pk.Version = GetTemplateVersion(tr);
pk.Species = GetTemplateSpecies(pk, tr);
pk.Language = GetTemplateLanguage(tr);
pk.Gender = pk.GetSaneGender();
pk.ClearNickname();
pk.OT_Name = tr.OT;
pk.OT_Gender = tr.Gender;
pk.OriginalTrainerName = tr.OT;
pk.OriginalTrainerGender = tr.Gender;
pk.ID32 = tr.ID32;
if (tr is IRegionOrigin o && pk is IRegionOrigin gt)
if (tr is IRegionOriginReadOnly o && pk is IRegionOrigin gt)
{
gt.ConsoleRegion = o.ConsoleRegion;
gt.Country = o.Country;
@ -42,6 +39,17 @@ public static void TemplateFields(PKM pk, ITrainerInfo tr)
pk.RefreshChecksum();
}
private static GameVersion GetTemplateVersion(ITrainerInfo tr)
{
var version = tr.Version;
if (version.IsValidSavedVersion())
return version;
version = version.GetSingleVersion();
if (version.IsValidSavedVersion())
return version;
return default; // 0
}
private static void ApplyTrashBytes(PKM pk, ITrainerInfo tr)
{
// Copy OT trash bytes for sensitive games (Gen1/2)
@ -50,17 +58,17 @@ private static void ApplyTrashBytes(PKM pk, ITrainerInfo tr)
switch (tr)
{
case SAV1 s1:
s1.OT_Trash.CopyTo(pk12.OT_Trash);
s1.OriginalTrainerTrash.CopyTo(pk12.OriginalTrainerTrash);
break;
case SAV2 s2:
s2.OT_Trash.CopyTo(pk12.OT_Trash);
s2.OriginalTrainerTrash.CopyTo(pk12.OriginalTrainerTrash);
break;
}
}
private static ushort GetTemplateSpecies(PKM pk, ITrainerInfo tr)
{
ushort species = tr is IGameValueLimit s ? s.MaxSpeciesID : ((GameVersion)pk.Version).GetMaxSpeciesID();
ushort species = tr is IGameValueLimit s ? s.MaxSpeciesID : pk.Version.GetMaxSpeciesID();
if (species == 0)
species = pk.MaxSpeciesID;
return species;

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core;
@ -10,16 +9,16 @@ namespace PKHeX.Core;
public sealed class LegalMoveComboSource : ILegalMoveDisplaySource<ComboItem>
{
private readonly bool[] IsMoveBoxOrdered = new bool[4];
private ComboItem[] MoveDataAllowed = Array.Empty<ComboItem>();
private ComboItem[] MoveDataAllowed = [];
public IReadOnlyList<ComboItem> DataSource => (ComboItem[])MoveDataAllowed.Clone();
public IReadOnlyList<ComboItem> DataSource => [.. MoveDataAllowed]; // copy
/// <summary>
/// Resets the <see cref="MoveDataAllowed"/> data source with an updated collection.
/// </summary>
public void ReloadMoves(IReadOnlyList<ComboItem> moves)
{
MoveDataAllowed = moves.ToArray();
MoveDataAllowed = [.. moves]; // copy
ClearUpdateCheck();
}
@ -35,7 +34,7 @@ public void ReloadMoves(LegalMoveInfo info)
private void SortMoves(LegalMoveInfo info) => Array.Sort(MoveDataAllowed, (i1, i2) => Compare(i1, i2, info.CanLearn));
// defer re-population until dropdown is opened; handled by dropdown event
private void ClearUpdateCheck() => Array.Clear(IsMoveBoxOrdered, 0, IsMoveBoxOrdered.Length);
private void ClearUpdateCheck() => IsMoveBoxOrdered.AsSpan().Clear();
private static int Compare(ComboItem i1, ComboItem i2, Func<ushort, bool> check)
{

View File

@ -1,5 +1,6 @@
using System;
using System.Buffers;
using static PKHeX.Core.IndicatedSourceType;
namespace PKHeX.Core;
@ -8,16 +9,16 @@ namespace PKHeX.Core;
/// </summary>
public sealed class LegalMoveInfo
{
// Use a bool array instead of a HashSet; we have a limited range of moves.
// Use a byte array instead of a HashSet; we have a limited range of moves.
// This implementation is faster (no hashcode or bucket search) with lower memory overhead (1 byte per move ID).
private readonly bool[] AllowedMoves = new bool[(int)Move.MAX_COUNT + 1];
private readonly IndicatedSourceType[] AllowedMoves = new IndicatedSourceType[(int)Move.MAX_COUNT + 1];
/// <summary>
/// Checks if the requested <see cref="move"/> is legally able to be learned.
/// </summary>
/// <param name="move">Move to check if can be learned</param>
/// <returns>True if can learn the move</returns>
public bool CanLearn(ushort move) => AllowedMoves[move];
/// <param name="move">Move to check if it can be learned</param>
/// <returns>True if it can learn the move</returns>
public bool CanLearn(ushort move) => AllowedMoves[move] != None;
/// <summary>
/// Reloads the legality sources to permit the provided legal info.
@ -25,16 +26,89 @@ public sealed class LegalMoveInfo
/// <param name="la">Details of analysis, moves to allow</param>
public bool ReloadMoves(LegalityAnalysis la)
{
var rent = ArrayPool<bool>.Shared.Rent(AllowedMoves.Length);
var span = rent.AsSpan(0, AllowedMoves.Length);
LearnPossible.Get(la.Entity, la.EncounterOriginal, la.Info.EvoChainsAllGens, span);
var rentLearn = ArrayPool<bool>.Shared.Rent(AllowedMoves.Length);
var spanLearn = rentLearn.AsSpan(0, AllowedMoves.Length);
var rentEval = ArrayPool<IndicatedSourceType>.Shared.Rent(spanLearn.Length);
var spanEval = rentEval.AsSpan(0, spanLearn.Length);
try
{
LearnPossible.Get(la.Entity, la.EncounterOriginal, la.Info.EvoChainsAllGens, spanLearn);
ComputeEval(spanEval, spanLearn, la);
if (spanEval.SequenceEqual(AllowedMoves))
return false;
spanEval.CopyTo(AllowedMoves);
return true;
}
catch
{
if (Array.TrueForAll(AllowedMoves, z => z == None))
return false;
AllowedMoves.AsSpan().Clear();
return true;
}
finally
{
spanLearn.Clear();
spanEval.Clear();
ArrayPool<IndicatedSourceType>.Shared.Return(rentEval);
ArrayPool<bool>.Shared.Return(rentLearn);
}
}
// check prior move-pool to not needlessly refresh the data set
bool diff = !span.SequenceEqual(AllowedMoves);
if (diff) // keep
span.CopyTo(AllowedMoves);
span.Clear();
ArrayPool<bool>.Shared.Return(rent);
return diff;
private static void ComputeEval(Span<IndicatedSourceType> type, ReadOnlySpan<bool> learn, LegalityAnalysis la)
{
for (int i = 0; i < type.Length; i++)
type[i] = learn[i] ? Learn : None;
if (!la.Entity.IsOriginalMovesetDeleted())
AddEncounterMoves(type, la.EncounterOriginal);
type[0] = None; // Move ID 0 is always None
}
private static void AddEncounterMoves(Span<IndicatedSourceType> type, IEncounterTemplate enc)
{
if (enc is IEncounterEgg egg)
{
var moves = egg.Learn.GetEggMoves(enc.Species, enc.Form);
foreach (var move in moves)
type[move] = Egg;
}
else if (enc is IMoveset {Moves: {HasMoves: true} set})
{
foreach (var move in set.AsSpan())
{
if (type[move] == None)
type[move] = Encounter;
}
}
else if (enc is ISingleMoveBonus single)
{
var moves = single.GetMoveBonusPossible();
foreach (var move in moves)
{
if (type[move] == None)
type[move] = EncounterSingle;
}
}
if (enc is IRelearn { Relearn: {HasMoves: true} relearn})
{
foreach (var move in relearn.AsSpan())
{
if (type[move] == None)
type[move] = Relearn;
}
}
}
}
public enum IndicatedSourceType : byte
{
None = 0,
Learn,
Egg,
Encounter,
EncounterSingle,
Relearn,
}

View File

@ -5,12 +5,10 @@ namespace PKHeX.Core;
/// <summary>
/// Legal Move information for a single <see cref="PKM"/>, for indicating if a move is legal or not.
/// </summary>
public sealed class LegalMoveSource<T>
public sealed class LegalMoveSource<T>(ILegalMoveDisplaySource<T> Display)
{
public LegalMoveInfo Info { get; } = new();
public readonly ILegalMoveDisplaySource<T> Display;
public LegalMoveSource(ILegalMoveDisplaySource<T> display) => Display = display;
public readonly ILegalMoveDisplaySource<T> Display = Display;
public void ReloadMoves(LegalityAnalysis source)
{

View File

@ -1,6 +1,6 @@
using System;
using System.Buffers;
using System.Text;
using System.Diagnostics;
namespace PKHeX.Core;
@ -12,8 +12,8 @@ public static class QRMessageUtil
private const string QR6PathBad = "null/#";
private const string QR6Path = "http://lunarcookies.github.io/b1s1.html#";
private const string QR6PathWC = "http://lunarcookies.github.io/wc.html#";
private static string GetExploitURLPrefixPKM(int format) => format == 6 ? QR6Path : QR6PathBad;
private static string GetExploitURLPrefixWC(int format) => format == 6 ? QR6PathWC : QR6PathBad;
private static string GetExploitURLPrefixPKM(byte format) => format == 6 ? QR6Path : QR6PathBad;
private static string GetExploitURLPrefixWC(byte format) => format == 6 ? QR6PathWC : QR6PathBad;
/// <summary>
/// Gets the <see cref="PKM"/> data from the message that is encoded in a QR.
@ -24,7 +24,7 @@ public static class QRMessageUtil
public static PKM? GetPKM(ReadOnlySpan<char> message, EntityContext context)
{
var data = DecodeMessagePKM(message);
if (data == null)
if (data is null)
return null;
return EntityFormat.GetFromBytes(data, context);
}
@ -37,16 +37,21 @@ public static class QRMessageUtil
public static string GetMessage(PKM pk)
{
if (pk is PK7 pk7)
{
byte[] payload = QR7.GenerateQRData(pk7);
return GetMessage(payload);
}
return GetMessage(pk7);
var server = GetExploitURLPrefixPKM(pk.Format);
var data = pk.EncryptedBoxData;
return GetMessageBase64(data, server);
}
/// <inheritdoc cref="GetMessage(PKM)"/>
public static string GetMessage(PK7 pk7, int box = 0, int slot = 0, int num_copies = 1)
{
Span<byte> data = stackalloc byte[QR7.SIZE];
QR7.SetQRData(pk7, data, box, slot, num_copies);
return GetMessage(data);
}
/// <summary>
/// Gets a QR Message from the input <see cref="byte"/> data.
/// </summary>
@ -54,10 +59,10 @@ public static string GetMessage(PKM pk)
/// <returns>QR Message</returns>
public static string GetMessage(ReadOnlySpan<byte> payload)
{
var sb = new StringBuilder(payload.Length);
foreach (var b in payload)
sb.Append((char)b);
return sb.ToString();
Span<char> result = stackalloc char[payload.Length];
for (int i = 0; i < payload.Length; i++)
result[i] = (char)payload[i];
return new string(result);
}
/// <summary>
@ -93,7 +98,7 @@ public static string GetMessageBase64(ReadOnlySpan<byte> data, string server)
if (message.StartsWith("http", StringComparison.Ordinal)) // inject url
return DecodeMessageDataBase64(message);
const int g7size = 0xE8;
const int g7size = PokeCrypto.SIZE_6STORED; // 0xE8;
const int g7intro = 0x30;
if (message.StartsWith("POKE", StringComparison.Ordinal) && message.Length > g7intro + g7size) // G7 data
return GetBytesFromMessage(message[g7intro..], g7size);
@ -118,9 +123,15 @@ public static string GetMessageBase64(ReadOnlySpan<byte> data, string server)
private static byte[] GetBytesFromMessage(ReadOnlySpan<char> input, int count)
{
byte[] data = new byte[count];
for (int i = data.Length - 1; i >= 0; i--)
data[i] = (byte)input[i];
return data;
byte[] result = new byte[count];
GetBytesFromMessage(input, result);
return result;
}
private static void GetBytesFromMessage(ReadOnlySpan<char> input, Span<byte> output)
{
Debug.Assert(input.Length >= output.Length);
for (int i = 0; i < output.Length; i++)
output[i] = (byte)input[i];
}
}

View File

@ -6,48 +6,51 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 7 PGL QR Code encoded <see cref="PKM"/> entities.
/// </summary>
public sealed class QRPK7 : IEncounterInfo
public sealed class QRPK7(Memory<byte> Raw) : IEncounterInfo
{
public GameVersion Version => (GameVersion)CassetteVersion;
public bool EggEncounter => false;
public const int SIZE = 0x30;
private Span<byte> Data => Raw.Span;
public bool IsEgg => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
public int Generation => Version.GetGeneration();
public byte Generation => Version.Generation;
public EntityContext Context => EntityContext.Gen7;
public bool IsShiny => false;
public ushort Location => 0;
public ushort EggLocation => 0;
public AbilityPermission Ability => (AbilityPermission)AbilityIndex;
public Ball FixedBall => (Ball)Ball;
public Shiny Shiny => Shiny.Never;
private readonly byte[] Data;
public const int SIZE = 0x30;
public QRPK7(byte[] d) => Data = (byte[])d.Clone();
public uint EncryptionConstant => ReadUInt32LittleEndian(Data.AsSpan(0));
public byte HT_Flags => Data[4];
public int Unk_5 => Data[5];
public int Unk_6 => Data[6];
public int Unk_7 => Data[7];
public int Move1_PPUps => Data[8];
public int Move2_PPUps => Data[9];
public int Move3_PPUps => Data[0xA];
public int Move4_PPUps => Data[0xB];
public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0xC)); set => WriteUInt32LittleEndian(Data.AsSpan(0xC), value); }
public uint EncryptionConstant => ReadUInt32LittleEndian(Data);
public byte HyperTrainFlags => Data[4];
public byte Unk_5 => Data[5];
public byte Unk_6 => Data[6];
public byte Unk_7 => Data[7];
public byte Move1_PPUps => Data[8];
public byte Move2_PPUps => Data[9];
public byte Move3_PPUps => Data[0xA];
public byte Move4_PPUps => Data[0xB];
public uint IV32 { get => ReadUInt32LittleEndian(Data[0xC..]); set => WriteUInt32LittleEndian(Data[0xC..], value); }
public int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | (uint)((value > 31 ? 31 : value) << 00); }
public int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | (uint)((value > 31 ? 31 : value) << 05); }
public int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | (uint)((value > 31 ? 31 : value) << 10); }
public int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | (uint)((value > 31 ? 31 : value) << 15); }
public int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | (uint)((value > 31 ? 31 : value) << 20); }
public int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | (uint)((value > 31 ? 31 : value) << 25); }
public uint PID => ReadUInt32LittleEndian(Data.AsSpan(0x10));
public ushort Species => ReadUInt16LittleEndian(Data.AsSpan(0x14));
public ushort HeldItem => ReadUInt16LittleEndian(Data.AsSpan(0x16));
public ushort Move1 => ReadUInt16LittleEndian(Data.AsSpan(0x18));
public ushort Move2 => ReadUInt16LittleEndian(Data.AsSpan(0x1A));
public ushort Move3 => ReadUInt16LittleEndian(Data.AsSpan(0x1C));
public ushort Move4 => ReadUInt16LittleEndian(Data.AsSpan(0x1E));
public int Unk_20 => Data[0x20];
public int AbilityIndex => Data[0x21];
public int Nature => Data[0x22];
public uint PID => ReadUInt32LittleEndian(Data[0x10..]);
public ushort Species => ReadUInt16LittleEndian(Data[0x14..]);
public ushort HeldItem => ReadUInt16LittleEndian(Data[0x16..]);
public ushort Move1 => ReadUInt16LittleEndian(Data[0x18..]);
public ushort Move2 => ReadUInt16LittleEndian(Data[0x1A..]);
public ushort Move3 => ReadUInt16LittleEndian(Data[0x1C..]);
public ushort Move4 => ReadUInt16LittleEndian(Data[0x1E..]);
public byte Unk_20 => Data[0x20];
public byte AbilityIndex => Data[0x21];
public Nature Nature => (Nature)Data[0x22];
public bool FatefulEncounter => (Data[0x23] & 1) == 1;
public int Gender => (Data[0x23] >> 1) & 3;
public byte Gender => (byte)((Data[0x23] >> 1) & 3);
public byte Form => (byte)(Data[0x23] >> 3);
public byte EV_HP => Data[0x24];
public byte EV_ATK => Data[0x25];
@ -55,12 +58,12 @@ public sealed class QRPK7 : IEncounterInfo
public byte EV_SPE => Data[0x27];
public byte EV_SPA => Data[0x28];
public byte EV_SPD => Data[0x29];
public int Unk_2A => Data[0x2A];
public int Friendship => Data[0x2B];
public int Ball => Data[0x2C];
public byte Unk_2A => Data[0x2A];
public byte Friendship => Data[0x2B];
public byte Ball => Data[0x2C];
public byte Level => Data[0x2D];
public int CassetteVersion => Data[0x2E];
public int Language => Data[0x2F];
public GameVersion Version => (GameVersion)Data[0x2E];
public byte Language => Data[0x2F];
/// <summary>
/// Converts the <see cref="Data"/> to a rough PKM.
@ -82,7 +85,7 @@ public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
Nature = Nature,
FatefulEncounter = FatefulEncounter,
Form = Form,
HyperTrainFlags = HT_Flags,
HyperTrainFlags = HyperTrainFlags,
IV_HP = IV_HP,
IV_ATK = IV_ATK,
IV_DEF = IV_DEF,
@ -104,16 +107,16 @@ public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
Move3_PPUps = Move3_PPUps,
Move4_PPUps = Move4_PPUps,
HeldItem = HeldItem,
HT_Friendship = Friendship,
OT_Friendship = Friendship,
HandlingTrainerFriendship = Friendship,
OriginalTrainerFriendship = Friendship,
Ball = Ball,
Version = CassetteVersion,
Version = Version,
OT_Name = tr.OT,
HT_Name = tr.OT,
OriginalTrainerName = tr.OT,
HandlingTrainerName = tr.OT,
CurrentLevel = Level,
Met_Level = Level,
MetDate = DateOnly.FromDateTime(DateTime.Today),
MetLevel = Level,
MetDate = EncounterDate.GetDate3DS(),
};
RecentTrainerCache.SetConsoleRegionData3DS(pk, tr);

View File

@ -25,7 +25,7 @@ public static string[] GetQRLines(this PKM pk)
if (move == 0)
continue;
if (sb.Length != 0)
sb.Append(" / ");
sb.Append(Moveset.DefaultSeparator);
var moveName = move < s.movelist.Length ? s.movelist[move] : "ERROR";
sb.Append(moveName);
}
@ -33,12 +33,12 @@ public static string[] GetQRLines(this PKM pk)
string IVs = $"IVs: {pk.IV_HP:00}/{pk.IV_ATK:00}/{pk.IV_DEF:00}/{pk.IV_SPA:00}/{pk.IV_SPD:00}/{pk.IV_SPE:00}";
string EVs = $"EVs: {pk.EV_HP:00}/{pk.EV_ATK:00}/{pk.EV_DEF:00}/{pk.EV_SPA:00}/{pk.EV_SPD:00}/{pk.EV_SPE:00}";
return new[]
{
string.Join(" ", header),
return
[
string.Join(' ', header),
sb.ToString(),
IVs + " " + EVs,
};
];
}
private static IEnumerable<string> GetHeader(PKM pk, GameStrings s)
@ -68,6 +68,6 @@ private static IEnumerable<string> GetHeader(PKM pk, GameStrings s)
}
if (pk.Format >= 3 && (uint)pk.Nature < s.Natures.Count)
yield return s.natures[pk.Nature];
yield return s.natures[(byte)pk.Nature];
}
}

View File

@ -16,54 +16,82 @@ public static class Pokerus
/// Checks if any Pokérus values are possible to have on the input entity.
/// </summary>
/// <param name="pk">Entity to check</param>
/// <param name="enc">Encounter template matched to</param>
/// <returns>True if Pokérus exists in the game format, or can be transmitted to the entity via another game.</returns>
public static bool IsObtainable(PKM pk) => pk switch
public static bool IsObtainable(PKM pk, ISpeciesForm enc) => pk switch
{
PA8 pa8 => HasVisitedAnother(pa8),
PB7 => false,
PK9 => false,
PA8 pa8 => HasVisitedAnother(pa8, enc),
PB7 => false, // Does not exist in game.
PK9 => false, // Does not exist in game, does not get copied over via HOME.
PA9 => false, // Does not exist in game, does not get copied over via HOME.
_ => true,
};
private static bool HasVisitedAnother(PA8 pk)
private static bool HasVisitedAnother(PA8 pk, ISpeciesForm enc)
{
if (pk.IsUntraded)
return false;
if (pk.Tracker == 0)
return false;
if (PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form))
// Ideally we can just check an evolution history to see if it has visited an infectious game, but this method has limited input.
// Use encounter as the lowest evolved species, as Hisuian forms/evolutions are not available on BD/SP or SW/SH.
if (PersonalTable.BDSP.IsPresentInGame(enc.Species, enc.Form))
return true;
if (PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form))
if (PersonalTable.SWSH.IsPresentInGame(enc.Species, enc.Form))
return true;
// S/V, BD/SP, and SW/SH have the ability to receive every Pokémon
// However, S/V does not have Pokérus!
// Species that cannot originate from PLA/SV and have Pokérus.
// Any Hisuian form
// Oshawott, Dewott, Samurott, Enamorus
// Species that cannot originate as-species (can via pre-evo!!!):
// Wyrdeer, Kleavor, Ursaluna, Sneasler, Overqwil, Basculegion
return pk.Generation is (< 8 and >= 1); // Transferred from prior game
}
/// <summary>
/// Indicates if the original <see cref="context"/> can randomly infect with Pokérus.
/// </summary>
public static bool CanOriginatePokerus(this EntityContext context) => context switch
{
EntityContext.Gen1 => false,
EntityContext.Gen7b => false,
EntityContext.Gen8a => false,
EntityContext.Gen9 => false,
EntityContext.Gen9a => false,
_ => true,
};
/// <summary>
/// Checks if the Pokérus value for Strain is possible to have on the input entity.
/// </summary>
/// <param name="pk">Entity to check</param>
/// <param name="enc">Encounter template matched to</param>
/// <param name="strain">Strain number</param>
/// <param name="days">Duration remaining</param>
/// <returns>True if valid</returns>
public static bool IsStrainValid(PKM pk, int strain, int days)
public static bool IsStrainValid(PKM pk, ISpeciesForm enc, int strain, int days)
{
if (!IsObtainable(pk))
if (!IsObtainable(pk, enc))
return IsSusceptible(strain, days);
if (pk.Format <= 2)
return IsStrainValid2(strain);
return IsStrainValid(strain);
}
/// <inheritdoc cref="IsStrainValid(PKM,int,int)"/>
/// <inheritdoc cref="IsStrainValid(PKM,ISpeciesForm,int,int)"/>
/// <remarks>
/// Strains 9+ are not obtainable due to game programming error (jmp label too early).
/// </remarks>
public static bool IsStrainValid2(int strain) => strain <= 8;
/// <inheritdoc cref="IsStrainValid(PKM,int,int)"/>
/// <inheritdoc cref="IsStrainValid(PKM,ISpeciesForm,int,int)"/>
/// <remarks>
/// Gen3 R/S have a 30/255 chance of giving strain 0, and a 1/255 chance of giving strain 8.
/// Transfers will retain strain 0/8 and they're still able to infect others.
/// Transfers will retain strain 0/8, and they're still able to infect others.
/// </remarks>
public static bool IsStrainValid(int strain) => strain <= 0xF;
@ -102,17 +130,18 @@ public static bool IsDurationValid(int strain, int days, out int max)
/// </summary>
/// <param name="strain">Strain number</param>
/// <param name="days">Duration remaining</param>
/// <returns>True if can be infected by another infectious individual.</returns>
/// <returns>True if it can be infected by another infectious individual.</returns>
public static bool IsSusceptible(int strain, int days) => strain == 0 && days == 0;
/// <summary>
/// Vaccinates the Pokémon so it will never be infectious in the format it exists in.
/// Vaccinates the Pokémon, so it will never be infectious in the format it exists in.
/// </summary>
/// <param name="pk">Entity to modify.</param>
/// <param name="enc">Encounter template matched to</param>
/// <remarks>Overwrites all Pokérus values even if already legal.</remarks>
public static void Vaccinate(this PKM pk)
public static void Vaccinate(this PKM pk, ISpeciesForm enc)
{
pk.PKRS_Strain = IsObtainable(pk) ? 1 : 0;
pk.PKRS_Days = 0;
pk.PokerusStrain = IsObtainable(pk, enc) ? 1 : 0;
pk.PokerusDays = 0;
}
}

View File

@ -0,0 +1,48 @@
namespace PKHeX.Core;
/// <summary>
/// Logic for determining the hidden potential (overall IV grade) of a Pokémon.
/// </summary>
public static class PowerPotential
{
/// <summary>
/// Gets the Potential evaluation of the input <see cref="ivTotal"/>.
/// </summary>
public static int GetPotential(int ivTotal) => ivTotal switch
{
<= 90 => 0,
<= 120 => 1,
<= 150 => 2,
_ => 3,
};
private static string GetPotentialUnicode(int rating) => rating switch
{
0 => "★☆☆☆",
1 => "★★☆☆",
2 => "★★★☆",
_ => "★★★★",
};
private static string GetPotentialASCII(int rating) => rating switch
{
0 => "+",
1 => "++",
2 => "+++",
_ => "++++",
};
/// <summary>
/// Gets the Potential evaluation of the input <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to analyze.</param>
/// <param name="unicode">Returned value is unicode or not</param>
/// <returns>Potential string</returns>
public static string GetPotentialString(this PKM pk, bool unicode = true)
{
var rating = pk.PotentialRating;
if (unicode)
return GetPotentialUnicode(rating);
return GetPotentialASCII(rating);
}
}

View File

@ -0,0 +1,13 @@
namespace PKHeX.Core;
public interface IProgramSettings
{
IStartupSettings Startup { get; }
BackupSettings Backup { get; }
SaveLanguageSettings SaveLanguage { get; }
SlotWriteSettings SlotWrite { get; }
SetImportSettings Import { get; }
LegalitySettings Legality { get; }
EntityConverterSettings Converter { get; }
LocalResourceSettings LocalResources { get; }
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -15,10 +15,14 @@ public interface IStartupSettings
/// <summary>
/// Method to load the environment's initial save file.
/// </summary>
AutoLoadSetting AutoLoadSaveOnStartup { get; }
SaveFileLoadSetting AutoLoadSaveOnStartup { get; }
/// <summary>
/// List of recently loaded save file paths.
/// </summary>
List<string> RecentlyLoaded { get; }
string Version { get; set; }
bool ShowChangelogOnUpdate { get; set; }
bool ForceHaXOnLaunch { get; set; }
}

View File

@ -1,12 +1,12 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary>
/// Option to load a save file automatically to an editing environment.
/// </summary>
public enum AutoLoadSetting
public enum SaveFileLoadSetting
{
/// <summary>
/// Doesn't auto load a save file, and instead uses a fake save file data.
/// Doesn't autoload a save file, and instead uses a fake save file data.
/// </summary>
Disabled,

View File

@ -0,0 +1,22 @@
using System;
using System.ComponentModel;
namespace PKHeX.Core;
public sealed class AdvancedSettings
{
[LocalizedDescription("Skip the Overwrite prompt when exporting a save file, to always Save As...")]
public bool SaveExportForceSaveAs { get; set; }
[LocalizedDescription("Check if the Pokémon in the editor has unsaved changes before exporting the save file.")]
public bool SaveExportCheckUnsavedEntity { get; set; } = true;
[LocalizedDescription("Folder path that contains dump(s) of block hash-names. If a specific dump file does not exist, only names defined within the program's code will be loaded.")]
public string PathBlockKeyList { get; set; } = string.Empty;
[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;
[Browsable(false)]
public string[] GetExclusionList8() => Array.ConvertAll(HideEvent8Contains.Split(',', StringSplitOptions.RemoveEmptyEntries), z => z.Trim());
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace PKHeX.Core;
public sealed class BackupSettings
{
[LocalizedDescription("Automatic Backups of Save Files are copied to the backup folder when true.")]
public bool BAKEnabled { get; set; } = true;
[LocalizedDescription("Tracks if the \"Create Backup\" prompt has been issued to the user.")]
public bool BAKPrompt { get; set; }
[LocalizedDescription("List of extra locations to look for Save Files.")]
public List<string> OtherBackupPaths { get; set; } = [];
[LocalizedDescription("Save File file-extensions (no period) that the program should also recognize.")]
public List<string> OtherSaveFileExtensions { get; set; } = [];
}

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