Compare commits

...
Author SHA1 Message Date
Polymeta b79e73928a add multibattle NPC test command for debugging
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-21 15:35:59 +01:00
Polymeta c89ea759a6 fix invalidActionExceptions if the gimmickMove's targetType is different from it's normal move
good example is earthquake (allAdjacent) vs maxquake (adjacentFoe)

Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-08 17:00:58 +01:00
Polymeta c6479525f9 double oop docs
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 16:07:55 +01:00
Polymeta 2a7dababab oop licenses
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 15:58:21 +01:00
Polymeta 7ec6e61d4a first approach with resetting forms pre-battle
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 15:16:06 +01:00
Polymeta 7735af5c11 fixed itempredicates not working when used in species definition
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 11:39:51 +01:00
Polymeta 18d25dcf04 small reshuffle for requirements and introduce keyitem requirement (also add aerodactyl as example)
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 11:39:18 +01:00
Polymeta e259dc18a2 fix doubles not properly working with gimmicks
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 11:26:30 +01:00
Polymeta 7439436e93 fix trailing comma in schema, and check pokemon entity for registryAccess before doing the rest
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-06 10:49:37 +01:00
Polymeta 1c51d5ca98 prevent shiftbutton from being triggered or clicked when its not actually visible
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-06 10:42:59 +01:00
Segfault Guy d94952347f Portrait updates and form effects. 2025-04-30 00:30:06 -04:00
Segfault Guy 7fe6141640 form20 blaze it 2025-04-20 23:41:12 -04:00
Segfault Guy 4b1653ca6c Merge remote-tracking branch 'origin/main' into form_changes 2025-04-20 20:53:54 -04:00
Polymeta 068b55be7b Merge branch 'josh/dynamic-light-integration' into 'main'
Josh/dynamic light integration

See merge request cable-mc/cobblemon!1621
2025-04-20 13:52:34 +00:00
JoshandPolymeta 3f6bd52d43 Josh/dynamic light integration 2025-04-20 13:52:34 +00:00
Polymeta 4ca3f25ab2 Merge branch 'translucent-culling' into 'main'
Translucent Culling Parameter

See merge request cable-mc/cobblemon!1606
2025-04-20 09:47:02 +00:00
GertimoshkaandPolymeta 70e81f1cbe Translucent Culling Parameter 2025-04-20 09:47:02 +00:00
Polymeta df8abf4ddb Merge branch 'josh/left-handed-item-fix' into 'main'
Item position adjustment to perfectly line up hand-held items

See merge request cable-mc/cobblemon!1610
2025-04-20 09:46:28 +00:00
JoshandPolymeta 26dd0b9d42 Item position adjustment to perfectly line up hand-held items 2025-04-20 09:46:28 +00:00
Paul 2f16ba5470 Don't play quirks in battle GUI 2025-04-20 08:53:39 +10:00
Landon cbdfbc9745 Merge branch 'pc-box-name-limit' into 'main'
Adds name character length restriction to PC box name.

See merge request cable-mc/cobblemon!1620
2025-04-19 11:28:15 +00:00
Hiroku ac210e027b Merge branch 'windows-12' into 'main'
more pc stat displays

See merge request cable-mc/cobblemon!1617
2025-04-19 11:04:57 +00:00
TestandHiroku 1fc91a794f more pc stat displays 2025-04-19 11:04:57 +00:00
Landon f78b289171 Merge branch 'riding-dismount' into 'main'
Created the Pokemon Dismount Logic

See merge request cable-mc/cobblemon!1613
2025-04-19 10:47:16 +00:00
AramandLandon c46461c843 Created the Pokemon Dismount Logic 2025-04-19 10:47:16 +00:00
Deltric 386a897295 Adds name character length restriction to PC box name. 2025-04-19 05:46:22 -05:00
Hiroku 8328f741d8 Merge branch 'spawn-pool-observable' into 'main'
Observable in SpawnPool was not emitted on reload.

See merge request cable-mc/cobblemon!1618
2025-04-19 09:07:56 +00:00
Deltric 2b19943416 Observable in SpawnPool was not emitted on reload. 2025-04-19 03:30:50 -05:00
Paul 550b99be36 Merge remote-tracking branch 'origin/main' into lang-updates 2025-04-18 22:01:33 +10:00
Polymeta bfbee7b1d7 Merge branch 'loot-fix' into 'main'
Improved parity with vanilla mobs' drop behavior

See merge request cable-mc/cobblemon!1576
2025-04-18 07:02:45 +00:00
Test f5d71390dd Merge branch 'dex-zoom' into 'main'
A few dex scanner fixes/improvements, closes #1328

Closes #1328

See merge request cable-mc/cobblemon!1608
2025-04-18 05:06:31 +00:00
CraterandTest 6ff5d3df06 A few dex scanner fixes/improvements, closes #1328 2025-04-18 05:06:30 +00:00
gruxor 988766894a Review resolutions 2025-04-18 00:08:43 -04:00
gruxor 3d5a365a6e Added a configuration option for drops' behavior upon Pokémon death 2025-04-17 14:06:33 -04:00
gruxor 4b64b4307a Changelog update 2025-04-17 14:04:16 -04:00
gruxor 382f741db8 Improved parity with vanilla mobs' drop behavior 2025-04-17 14:04:16 -04:00
TeggyandWeblate 6d62d911d6 Translated using Weblate (Spanish)
Currently translated at 96.6% (6787 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-04-17 11:38:28 +00:00
Alexis SalgueiroandWeblate a1d40084c5 Translated using Weblate (French)
Currently translated at 92.2% (6479 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-04-17 04:04:45 +00:00
TeggyandWeblate 8b1b63e110 Translated using Weblate (Spanish)
Currently translated at 96.4% (6775 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-04-17 04:04:44 +00:00
TeggyandWeblate 9e2e95e287 Translated using Weblate (Spanish)
Currently translated at 96.0% (6744 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-04-16 09:42:42 +00:00
TeggyandWeblate 6694680c50 Translated using Weblate (Spanish)
Currently translated at 95.9% (6735 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-04-15 05:11:49 +00:00
TeggyandWeblate 8cafce0a4a Translated using Weblate (Spanish)
Currently translated at 95.7% (6724 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-04-14 10:33:04 +00:00
Watuneni iteiteandWeblate de271f81b1 Translated using Weblate (Japanese)
Currently translated at 76.4% (5365 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-04-14 06:50:20 +00:00
TeggyandWeblate feb78c5c58 Translated using Weblate (Spanish)
Currently translated at 95.3% (6698 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-04-14 06:50:19 +00:00
MiaandWeblate e5d052a6bf Translated using Weblate (German)
Currently translated at 86.7% (6090 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/de/
2025-04-12 10:41:09 +00:00
Segfault Guy 7265ab57b9 Apply species schema changes to evolutions. 2025-04-11 01:47:41 -04:00
Segfault Guy 27e3a95b06 cleanup test changes 2025-04-11 01:17:29 -04:00
Segfault Guy 38de3c6267 In-battle form changes and PokemonProperties form completion. 2025-04-09 00:18:10 -04:00
TeggyandWeblate 0ea4641536 Translated using Weblate (Spanish)
Currently translated at 95.3% (6696 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-04-08 10:41:46 +00:00
TeggyandWeblate bc4699e354 Translated using Weblate (Spanish)
Currently translated at 95.1% (6683 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-04-02 11:09:46 +00:00
TeggyandWeblate 9270119675 Translated using Weblate (Spanish)
Currently translated at 95.1% (6682 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-31 15:23:52 +00:00
TeggyandWeblate 0058906022 Translated using Weblate (Spanish)
Currently translated at 95.1% (6678 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-31 00:16:23 +00:00
KyoandWeblate 14d2a76211 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (7021 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/vi/
2025-03-27 12:41:50 +00:00
Watuneni iteiteandWeblate 7669000962 Translated using Weblate (Japanese)
Currently translated at 76.3% (5358 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-03-27 12:41:46 +00:00
TeggyandWeblate 8912911800 Translated using Weblate (Spanish)
Currently translated at 94.3% (6621 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-22 01:16:22 +00:00
阿鱼andWeblate 62ecfaf54a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (7021 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-20 23:47:02 +00:00
TeggyandWeblate 6684177236 Translated using Weblate (Spanish)
Currently translated at 94.2% (6615 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-20 23:47:00 +00:00
Watuneni iteiteandWeblate deca5cf55b Translated using Weblate (Japanese)
Currently translated at 76.1% (5350 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-03-20 23:46:59 +00:00
Segfault Guy 376e5ac906 Merge branch 'main' into form_changes 2025-03-18 22:26:00 -04:00
阿鱼andWeblate 0f75797759 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (7021 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-17 21:03:49 +00:00
Alexis SalgueiroandWeblate c194aefce5 Translated using Weblate (French)
Currently translated at 91.5% (6425 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-03-15 14:17:58 +00:00
Aiser(CCHusky)andWeblate f4798c8783 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (7021 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-15 14:17:57 +00:00
chronicenandWeblate 2178a5f10e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (7021 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-15 14:17:57 +00:00
TeggyandWeblate bc145a56d5 Translated using Weblate (Spanish)
Currently translated at 93.6% (6575 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-14 17:00:49 +00:00
Segfault Guy b225283dcc Toggleable, deferrable, and wild forms. 2025-03-12 23:00:29 -04:00
Nico MerenqandWeblate 476ef3bb2f Translated using Weblate (Russian)
Currently translated at 73.9% (5189 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-03-12 13:19:09 +00:00
Alexis SalgueiroandWeblate 460050289b Translated using Weblate (French)
Currently translated at 90.7% (6373 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-03-10 15:04:25 +00:00
Nico MerenqandWeblate 01fd4dcc68 Translated using Weblate (Russian)
Currently translated at 73.8% (5182 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-03-09 18:06:58 +00:00
Nico MerenqandWeblate 0392eb3b7e Translated using Weblate (Russian)
Currently translated at 73.6% (5172 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-03-09 15:29:58 +00:00
阿鱼andWeblate 493f423a2f Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (7021 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-09 10:54:04 +00:00
阿鱼andWeblate cbcfa1bbfb Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (7021 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-09 10:45:17 +00:00
Nico MerenqandWeblate 17a3ff15db Translated using Weblate (Russian)
Currently translated at 73.6% (5171 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-03-09 10:30:06 +00:00
阿鱼andWeblate 9c14cccc98 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.9% (7017 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-09 10:30:05 +00:00
Weblate 850cfbe755 Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/
2025-03-08 22:51:12 +00:00
TeggyandWeblate 28afa6a9e8 Translated using Weblate (Spanish)
Currently translated at 93.1% (6538 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-08 22:51:08 +00:00
阿鱼andWeblate 7a76d95ce7 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.7% (6936 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-08 22:51:07 +00:00
TeggyandWeblate 54707d0618 Translated using Weblate (Spanish)
Currently translated at 92.1% (6470 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-08 15:08:19 +00:00
Aiser(CCHusky)andWeblate 307c400e28 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.6% (6929 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-08 15:08:18 +00:00
阿鱼andWeblate 5c9b3ab25d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.6% (6929 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-08 15:08:18 +00:00
阿鱼andWeblate 532e991fb2 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.1% (6890 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-08 11:31:35 +00:00
Nico MerenqandWeblate b1bf817329 Translated using Weblate (Russian)
Currently translated at 73.6% (5171 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-03-08 11:31:34 +00:00
TeggyandWeblate c091f90487 Translated using Weblate (Spanish)
Currently translated at 91.7% (6445 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-08 08:23:13 +00:00
阿鱼andWeblate a49d4715ff Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 97.0% (6816 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-08 08:23:12 +00:00
阿鱼andWeblate 652b15532e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 96.9% (6807 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-07 04:18:30 +00:00
TeggyandWeblate e4064897b0 Translated using Weblate (Spanish)
Currently translated at 91.5% (6430 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-03-07 04:18:29 +00:00
阿鱼andWeblate f9ca8222da Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 96.7% (6793 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-06 12:30:15 +00:00
阿鱼andWeblate 67480a945f Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 95.5% (6708 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-06 06:42:04 +00:00
Alexis SalgueiroandWeblate e6e811e6e6 Translated using Weblate (French)
Currently translated at 90.0% (6320 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-03-06 06:41:58 +00:00
Watuneni iteiteandWeblate 4635c531b9 Translated using Weblate (Japanese)
Currently translated at 73.7% (5178 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-03-06 06:41:56 +00:00
Watuneni iteiteandWeblate 3bdfac9a46 Translated using Weblate (Japanese)
Currently translated at 71.7% (5039 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-03-05 13:53:19 +00:00
chronicenandWeblate 2b7a71cb1c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 91.4% (6424 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-05 13:53:19 +00:00
阿鱼andWeblate d47a500a0e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 91.4% (6424 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-05 13:53:19 +00:00
阿鱼andWeblate 33e6dbb4c1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 87.4% (6143 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-03-04 15:50:25 +00:00
Alexis SalgueiroandWeblate 482c57dd2e Translated using Weblate (French)
Currently translated at 89.1% (6259 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-03-03 19:14:47 +00:00
Alexis SalgueiroandWeblate 503c62d78d Translated using Weblate (French)
Currently translated at 87.8% (6169 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-03-03 09:30:43 +00:00
Seonho LeeandWeblate 708d0ab6d3 Translated using Weblate (Korean)
Currently translated at 70.0% (4915 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-03-03 09:30:42 +00:00
TeggyandWeblate 19727542bd Translated using Weblate (Spanish)
Currently translated at 91.4% (6424 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-28 22:02:07 +00:00
Alexis SalgueiroandWeblate c4e6714a95 Translated using Weblate (French)
Currently translated at 87.6% (6153 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-02-28 02:41:41 +00:00
TeggyandWeblate c116e612c6 Translated using Weblate (Spanish)
Currently translated at 90.4% (6354 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-28 02:41:41 +00:00
Fiestorra ISVandWeblate 215a055ce6 Translated using Weblate (Spanish)
Currently translated at 90.4% (6354 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-28 02:41:40 +00:00
Fiestorra ISVandWeblate 9c0b6b8d26 Translated using Weblate (Spanish)
Currently translated at 87.3% (6134 of 7021 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-25 22:27:22 +00:00
Fiestorra ISVandWeblate 5c4462ee94 Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-25 18:48:01 +00:00
TeggyandWeblate 04edd6f9e2 Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-25 18:48:01 +00:00
Polymeta 52821cc051 Merge branch 'main' into lang-updates 2025-02-25 18:38:33 +00:00
Segfault Guy b90f852a71 Clear invalid forms on species change. 2025-02-24 20:12:23 -05:00
Segfault Guy fdf7a9703a coalesce temp and perm forms under same property and instead deserialize by custom adapter instead of having unique properties. 2025-02-23 23:21:43 -05:00
Watuneni iteiteandWeblate 0029deb029 Translated using Weblate (Japanese)
Currently translated at 81.6% (5218 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-02-23 23:03:25 +00:00
TeggyandWeblate c6360e2a36 Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-23 23:03:24 +00:00
Fiestorra ISVandWeblate 2e77c647b6 Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-23 23:03:22 +00:00
Watuneni iteiteandWeblate c3d3b19788 Translated using Weblate (Japanese)
Currently translated at 81.3% (5200 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-02-21 08:11:08 +00:00
Fiestorra ISVandWeblate 219cd61beb Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-21 08:11:07 +00:00
TeggyandWeblate 207e5a9142 Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-19 13:03:51 +00:00
Segfault Guy bb41f28342 Merge remote-tracking branch 'origin/main' into form_changes 2025-02-18 22:33:23 -05:00
Watuneni iteiteandWeblate c721fb534d Translated using Weblate (Japanese)
Currently translated at 81.2% (5191 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-02-18 15:00:29 +00:00
iván SeguraandWeblate dba2622fd7 Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-18 15:00:28 +00:00
iván SeguraandWeblate da68675133 Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-16 12:57:27 +00:00
iván SeguraandWeblate 9aea5a7daf Translated using Weblate (Spanish)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-02-15 13:26:10 +00:00
Gustavo PuerariandWeblate 10402575fb Translated using Weblate (Portuguese (Brazil))
Currently translated at 68.3% (4369 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-02-13 23:43:36 +00:00
João Pedro Silva AfonsoandWeblate 0f82ade57c Translated using Weblate (Portuguese (Brazil))
Currently translated at 68.3% (4369 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-02-13 23:43:35 +00:00
Segfault Guy c5ee7ebe7a Fix ability registration for forms. 2025-02-11 21:40:35 -05:00
Gustavo PuerariandWeblate 450b041118 Translated using Weblate (Portuguese (Brazil))
Currently translated at 65.1% (4166 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-02-11 21:12:32 +00:00
Weblate 770c349ac4 Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/
2025-02-10 00:41:50 +00:00
Gustavo PuerariandWeblate f76648b6f2 Translated using Weblate (Portuguese (Brazil))
Currently translated at 59.8% (3826 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-02-10 00:41:46 +00:00
Gustavo PuerariandWeblate 56e60667db Translated using Weblate (Portuguese (Brazil))
Currently translated at 51.5% (3297 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-02-07 00:29:47 +00:00
Gustavo PuerariandWeblate c4a60951c5 Translated using Weblate (Portuguese (Brazil))
Currently translated at 49.7% (3178 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-02-05 07:22:58 +00:00
Segfault Guy f6ae10e1d9 It's about f(orm)cking time. 2025-02-03 23:38:53 -05:00
KyoandWeblate 814eee8125 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (6391 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/vi/
2025-02-03 09:44:44 +00:00
Gustavo PuerariandWeblate 2a9ff3bfbd Translated using Weblate (Portuguese (Brazil))
Currently translated at 47.9% (3064 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-02-03 09:44:43 +00:00
Weblate 83350cb0db Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/
2025-01-31 22:57:44 +00:00
Patch Notes GuyandWeblate 778761dbee Translated using Weblate (Vietnamese)
Currently translated at 99.1% (6335 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/vi/
2025-01-31 22:57:39 +00:00
Gustavo PuerariandWeblate e9881b748a Translated using Weblate (Portuguese (Brazil))
Currently translated at 46.4% (2971 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-01-31 22:57:25 +00:00
Weblate dabaeefa23 Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/
2025-01-31 09:41:51 +00:00
Patch Notes GuyandWeblate 0e0bec2712 Added translation using Weblate (Vietnamese) 2025-01-31 09:41:47 +00:00
Gustavo PuerariandWeblate 48fe160334 Translated using Weblate (Portuguese (Brazil))
Currently translated at 45.1% (2887 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-01-31 03:10:48 +00:00
Gustavo PuerariandWeblate 7d599e12d0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 42.7% (2731 of 6391 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-01-27 23:30:11 +00:00
Segfault GuyandSegfault Guy b2070b53fb Changing form... changes form? Wow! 2024-12-03 23:41:17 -05:00
Segfault GuyandSegfault Guy 7fb873dc2c Showdown Form and Species registration rework. 2024-12-03 23:10:25 -05:00
Segfault GuyandSegfault Guy 641f529de3 Better Form and Evolution triggering. 2024-12-03 19:12:41 -05:00
Segfault GuyandSegfault Guy db9fb4fefa Refactor Evolution and add Transformation to expand application of Evolution events and requirements. 2024-12-02 23:13:57 -05:00
713 changed files with 18263 additions and 5452 deletions
+1
View File
@@ -6,6 +6,7 @@
- Added filter functionality in PC UI, which supports `PokemonProperties` (e.g. `shiny=yes` shows all shiny Pokémon).
- Added `/changewallpaper <player> <boxNumber> <wallpaper>` command to change a box wallpaper through commands.
- Added `/renamebox <player> <boxNumber> <name>` command to rename a PC box through commands.
- Added IVs and EVs stat displays in PC.
### Developer
- Added `RenamePCBoxEvent.Pre` and `RenamePCBoxEvent.Post` events to prevent players from renaming a box or changing their input.
+7
View File
@@ -19,6 +19,8 @@
- The player executing the command must be facing the target pokemon entity. Transformations are not persistent and will revert when resources are reloaded.
- Added lang keys for all moves and abilities up to Generation 9.
- Fixed Moon Ball moon phase logic to actually work correctly
- Added `translucent_cull` boolean option into resolver's layer to allow for translucent textures with culling
- Added [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) support for items held by Pokémon.
### Pokémon Added
@@ -127,6 +129,10 @@
- Shulker shell Forretress is now a cosmetic rather than a special evo and thus all shulker Forretress will revert back to normal until a shulker shell is put in their cosmetic slot.
- Updated `doPokemonSpawning` gamerule to support per-dimension configurations.
- The Pokedex now displays a form name of a "normal" Pokémon for when the base form is still a named form.
- Improved the zoom functionality of the Pokédex Scanner by giving the levels logarithmic scaling.
- Added a subtle rotation effect to the Pokédex Scanner's wheel when zooming.
- Improved parity with vanilla mobs' drop behavior; loot and XP drop on death instead of after the entire death sequence finishes.
- Quirk animations no longer play in the battle GUI since they were pretty distracting.
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
@@ -156,6 +162,7 @@
- Fixed an issue with newer versions of Fabric API where underground Pokémon were spawning in The End.
- Fixed spawning not working well when you're at high points surrounded by lower altitude spawning areas, such as flying.
- Fixed certain Pokémon with forms not having appropriate stock Pokédex entries.
- Fixed issue with Pokédex Scanner that caused the open/close overlay to have the wrong opacity values
- Fixed dragon's breath not being usable on the restoration tank when it should be
### Developer
@@ -13,10 +13,12 @@ import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokemon.FormData;
import com.cobblemon.mod.common.pokemon.lighthing.LightingData;
import dev.lambdaurora.lambdynlights.LambDynLights;
import dev.lambdaurora.lambdynlights.api.DynamicLightHandler;
import dev.lambdaurora.lambdynlights.api.DynamicLightHandlers;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -53,15 +55,19 @@ public class LambDynamicLightsCompat {
private static int resolvedPokemonLightLevel(PokemonEntity pokemon) {
var underwater = !pokemon.level().getFluidState(BlockPos.containing(pokemon.getX(), pokemon.getEyeY(), pokemon.getZ())).isEmpty();
return extractFormLightLevel(pokemon.getForm(), underwater).orElse(0);
ItemStack item = pokemon.getShownItem();
int itemLightLevel = item.isEmpty()?0:LambDynLights.getLuminanceFromItemStack(item, underwater);
int formLightLevel = extractFormLightLevel(pokemon.getForm(), underwater).orElse(0);
return Math.max(itemLightLevel, formLightLevel);
}
private static int resolvedShoulderLightLevel(Player player) {
final var shoulderDataPair = PokemonOnShoulderRenderer.shoulderDataOf(player);
var underwater = !player.level().getFluidState(BlockPos.containing(player.getX(), player.getEyeY(), player.getZ())).isEmpty();
final Optional<Integer> leftLightLevel = extractShoulderLightLevel(shoulderDataPair.getFirst(), underwater);
final Optional<Integer> rightLightLevel = extractShoulderLightLevel(shoulderDataPair.getSecond(), underwater);
return Math.max(leftLightLevel.orElse(0), rightLightLevel.orElse(0));
final int leftLightLevel = extractShoulderLightLevel(PokemonOnShoulderRenderer.shoulderDataFrom(player.getShoulderEntityLeft()), underwater);
final int rightLightLevel = extractShoulderLightLevel(PokemonOnShoulderRenderer.shoulderDataFrom(player.getShoulderEntityRight()), underwater);
return Math.max(leftLightLevel, rightLightLevel);
}
private static Optional<Integer> extractFormLightLevel(@NotNull FormData form, boolean underwater) {
@@ -71,11 +77,16 @@ public class LambDynamicLightsCompat {
return Optional.of(form.getLightingData().getLightLevel());
}
private static Optional<Integer> extractShoulderLightLevel(@Nullable PokemonOnShoulderRenderer.ShoulderData shoulderData, boolean underwater) {
private static int extractShoulderLightLevel(@Nullable PokemonOnShoulderRenderer.ShoulderData shoulderData, boolean underwater) {
if (shoulderData == null) {
return Optional.empty();
return 0;
}
return extractFormLightLevel(shoulderData.getForm(), underwater);
ItemStack item = shoulderData.getShownItem();
int itemLightLevel = item.isEmpty()?0:LambDynLights.getLuminanceFromItemStack(item, underwater);
int formLightLevel = extractFormLightLevel(shoulderData.getForm(), underwater).orElse(0);
return Math.max(itemLightLevel, formLightLevel);
}
private static boolean liquidGlowModeSupport(@NotNull LightingData.LiquidGlowMode liquidGlowMode, boolean underwater) {
@@ -86,9 +86,9 @@ import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.aspects.COSMETIC_SLOT_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.FORM_ASPECTS
import com.cobblemon.mod.common.pokemon.aspects.GENDER_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
@@ -100,6 +100,7 @@ import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.pokemon.transformation.triggers.BlockClickTrigger
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
@@ -254,13 +255,8 @@ object Cobblemon {
PlatformEvents.RIGHT_CLICK_BLOCK.subscribe { event ->
val player = event.player
val block = player.level().getBlockState(event.pos).block
player.party().forEach { pokemon ->
pokemon.lockedEvolutions
.filterIsInstance<BlockClickEvolution>()
.forEach { evolution ->
evolution.attemptEvolution(pokemon, BlockClickEvolution.BlockInteractionContext(block, player.level()))
}
}
val context = BlockClickTrigger.Context(block, player.level())
player.party().forEach { pokemon -> pokemon.triggerTransformations(BlockClickTrigger::class.java, context) }
}
PlatformEvents.CHANGE_DIMENSION.subscribe {
@@ -301,6 +297,7 @@ object Cobblemon {
SHINY_ASPECT.register()
GENDER_ASPECT.register()
FORM_ASPECTS.register()
COSMETIC_SLOT_ASPECT.register()
SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java
@@ -170,9 +170,6 @@ import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptanceP
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.PerformTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
@@ -211,6 +208,12 @@ import com.cobblemon.mod.common.net.serverhandling.storage.SwapPCPartyPokemonHan
import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.AddFormPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.ClearFormsPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.RemoveFormPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.AcceptFormPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.RevertFormPacket
import com.cobblemon.mod.common.net.serverhandling.*
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
@@ -430,6 +433,11 @@ object CobblemonNetwork {
// Pokédex scanning
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
// Form packets
list.add(PacketRegisterInfo(AddFormPacket.ID, AddFormPacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(ClearFormsPacket.ID, ClearFormsPacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RemoveFormPacket.ID, RemoveFormPacket::decode, PokemonUpdatePacketHandler()))
// Debug / cheats
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
@@ -533,6 +541,10 @@ object CobblemonNetwork {
// NPC packets
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
// Form Packets
list.add(PacketRegisterInfo(AcceptFormPacket.ID, AcceptFormPacket::decode, AcceptFormHandler))
list.add(PacketRegisterInfo(RevertFormPacket.ID, RevertFormPacket::decode, RevertFormHandler))
// Riding packet(s)
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, OrientationPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.abilities
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.google.gson.JsonObject
import com.mojang.serialization.Codec
@@ -25,7 +26,7 @@ class AbilityTemplate(
var builder: (AbilityTemplate, forced: Boolean, priority: Priority) -> Ability = { template, forced, priority -> Ability(template, forced, priority) },
val displayName: String = "cobblemon.ability.$name",
val description: String = "cobblemon.ability.$name.desc"
) {
) : ShowdownIdentifiable {
/**
* Returns the Ability or if applicable the extension connected to this template
*/
@@ -45,6 +46,8 @@ class AbilityTemplate(
*/
fun create(json: JsonObject) = create().loadFromJSON(json)
override fun showdownId(): String = ShowdownIdentifiable.REGEX.replace(this.name.lowercase(), "")
companion object {
@JvmStatic
@@ -48,9 +48,9 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
import com.cobblemon.mod.common.pokemon.transformation.progress.DefeatTransformationProgress
import com.cobblemon.mod.common.pokemon.transformation.progress.LastBattleCriticalHitsTransformationProgress
import com.cobblemon.mod.common.pokemon.transformation.requirements.DefeatRequirement
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.giveOrDropItemStack
@@ -77,7 +77,7 @@ open class PokemonBattle(
val side2: BattleSide
) {
/** Whether logging will be silenced for this battle. */
var mute = true
var mute = false
val struct = this.asMoLangValue()
val runtime = MoLangRuntime().also { it.environment.query = struct }
@@ -92,7 +92,7 @@ open class PokemonBattle(
actor.battle = this
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.filterIsInstance<LastBattleCriticalHitsTransformationProgress>()
.forEach { it.reset() }
}
}
@@ -252,11 +252,12 @@ open class PokemonBattle(
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
val pokemon = opponentPokemon.effectedPokemon
if (facedFainted) {
// TODO refactor and apply to forms
pokemon.lockedEvolutions.forEach { evolution ->
evolution.requirements.filterIsInstance<DefeatRequirement>().forEach { defeatRequirement ->
if (defeatRequirement.target.matches(faintedPokemon.effectedPokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DefeatEvolutionProgress && it.currentProgress().target == defeatRequirement.target }) { DefeatEvolutionProgress() }
progress.updateProgress(DefeatEvolutionProgress.Progress(defeatRequirement.target, progress.currentProgress().amount + 1))
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DefeatTransformationProgress && it.currentProgress().target == defeatRequirement.target }) { DefeatTransformationProgress() }
progress.updateProgress(DefeatTransformationProgress.Progress(defeatRequirement.target, progress.currentProgress().amount + 1))
}
}
}
@@ -8,11 +8,11 @@
package com.cobblemon.mod.common.api.drop
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.pokemon.Pokemon
class EvolutionItemDropEntry : ItemDropEntry() {
val requirements: MutableSet<EvolutionRequirement> = mutableSetOf()
val requirements: MutableSet<TransformationRequirement> = mutableSetOf()
override fun canDrop(pokemon: Pokemon?): Boolean = pokemon != null && requirements.all { it.check(pokemon) }
}
@@ -12,8 +12,8 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
/**
* Fired when an evolution is accepted.
@@ -10,8 +10,8 @@ package com.cobblemon.mod.common.api.events.pokemon.evolution
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
/**
* Fired after an evolution finishes.
@@ -8,9 +8,9 @@
package com.cobblemon.mod.common.api.events.pokemon.evolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.EvolutionDisplay
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.evolution.EvolutionDisplay
/**
* Fired before an evolution is sent to clients.
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.api.events.pokemon.evolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
/**
@@ -12,8 +12,8 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.util.getBoolean
/**
@@ -14,7 +14,7 @@ import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.FossilRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.transformation.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.ItemLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
@@ -215,6 +215,7 @@ fun drawPosablePortrait(
ageInTicks: Float = 0F,
headYaw: Float = 0F,
headPitch: Float = 0F,
doQuirks: Boolean = true,
r: Float = 1F,
g: Float = 1F,
b: Float = 1F,
@@ -240,6 +241,7 @@ fun drawPosablePortrait(
context.put(RenderContext.SPECIES, identifier)
context.put(RenderContext.ASPECTS, state.currentAspects)
context.put(RenderContext.POSABLE_STATE, state)
context.put(RenderContext.DO_QUIRKS, doQuirks)
val renderType = RenderType.entityCutout(texture)
@@ -74,7 +74,7 @@ abstract class RequestManager<T : ServerPlayerActionRequest> {
?: this.getInboundRequest(receiver.uuid, requestID)
/** Determines whether [player] can receive a [T]. */
open fun isBusy(player: ServerPlayer): Boolean = player.isInBattle() || player.isTrading() || player.party().find { it.entity?.isBusy == true } != null
open fun isBusy(player: ServerPlayer): Boolean = player.isBusy() || player.isPartyBusy()
/** Determines if the [target] is a valid interaction request target. */
abstract fun isValidInteraction(player: ServerPlayer, target: ServerPlayer): Boolean
@@ -29,7 +29,6 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.pokedex.*
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
@@ -57,9 +56,10 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticl
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.pokemon.transformation.triggers.ItemInteractionTrigger
import com.cobblemon.mod.common.pokemon.transformation.triggers.LevelUpTrigger
import com.cobblemon.mod.common.pokemon.transformation.triggers.TradeTrigger
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import net.minecraft.commands.arguments.EntityAnchorArgument
@@ -1180,16 +1180,15 @@ object MoLangFunctions {
map.put("result") { evolution.result.asMoLangValue() }
if (evolution is LevelUpEvolution) {
if (evolution.trigger is LevelUpTrigger) {
map.put("is_level_up") { DoubleValue.ONE }
} else if (evolution is TradeEvolution) {
} else if (evolution.trigger is TradeTrigger) {
map.put("is_trade") { DoubleValue.ONE }
} else if (evolution is ItemInteractionEvolution) {
} else if (evolution.trigger is ItemInteractionTrigger) {
map.put("is_item") { DoubleValue.ONE }
}
map.put("is_optional") { DoubleValue(evolution.optional) }
map.put("consumes_held_item") { DoubleValue(evolution.consumeHeldItem) }
map
}
@@ -12,6 +12,7 @@ import com.bedrockk.molang.runtime.struct.MoStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
@@ -56,7 +57,7 @@ open class MoveTemplate(
val critRatio: Double,
val effectChances: Array<Double>,
val actionEffect: ActionEffectTimeline?
) {
) : ShowdownIdentifiable {
val struct: MoStruct by lazy {
QueryStruct(hashMapOf())
.addFunction("name") { StringValue(name) }
@@ -192,4 +193,6 @@ open class MoveTemplate(
}
return this.elementalType
}
override fun showdownId() = ShowdownIdentifiable.REGEX.replace(this.name.lowercase(), "")
}
@@ -466,29 +466,18 @@ open class PokemonProperties {
}
fun matches(properties: PokemonProperties): Boolean {
return properties.asString() == this.asString()
// Custom properties check could be duplicated but can't assume 3rd party hasn't done anything type specific
return this.commonMatches(properties) && this.customProperties.containsAll(properties.customProperties)
}
// Undefined properties will match whatever they're compared with
private fun commonMatches(pokemon: Pokemon): Boolean {
level?.takeIf { it != pokemon.level }?.let { return false }
shiny?.takeIf { it != pokemon.shiny }?.let { return false }
gender?.takeIf { it != pokemon.gender }?.let { return false }
species?.run {
try {
val species = if (this == "random") {
PokemonSpecies.species.random()
} else {
PokemonSpecies.getByIdentifier(this.asIdentifierDefaultingNamespace()) ?: return@run
}
if (pokemon.species != species) {
return false
}
} catch (e: ResourceLocationException) {
return false
}
}
species?.takeIf { it.asSpecies() != pokemon.species }?.let { return false }
nickname?.takeIf { it.string != pokemon.nickname?.string }?.let { return false }
form?.takeIf { !it.equals(pokemon.form.name, true) }?.let { return false }
form?.takeIf { !it.equals(pokemon.form.formOnlyShowdownId(), true) }?.let { return false }
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false }
@@ -527,6 +516,36 @@ open class PokemonProperties {
return true
}
// Undefined properties will match whatever they're compared with
private fun commonMatches(properties: PokemonProperties): Boolean {
level?.takeIf { it != properties.level }?.let { return false }
shiny?.takeIf { it != properties.shiny }?.let { return false }
gender?.takeIf { it != properties.gender }?.let { return false }
species?.takeIf { it.asSpecies() != properties.species?.asSpecies() }?.let { return false }
nickname?.takeIf { it.string != properties.nickname?.string }?.let { return false }
form?.takeIf { it != properties.form }?.let { return false }
friendship?.takeIf { it != properties.friendship }?.let { return false }
pokeball?.takeIf { it != properties.pokeball }?.let { return false }
nature?.takeIf { it != properties.nature }?.let { return false }
ability?.takeIf { it != properties.ability }?.let { return false }
status?.takeIf { it != properties.status }?.let { return false }
ivs?.forEach{ stat ->
if (stat.value != properties.ivs?.get(stat.key)) { return false }
}
evs?.forEach{ stat ->
if (stat.value != properties.evs?.get(stat.key)) { return false }
}
type?.takeIf { it != properties.type }?.let { return false }
teraType?.takeIf { it != properties.teraType }?.let { return false }
dmaxLevel?.takeIf { it != properties.dmaxLevel }?.let { return false }
gmaxFactor?.takeIf { it != properties.gmaxFactor }?.let { return false }
tradeable?.takeIf { it != properties.tradeable }?.let { return false }
originalTrainer?.takeIf { it != properties.originalTrainer }?.let{ return false }
originalTrainerType?.takeIf { it != properties.originalTrainerType }?.let{ return false }
moves?.takeIf { it.any { move -> properties.moves?.none { it == move } == true } }?.let { return false }
return true
}
// TODO Review the need for this, usage for us is in commands that deviate from the behavior established by multiple Pokémon query equivalent
fun isSubSetOf(properties: PokemonProperties): Boolean {
level?.takeIf { it != properties.level }?.let { return false }
@@ -798,4 +817,15 @@ open class PokemonProperties {
return potentialAbility.template.create(false, potentialAbility.priority)
}
private fun String.asSpecies() =
try {
if (this == "random") {
PokemonSpecies.species.random()
} else {
PokemonSpecies.getByIdentifier(this.asIdentifierDefaultingNamespace())
}
}
catch (e: ResourceLocationException) {
null
}
}
@@ -12,6 +12,8 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.abilities.AbilityTemplate
import com.cobblemon.mod.common.api.abilities.CommonAbility
import com.cobblemon.mod.common.api.abilities.PotentialAbility
import com.cobblemon.mod.common.api.ai.SleepDepth
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
@@ -24,14 +26,15 @@ import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.PreEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.transformation.evolution.PreEvolution
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroup
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroupAdapter
import com.cobblemon.mod.common.api.pokemon.moves.Learnset
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
@@ -42,11 +45,16 @@ import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPack
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.SpeciesAdditions
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonPreEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.pokemon.abilities.SpecialAbility
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.transformation.adapters.*
import com.cobblemon.mod.common.pokemon.transformation.form.GigantamaxForm
import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm
import com.cobblemon.mod.common.pokemon.transformation.form.StandardForm
import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm
import com.cobblemon.mod.common.pokemon.transformation.requirements.HeldItemRequirement
import com.cobblemon.mod.common.pokemon.transformation.requirements.MoveSetRequirement
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import com.cobblemon.mod.common.util.cobblemonResource
@@ -69,6 +77,7 @@ import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.phys.AABB
import java.util.NoSuchElementException
object PokemonSpecies : JsonDataRegistry<Species> {
@@ -84,10 +93,8 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(ExperienceGroup::class.java, ExperienceGroupAdapter)
.registerTypeAdapter(EntityDimensions::class.java, EntityDimensionsAdapter)
.registerTypeAdapter(Learnset::class.java, LearnsetAdapter)
.registerTypeAdapter(Evolution::class.java, CobblemonEvolutionAdapter)
.registerTypeAdapter(AABB::class.java, BoxAdapter)
.registerTypeAdapter(AbilityPool::class.java, AbilityPoolAdapter)
.registerTypeAdapter(EvolutionRequirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(PreEvolution::class.java, CobblemonPreEvolutionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(Set::class.java, Evolution::class.java).type, LazySetAdapter(Evolution::class))
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
@@ -106,7 +113,10 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(TransformationTrigger::class.java, CobblemonTriggerAdapter)
.registerTypeAdapter(TransformationRequirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(FormData::class.java, FormAdapter)
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
@@ -126,6 +136,9 @@ object PokemonSpecies : JsonDataRegistry<Species> {
get() = this.speciesByIdentifier.values
val implemented = mutableListOf<Species>()
val forms: Collection<FormData>
get() = this.species.flatMap { it.allForms }
init {
SpeciesAdditions.observable.subscribe {
this.species.forEach(Species::initialize)
@@ -225,34 +238,18 @@ object PokemonSpecies : JsonDataRegistry<Species> {
}
/**
* The representation of a [Species] and/or [FormData] in the context of showdown.
* The representation of [FormData] in the context of Showdown.
* This is intended as a sort of DTO that can be easily converted between JSON and Java/JS objects.
*
* @param species The [Species] being converted or the base species if the form is not null.
* @param form The [FormData] being converted int o species (Showdown considers them species) will be null when dealing with the base form.
* @param form The [FormData] being converted into a species (Showdown considers them species).
*/
@Suppress("unused")
internal class ShowdownSpecies(species: Species, form: FormData?) {
val num = species.nationalPokedexNumber
val name = if (form != null) "${createShowdownName(species)}-${form.name}" else createShowdownName(species)
val baseSpecies = if (form != null) createShowdownName(species) else this.name
val forme = form?.name
// ToDo baseForme
val otherFormes = if (form == null && species.forms.isNotEmpty()) species.forms.map { "${this.name}-${it.name}" } else emptyList()
val formeOrder = if (form == null && this.otherFormes.isNotEmpty()) arrayListOf(this.name, *this.otherFormes.toTypedArray()) else emptyList()
val abilities: Map<String, String> = mapOf(
"0" to "No Ability",
"1" to "No Ability",
"H" to "No Ability",
"S" to "No Ability"
)
val types = (form?.types ?: species.types).map { it.name.replaceFirstChar(Char::uppercase) }
val preevo: String? = (form?.preEvolution ?: species.preEvolution)?.let { if (it.form == it.species.standardForm) createShowdownName(it.species) else "${createShowdownName(it.species)}-${it.form.name}" }
// For the context of battles the content here doesn't matter whatsoever and due to how PokemonProperties work we can't guarantee an actual specific species is defined.
val evos = if ((form?.evolutions ?: species.evolutions).isEmpty()) emptyList() else arrayListOf("")
val nfe = this.evos.isNotEmpty()
val eggGroups = (form?.eggGroups ?: species.eggGroups).map { it.showdownID }
val gender: String? = when (form?.maleRatio ?: species.maleRatio) {
internal open class ShowdownSpecies(form: FormData) {
val num = form.species.nationalPokedexNumber
val name = form.showdownName()
val types = form.types.map { it.name.replaceFirstChar(Char::uppercase) }
val eggGroups = form.eggGroups.map { it.showdownID }
val gender: String? = when (form.maleRatio) {
0F -> "F"
1F -> "M"
-1F, 1.125F -> "N"
@@ -260,35 +257,132 @@ object PokemonSpecies : JsonDataRegistry<Species> {
}
val genderRatio = if (this.gender == null)
mapOf(
"maleRatio" to (form?.maleRatio ?: species.maleRatio),
"femaleRation" to (1F - (form?.maleRatio ?: species.maleRatio))
"M" to (form.maleRatio),
"F" to (1F - (form.maleRatio))
) else null
val baseStats = mapOf(
"hp" to (form?.baseStats?.get(Stats.HP) ?: species.baseStats[Stats.HP] ?: 1),
"atk" to (form?.baseStats?.get(Stats.ATTACK) ?: species.baseStats[Stats.ATTACK] ?: 1),
"def" to (form?.baseStats?.get(Stats.DEFENCE) ?: species.baseStats[Stats.DEFENCE] ?: 1),
"spa" to (form?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: species.baseStats[Stats.SPECIAL_ATTACK] ?: 1),
"spd" to (form?.baseStats?.get(Stats.SPECIAL_DEFENCE) ?: species.baseStats[Stats.SPECIAL_DEFENCE] ?: 1),
"spe" to (form?.baseStats?.get(Stats.SPEED) ?: species.baseStats[Stats.SPEED] ?: 1)
)
val heightm = (form?.height ?: species.height) / 10
val weightkg = (form?.weight ?: species.weight) / 10
val baseStats = Stats.PERMANENT.associateBy({ it.showdownId }, { form.baseStats[it] ?: 1 })
val heightm = form.height / 10
val weightkg = form.weight / 10
// This is ugly, but we already have it hardcoded in the mod anyway
val maxHP = if (species.showdownId() == "shedinja") 1 else null
val canGigantamax: String? = if (form?.gigantamaxMove != null) form.gigantamaxMove.name else null
val cannotDynamax = form?.dynamaxBlocked ?: species.dynamaxBlocked
// ToDo battleOnly
// ToDo changesFrom
val requiredMove = form?.requiredMove
val requiredItem = form?.requiredItem
val requiredItems = form?.requiredItems
val maxHP = if (this.num == 292) 1 else null
val cannotDynamax = form.dynamaxBlocked
val abilities = form.showdownAbilityMapping()
// Common fields exempt from standard forms
val baseSpecies = if (form !is StandardForm) form.species.standardForm.showdownName() else null
val forme = if (form !is StandardForm) form.name else null
/**
* The following fields are optional and currently omitted
*
* cosmeticFormes (not needed, we have species features)
* addedType (tertiary type [by name or showdownid] added by moves or abilities)
* tier (smogon tryhard bs)
* doublesTier (smogon tryhard bs)
* natDexTier (smogon tryhard bs)
* evoType (the evolution method used to evolve from 'prevo'; trade', 'useItem', 'levelMove', 'levelExtra', 'levelFriendship', 'levelHold', 'other')
* evoMove (the move [by name or showdownid] used to evolve from 'prevo')
* evoLevel (the minimum level required to evolve from 'prevo')
* canHatch (flag for baby pokemon)
* color (color, value is arbitrary)
* tags (flags for filtering pokemon; 'Mega', 'Mythical', 'Sub-Legendary', 'Restricted Legendary', 'Paradox')
* unreleasedHidden (whether this pokemon has an unreleased hidden ability)
* maleOnlyHidden (whether it is only possible to get the hidden ability on a male pokemon)
* gmaxUnreleased (whether this pokemon has an unreleased gmax form)
* pokemonGoData (no one cares)
*/
}
private fun createShowdownName(species: Species): String {
if (species.resourceIdentifier.namespace == Cobblemon.MODID) {
return species.name
/**
* The representation of a [PermanentForm] in the context of Showdown.
*
* @param form The [PermanentForm] being converted into a species.
*/
internal open class ShowdownPermanentForm(form: PermanentForm) : ShowdownSpecies(form) {
// species this form evolves from
val prevo = form.preEvolution?.form?.showdownName()
// For the context of battles the content here doesn't matter whatsoever and due to how PokemonProperties work we can't guarantee an actual specific species is defined.
val evos = form.evolutions.map { "" }
// not fully evolved
val nfe = this.evos.isNotEmpty()
// whether there is a gmax form to change to
val canGigantamax: String? = form.changeableForms.filterIsInstance<GigantamaxForm>().firstOrNull()?.name // TODO id better
}
/**
* The representation of a [StandardForm] in the context of Showdown.
*
* @param form The [StandardForm] being converted into a species.
*/
internal class ShowdownStandardForm(form: StandardForm) : ShowdownPermanentForm(form) {
// list of all forms descending from this base species
val otherFormes = form.flattenForms().map { it.showdownName() }
// list of forms [by name or showdownid] in the order they appear in game [union of baseSpecies, otherFormes, and cosmeticFormes] with the exception of ending with 'Gmax' forms
val formeOrder = if (this.otherFormes.isNotEmpty()) arrayListOf(this.name, *this.otherFormes.toTypedArray()) else null
// TODO we don't enforce any form order with our Species registration - right now this looks like only a problem with Mega Rayquaza, but investigate changes needed on Showdown's end to enable these lists to be more dynamic
}
/**
* The representation of a [TemporaryForm] in the context of Showdown.
*
* @param form The [TemporaryForm] being converted into a species.
*/
internal class ShowdownTemporaryForm(form: TemporaryForm) : ShowdownSpecies(form) {
// form change can occur during a battle
val battleOnly = if (form.battleOnly) form.parentForm.showdownName() else null
// form change can occur outside of battle (mutually exclusive with battleOnly)
val changesFrom = if (!form.battleOnly) form.parentForm.showdownName() else null
// pokemon must know this move to be in this form
val requiredMove = form.requirements.filterIsInstance<MoveSetRequirement>().firstOrNull()?.move?.showdownId() // TODO update showdown to support 'requiredMoves'
// pokemon must hold one of these item(s) to be in this form
val requiredItems = form.requirements.filterIsInstance<HeldItemRequirement>().firstOrNull()?.let { requirement ->
try {
requirement.itemCondition.items.get().map { BuiltInRegistries.ITEM.getKey(it.value()).path } // TODO held items on showdown are ignorant of namespace
}
catch (e: NoSuchElementException)
{
Cobblemon.LOGGER.error("Failed to resolve required held item for ${form.showdownId()}}") // TODO this needs to be done after tags are loaded...
null
}
}
return "${species.resourceIdentifier.namespace}:${species.name}"
}
/** Accommodate for Showdown bullshit. */
/*
private fun TemporaryForm.showdownParentForm(): Any {
if (this.parentOverrides.isNotEmpty())
return this.species.standardForm.flattenForms().filter {
parentOverrides.contains(it.showdownId())
}.map {
it.showdownName()
}
else
return parentForm.showdownName()
}
*/
/**
* Constructs a mapping of the [PotentialAbility]s belonging to a [FormData] in Showdown identifiable format.
*
* @return A map of type (numeric for [CommonAbility]s, H for [HiddenAbility], S for [SpecialAbility]) to showdownid.
*/
private fun FormData.showdownAbilityMapping(): Map<String, String>? {
// potential abilities
val commonAbilities = this.abilities.filterIsInstance<CommonAbility>()
val hiddenAbility = this.abilities.find { it is HiddenAbility }
val specialAbility = this.abilities.find { it is SpecialAbility }
// can have multiple common abilities
val abilities = mutableMapOf<String, String>()
val mapping: (String, PotentialAbility) -> Unit = { key, ability ->
abilities.put(key, ability.template.showdownId())
}
// can only have one hidden and one special ability
commonAbilities.forEachIndexed { index, ability -> mapping(index.toString(), ability) }
hiddenAbility?.let { ability -> mapping("H", ability) }
specialAbility?.let { ability -> mapping("S", ability) }
return if (abilities.isEmpty()) null else abilities
}
}
@@ -0,0 +1,24 @@
package com.cobblemon.mod.common.api.pokemon.aspect
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* A provider of aspects for a [SpeciesFeature].
*
* @author Segfault Guy
* @since Feb 12th, 2024
*/
interface FeatureAspectProvider: AspectProvider {
var default: String?
var isAspect: Boolean
/** Returns true if this [aspect] is provided by this provider. */
fun matches(aspect: String): Boolean
/** Creates a [SpeciesFeature] that provides the respective [aspect]. */
fun from(aspect: String): SpeciesFeature<*>?
/** Sets the [aspect] that's provided. */
fun set(pokemon: Pokemon, aspect: String)
}
@@ -24,11 +24,11 @@ interface SingleConditionalAspectProvider : AspectProvider {
fun getForFeature(name: String): SingleConditionalAspectProvider {
return object : SingleConditionalAspectProvider {
override val aspect: String = name
override fun meetsCondition(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled == true
override fun meetsCondition(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.value == true
override fun meetsCondition(pokemonProperties: PokemonProperties) = pokemonProperties
.customProperties
.filterIsInstance<FlagSpeciesFeature>()
.any { it.name == name && it.enabled }
.any { it.name == name && it.value }
}
}
@@ -1,54 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
/**
* Represents an evolution of a [Pokemon] that can only occur during specific actions and with added context.
* For the default implementations see [ItemInteractionEvolution] & [TradeEvolution].
*
* @param RC The context given at runtime when querying the [Evolution].
* @param TC The context that is serialized from JSON during species loading, this is what the [RC] is expected to match against.
* @author Licious
* @since March 19th, 2022
*/
interface ContextEvolution<RC, TC> : Evolution {
/**
* The target context for this [Evolution] to even be tested.
*/
val requiredContext: TC
/**
* Attempts to evolve the given [Pokemon] under the given context of type [RC].
*
* @param pokemon The [Pokemon] attempting to evolve.
* @param context The context of this query.
* @return If the evolution was successful.
*/
fun attemptEvolution(pokemon: Pokemon, context: RC): Boolean {
if (this.testContext(pokemon, context) && super.test(pokemon)) {
return super.evolve(pokemon)
}
return false
}
/**
* Checks if the given context is valid for the [requiredContext].
*
* @param pokemon The [Pokemon] attempting to evolve.
* @param context The context of this query.
* @return If the context matched the [requiredContext].
*/
fun testContext(pokemon: Pokemon, context: RC): Boolean
}
@@ -1,69 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution
import com.cobblemon.mod.common.api.pokemon.evolution.progress.EvolutionProgress
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.evolution.controller.ClientEvolutionController
import com.cobblemon.mod.common.pokemon.evolution.controller.ServerEvolutionController
/**
* Responsible for holding all available [EvolutionLike]s in the [Pokemon].
* Also handles all the networking behind them.
* For the Cobblemon default implementations see [ClientEvolutionController] & [ServerEvolutionController].
*
* @author Licious
* @since April 28th, 2022
*/
interface EvolutionController<T : EvolutionLike, out I : PreProcessor> : MutableSet<T> {
/**
* Resolves the [Pokemon] attached to this controller.
*
* @return The [Pokemon] this controller is attached to.
*/
fun pokemon(): Pokemon
/**
* Starts the given evolution on this controller.
* All the necessary networking will be handled.
*
* @param evolution The [EvolutionLike] starting.
*/
fun start(evolution: T)
/**
* Returns an immutable collection of [EvolutionProgress]es attached to this controller.
*
* @return The currently tracked [EvolutionProgress]es.
*/
fun progress(): Collection<EvolutionProgress<*>>
/**
* Attaches a [EvolutionProgress] to this controller.
*
* @param P The type of [EvolutionProgress].
* @param progress The [EvolutionProgress] being attached for tracking.
* @return The instance being tracked.
*/
fun <P : EvolutionProgress<*>> trackProgress(progress: P): P
/**
* Attempts to find an [EvolutionProgress] in [progress] or creates and tracks using [trackProgress].
*
* @param P The type of [EvolutionProgress].
* @param predicate The lambda attempting to find specific progress. This needs to ensure the [EvolutionProgress] is of type [P] or the cast will fail.
* @param progressFactory The lambda creating a new instance of type [P] if [predicate] can't find an element.
* @return The found match in [progress] or the created and now tracked with [trackProgress].
*/
fun <P : EvolutionProgress<*>> progressFirstOrCreate(predicate: (progress: EvolutionProgress<*>) -> Boolean, progressFactory: () -> P): P
fun asIntermediate(): I
}
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.evolution.CobblemonEvolutionDisplay
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
/**
* Represents an evolution of a [Pokemon], this is the client side counterpart of [Evolution].
* This has no attachments to any data regarding the evolution itself and only serves for display purposes and basic communication.
*
* @author Licious
* @since April 28th, 2022
*/
interface EvolutionDisplay : EvolutionLike {
/**
* The [Species] of the evolution result.
*/
val species: Species
/**
* The aspects of the evolution result.
* These are used by the client to resolve the entity it needs to display.
*/
val aspects: Set<String>
companion object {
@JvmStatic
val CODEC: Codec<EvolutionDisplay> = RecordCodecBuilder.create { instance ->
instance.group(
Codec.STRING.fieldOf("id").forGetter(EvolutionDisplay::id),
Species.BY_IDENTIFIER_CODEC.fieldOf("species").forGetter(EvolutionDisplay::species),
Codec.list(Codec.STRING).fieldOf("aspects").forGetter { it.aspects.toList() }
).apply(instance) { id, species, aspects -> CobblemonEvolutionDisplay(id, species, aspects.toSet()) }
}
}
}
@@ -1,59 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution
/**
* Responsible for holding the different [EvolutionController] implementations based on the logical side.
* If you haven't already checked your side previously you can use [isClient].
*
* @param C The type of [EvolutionLike] on the client side.
* @param S The type of [EvolutionLike] on the server side.
* @param CI The type of [PreProcessor] on the client side.
* @param SI The type of [PreProcessor] on the server side.
*
* @author Licious
* @since June 18th, 2022
*/
interface EvolutionProxy<C : EvolutionLike, S : EvolutionLike, CI : PreProcessor, SI : PreProcessor> {
/**
* Checks if the current logical side is the client.
*
* @return True if the current logical side is the client.
*/
fun isClient(): Boolean
/**
* Returns the current [EvolutionController].
* Keep in mind this will have type erasure and as such most operations will not be possible.
* It's recommended to work with [client] or [server] when possible.
*
* @return The current [EvolutionController].
*/
fun current(): EvolutionController<out EvolutionLike, *>
/**
* The client side implementation of the [EvolutionController].
*
* @throws [ClassCastException] if called from the server side.
*
* @return The client side implementation of the [EvolutionController] if possible.
*/
fun client(): EvolutionController<C, CI>
/**
* The server side implementation of the [EvolutionController].
*
* @throws [ClassCastException] if called from the client side.
*
* @return The server side implementation of the [EvolutionController] if possible.
*/
fun server(): EvolutionController<S, SI>
}
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
/**
* Represents an evolution of a [Pokemon] that can occur without any additional context or actions.
* For the default implementation see [LevelUpEvolution].
*
* @author Licious
* @since March 19th, 2022
*/
interface PassiveEvolution : Evolution {
/**
* Checks if the given [Pokemon] satisfies the requirements.
* If yes the evolution will attempt to start.
*
* @param pokemon The [Pokemon] being tested.
* @return If the [Pokemon] will evolve.
*/
fun attemptEvolution(pokemon: Pokemon): Boolean {
if (super.test(pokemon)) {
return super.evolve(pokemon)
}
return false
}
/**
* If the evolution will disappear once the conditions are no longer met.
*/
val permanent: Boolean
}
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution.adapters
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonEvolutionAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
/**
* Saves and loads [Evolution]s with JSON.
* For the default implementation see [CobblemonEvolutionAdapter].
*
* @author Licious
* @since March 20th, 2022
*/
interface EvolutionAdapter : JsonDeserializer<Evolution>, JsonSerializer<Evolution> {
/**
* Registers the given type of [Evolution] to it's associated ID for deserialization.
*
* @param T The type of [Evolution].
* @param id The id of the evolution event.
* @param type The [KClass] of the type.
*/
fun <T : Evolution> registerType(id: String, type: KClass<T>)
}
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution.adapters
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
/**
* Saves and loads [EvolutionRequirement]s with JSON.
* For the default implementation see [CobblemonRequirementAdapter].
*
* @author Licious
* @since March 21st, 2022
*/
interface RequirementAdapter : JsonDeserializer<EvolutionRequirement>, JsonSerializer<EvolutionRequirement> {
/**
* Registers the given type of [EvolutionRequirement] to it's associated ID for deserialization.
*
* @param T The type of [EvolutionRequirement].
* @param id The id of the evolution event.
* @param type The [KClass] of the type.
*/
fun <T : EvolutionRequirement> registerType(id: String, type: KClass<T>)
}
@@ -1,66 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution.progress
import com.cobblemon.mod.common.pokemon.evolution.controller.ServerEvolutionController
import com.cobblemon.mod.common.pokemon.evolution.progress.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import com.mojang.serialization.Lifecycle
import net.minecraft.core.MappedRegistry
import net.minecraft.core.Registry
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import kotlin.jvm.Throws
object EvolutionProgressTypes {
private val REGISTRY: Registry<EvolutionProgressType<*>> = MappedRegistry(
ResourceKey.createRegistryKey(cobblemonResource("evolution_progress")), Lifecycle.stable()
)
@JvmStatic
val DAMAGE_TAKEN = this.registerType(DamageTakenEvolutionProgress.ID, EvolutionProgressType(DamageTakenEvolutionProgress.CODEC))
@JvmStatic
val DEFEAT = this.registerType(DefeatEvolutionProgress.ID, EvolutionProgressType(DefeatEvolutionProgress.CODEC))
@JvmStatic
val LAST_BATTLE_CRITICAL_HITS = this.registerType(LastBattleCriticalHitsEvolutionProgress.ID, EvolutionProgressType(LastBattleCriticalHitsEvolutionProgress.CODEC))
@JvmStatic
val RECOIL = this.registerType(RecoilEvolutionProgress.ID, EvolutionProgressType(RecoilEvolutionProgress.CODEC))
@JvmStatic
val USE_MOVE = this.registerType(UseMoveEvolutionProgress.ID, EvolutionProgressType(UseMoveEvolutionProgress.CODEC))
/**
* Registers an evolution progress.
*
* @param T The type of the [EvolutionProgress].
* @param id The [Identifier].
* @param type The [EvolutionProgressType] record.
* @return The registered [EvolutionProgressType] of type [T].
*
* @throws [IllegalStateException] if [Registry.register] fails.
*/
@Throws(IllegalStateException::class)
@JvmStatic
fun <T : EvolutionProgress<*>> registerType(id: ResourceLocation, type: EvolutionProgressType<T>): EvolutionProgressType<T> {
return Registry.register(REGISTRY, id, type)
}
/**
* Creates a codec instance of this registry.
*
* @return The generated [Codec] of [EvolutionProgress].
*/
fun codec(): Codec<EvolutionProgress<*>> = REGISTRY.byNameCodec().dispatch(
ServerEvolutionController.ID_KEY,
{ it.type() },
{ it.codec }
)
}
@@ -12,7 +12,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.OriginalTrainerType
import com.cobblemon.mod.common.pokemon.evolution.requirements.LevelRequirement
import com.cobblemon.mod.common.pokemon.transformation.requirements.LevelRequirement
import kotlin.math.pow
import kotlin.math.roundToInt
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.aspect.FeatureAspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.SummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -29,11 +29,11 @@ import net.minecraft.network.RegistryFriendlyByteBuf
*/
open class ChoiceSpeciesFeatureProvider(
override var keys: List<String>,
var default: String? = null,
override var default: String? = null,
var choices: List<String> = listOf(),
var isAspect: Boolean = true,
override var isAspect: Boolean = true,
var aspectFormat: String = "{{choice}}"
) : SynchronizedSpeciesFeatureProvider<StringSpeciesFeature>, CustomPokemonPropertyType<StringSpeciesFeature>, AspectProvider {
) : SynchronizedSpeciesFeatureProvider<StringSpeciesFeature>, CustomPokemonPropertyType<StringSpeciesFeature>, FeatureAspectProvider {
override var needsKey = true
override var visible = false
fun getAspect(feature: StringSpeciesFeature) = aspectFormat.substitute("choice", feature.value)
@@ -76,6 +76,28 @@ open class ChoiceSpeciesFeatureProvider(
return aspects
}
fun parseValue(aspect: String): String? {
if (!matches(aspect)) return null
val head = aspectFormat.substringBefore("{{choice}}")
val tail = aspectFormat.substringAfter("{{choice}}").takeIf { it.isNotBlank() }
var value = aspect.substringAfter(head)
tail?.let { value = value.substringBefore(it) }
return value
}
override fun matches(aspect: String): Boolean = this.isAspect && this.getAllAspects().contains(aspect)
override fun from(aspect: String): SpeciesFeature<*>? {
val value = parseValue(aspect)
return if (value != null && choices.contains(value)) this.fromString(value) else null
}
override fun set(pokemon: Pokemon, aspect: String) {
val value = parseValue(aspect)
if (value != null && choices.contains(value)) this.get(pokemon)?.value = value
}
override fun examples() = choices
internal constructor(): this(emptyList())
@@ -123,7 +145,9 @@ open class ChoiceSpeciesFeatureProvider(
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect) {
get(pokemon)?.let { setOf(getAspect(it)) } ?: emptySet()
get(pokemon)?.let {
setOf(getAspect(it))
} ?: emptySet()
} else {
emptySet()
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.aspect.FeatureAspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.SummarySpeciesFeatureRenderer
@@ -30,65 +30,74 @@ import kotlin.random.Random
* @author Hiroku
* @since May 13th, 2022
*/
open class FlagSpeciesFeature(override val name: String) : SynchronizedSpeciesFeature, CustomPokemonProperty {
open class FlagSpeciesFeature(override val name: String) : SynchronizedSpeciesFeature<Boolean>, CustomPokemonProperty {
constructor(name: String, enabled: Boolean): this(name) {
this.enabled = enabled
this.value = enabled
}
var enabled = false
override var value: Boolean = false
override fun saveToNBT(pokemonNBT: CompoundTag): CompoundTag {
pokemonNBT.putBoolean(name, enabled)
pokemonNBT.putBoolean(name, value)
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature {
enabled = if (pokemonNBT.contains(name)) pokemonNBT.getBoolean(name) else enabled
override fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature<Boolean> {
value = if (pokemonNBT.contains(name)) pokemonNBT.getBoolean(name) else value
return this
}
override fun saveToJSON(pokemonJSON: JsonObject): JsonObject {
pokemonJSON.addProperty(name, enabled)
pokemonJSON.addProperty(name, value)
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature<Boolean> {
val isEnabled = pokemonJSON.get(name)?.asBoolean
enabled = isEnabled ?: this.enabled
value = isEnabled ?: this.value
return this
}
override fun saveToBuffer(buffer: RegistryFriendlyByteBuf, toClient: Boolean) {
buffer.writeBoolean(enabled)
buffer.writeBoolean(value)
}
override fun loadFromBuffer(buffer: RegistryFriendlyByteBuf) {
enabled = buffer.readBoolean()
value = buffer.readBoolean()
}
override fun asString() = "$name=$enabled"
override fun asString() = "$name=$value"
override fun apply(pokemon: Pokemon) {
val featureProvider = SpeciesFeatures.getFeature(name) ?: return
if (featureProvider in SpeciesFeatures.getFeaturesFor(pokemon.species)) {
val existingFeature = pokemon.getFeature<FlagSpeciesFeature>(name)
if (existingFeature != null) {
existingFeature.enabled = enabled
existingFeature.value = value
} else {
pokemon.features.add(FlagSpeciesFeature(name, enabled))
pokemon.features.add(FlagSpeciesFeature(name, value))
}
pokemon.updateAspects()
}
}
override fun matches(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled == enabled
override fun matches(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.value == value
}
class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpeciesFeature>, CustomPokemonPropertyType<FlagSpeciesFeature>, AspectProvider {
class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpeciesFeature>, CustomPokemonPropertyType<FlagSpeciesFeature>, FeatureAspectProvider {
override var keys: List<String> = emptyList()
// Uses get() = true because that way there's no backing field. It MUST be true, this way no JSON trickery will overwrite it
override val needsKey get() = true
var default: String? = null
var isAspect = true
override var default: String? = null
override var isAspect = true
override fun matches(aspect: String): Boolean = this.isAspect && this.keys.first() == aspect
override fun from(aspect: String): SpeciesFeature<*>? = if (matches(aspect)) fromString(null) else null
override fun set(pokemon: Pokemon, aspect: String) {
this.get(pokemon)?.value = true
}
override var visible: Boolean = false
override fun invoke(buffer: RegistryFriendlyByteBuf, name: String): FlagSpeciesFeature? {
@@ -170,7 +179,7 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.enabled == true) {
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.value == true) {
setOf(keys.first())
} else {
emptySet()
@@ -178,7 +187,7 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.enabled == true) {
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.value == true) {
setOf(keys.first())
} else {
emptySet()
@@ -84,7 +84,7 @@ object GlobalSpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
}
fun register(name: String, provider: SpeciesFeatureProvider<*>) = register(name, provider, isCoded = true)
fun <T : SpeciesFeature> register(name: String, providerLambda: () -> T) {
fun <T : SpeciesFeature<*>> register(name: String, providerLambda: () -> T) {
register(name, object : SpeciesFeatureProvider<T> {
override fun invoke(pokemon: Pokemon) = providerLambda()
override fun invoke(nbt: CompoundTag) = providerLambda().apply { loadFromNBT(nbt) }
@@ -26,8 +26,8 @@ import net.minecraft.world.phys.Vec3
* A species feature value that's just an integer. Complex stuff.
* @author Hiroku
*/
class IntSpeciesFeature(override var name: String) : SynchronizedSpeciesFeature, CustomPokemonProperty {
var value = 0
class IntSpeciesFeature(override var name: String) : SynchronizedSpeciesFeature<Int>, CustomPokemonProperty {
override var value = 0
constructor() : this("")
constructor(name: String, value: Int) : this(name) {
@@ -39,7 +39,7 @@ class IntSpeciesFeature(override var name: String) : SynchronizedSpeciesFeature,
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: CompoundTag): SynchronizedSpeciesFeature {
override fun loadFromNBT(pokemonNBT: CompoundTag): SynchronizedSpeciesFeature<Int> {
value = pokemonNBT.getInt(name)
return this
}
@@ -49,7 +49,7 @@ class IntSpeciesFeature(override var name: String) : SynchronizedSpeciesFeature,
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature<Int> {
value = pokemonJSON.get(name).asInt
return this
}
@@ -24,10 +24,11 @@ import net.minecraft.nbt.CompoundTag
* @author Hiroku
* @since May 13th, 2022
*/
interface SpeciesFeature {
interface SpeciesFeature<T> {
val name: String
var value: T
fun saveToNBT(pokemonNBT: CompoundTag): CompoundTag
fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature
fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature<T>
fun saveToJSON(pokemonJSON: JsonObject): JsonObject
fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature
fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature<T>
}
@@ -21,7 +21,7 @@ import net.minecraft.nbt.CompoundTag
* @author Hiroku
* @since November 29th, 2022
*/
interface SpeciesFeatureProvider<T : SpeciesFeature> {
interface SpeciesFeatureProvider<T : SpeciesFeature<*>> {
/**
* Attempts to create a species feature for the given [Pokemon]. It is appropriate to check for an existing feature
* that is compatible with this type and transfer / retain that feature instead of constructing a new one. For example
@@ -24,14 +24,14 @@ import net.minecraft.network.RegistryFriendlyByteBuf
*/
class StringSpeciesFeature(
override val name: String,
var value: String
) : SynchronizedSpeciesFeature, CustomPokemonProperty {
override var value: String
) : SynchronizedSpeciesFeature<String>, CustomPokemonProperty {
override fun saveToNBT(pokemonNBT: CompoundTag): CompoundTag {
pokemonNBT.putString(name, value)
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature {
override fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature<String> {
value = pokemonNBT.getString(name)?.takeIf { it.isNotBlank() }?.lowercase() ?: return this
return this
}
@@ -41,7 +41,7 @@ class StringSpeciesFeature(
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature<String> {
value = pokemonJSON.get(name)?.asString?.lowercase() ?: return this
return this
}
@@ -16,4 +16,4 @@ import com.cobblemon.mod.common.api.serialization.BufferSerializer
* @author Hiroku
* @since November 13th, 2023
*/
interface SynchronizedSpeciesFeature : SpeciesFeature, BufferSerializer
interface SynchronizedSpeciesFeature<T> : SpeciesFeature<T>, BufferSerializer
@@ -22,7 +22,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf
* @author Hiroku
* @since November 13th, 2023
*/
interface SynchronizedSpeciesFeatureProvider<T : SynchronizedSpeciesFeature> : SpeciesFeatureProvider<T>, BufferSerializer {
interface SynchronizedSpeciesFeatureProvider<T : SynchronizedSpeciesFeature<*>> : SpeciesFeatureProvider<T>, BufferSerializer {
var visible: Boolean
operator fun invoke(buffer: RegistryFriendlyByteBuf, name: String): T?
/** Gets the feature from this Pokémon, if it has been created yet. */
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation
import com.cobblemon.mod.common.api.pokemon.transformation.controller.TransformationController
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.ContextTrigger
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Represents a transformation of a [Pokemon]. This is the server side counterpart of [TransformationDisplay] and serves as
* the driver of the transformation. These are triggered by a player-driven or world-driven event.
*
* @author Segfault Guy
* @since Sept 8th, 2023
*/
interface Transformation : TransformationLike {
/** The [TransformationTrigger] to attempt this transformation. */
val trigger: TransformationTrigger
/** The [TransformationRequirement]s that need to be satisfied for this transformation. */
val requirements: Set<TransformationRequirement>
/** If this transformation can be deferred by the user and started whenever. */
val optional: Boolean
/** If this transformation needs its requirements reevaluated to remain valid in a [TransformationController]. */
val reevaluate: Boolean
/** Checks if all [TransformationRequirement]s are met. */
fun checkRequirements(pokemon: Pokemon) = this.requirements.all { requirement -> requirement.check(pokemon) }
/**
* Tests if the given [Pokemon] passes all the conditions and is ready to transform.
*
* @param pokemon The [Pokemon] being queried.
* @param context The optional [ContextTrigger.Context] needed to transform.
* @return Whether the [Transformation] can start.
*/
fun test(pokemon: Pokemon, context: ContextTrigger.Context?) = this.trigger.testTrigger(context) && this.checkRequirements(pokemon)
/**
* Starts this transformation if conditions are met.
*
* @param pokemon The [Pokemon] being transformed.
* @param context The optional [ContextTrigger.Context] needed to transform.
* @return Whether the [Transformation] was successful.
*/
fun start(pokemon: Pokemon, context: ContextTrigger.Context?): Boolean {
if (test(pokemon, context)) {
forceStart(pokemon)
return true
}
return false
}
/**
* Starts this transformation without evaluating the requirements.
*
* @param pokemon The [Pokemon] being transformed.
*/
fun forceStart(pokemon: Pokemon) { this.requirements.forEach { it.fulfill(pokemon) } }
/**
* Converts this transformation to its client side representation.
*
* @param pokemon The [Pokemon] being transformed.
* @return The [TransformationDisplay] representing this transformation on the client.
*/
fun convertToDisplay(pokemon: Pokemon): TransformationDisplay
/**
* Validates whether a transformation still meets the [TransformationRequirement]s.
* If [Transformation.reevaluate] is false, this check will be bypassed.
*
* @param pokemon the [Pokemon] to test against.
* @return Whether this transformation is still valid and can be made for [pokemon].
*/
fun reevaluate(pokemon: Pokemon) = !this.reevaluate || this.checkRequirements(pokemon)
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
/**
* Represents a transformation of a [Pokemon], this is the client side counterpart of [Transformation].
* This has no attachments to any data regarding the transformation itself and only serves for display purposes and basic communication.
*
* @author Licious
* @since April 28th, 2022
*/
interface TransformationDisplay : TransformationLike {
/**
* The [Species] of the transformation result.
*/
val species: Species
/**
* The aspects of the transformation result.
* These are used by the client to resolve the entity it needs to display.
*/
val aspects: Set<String>
}
@@ -6,20 +6,20 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution
package com.cobblemon.mod.common.api.pokemon.transformation
/**
* Represents the base of an evolution.
* It can either be the full data container present on the server side [Evolution].
* Or the client side representation for display [EvolutionDisplay].
* Represents the base of a transformation.
* It can either be the full data container present on the server side [Transformation].
* Or the client side representation for display [TransformationDisplay].
*
* @author Licious
* @since April 28th, 2022
*/
interface EvolutionLike {
interface TransformationLike {
/**
* The unique id of the evolution.
* The unique id of the transformation.
* It should be human-readable, I.E pikachu_level
*/
val id: String
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.adapters
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.pokemon.transformation.adapters.CobblemonRequirementAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
/**
* Saves and loads [TransformationRequirement]s with JSON.
* For the default implementation see [CobblemonRequirementAdapter].
*
* @author Licious
* @since March 21st, 2022
*/
interface RequirementAdapter : JsonDeserializer<TransformationRequirement>, JsonSerializer<TransformationRequirement> {
/**
* Registers the given type of [TransformationRequirement] to it's associated ID for deserialization.
*
* @param T The type of [TransformationRequirement].
* @param id The id of the evolution event.
* @param type The [KClass] of the type.
*/
fun <T : TransformationRequirement> registerType(id: String, type: KClass<T>)
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.adapters
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.pokemon.transformation.adapters.CobblemonTriggerAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
/**
* Saves and loads [TransformationTrigger]s with JSON.
* For the default implementation see [CobblemonTriggerAdapter].
*
* @author Licious
* @since March 20th, 2022
*/
interface TriggerAdapter : JsonDeserializer<TransformationTrigger>, JsonSerializer<TransformationTrigger> {
/**
* Registers the given type of [TransformationTrigger] to it's associated ID for deserialization.
*
* @param T The type of [TransformationTrigger].
* @param id The id of the evolution event.
* @param type The [KClass] of the type.
*/
fun <T : TransformationTrigger> registerType(id: String, type: KClass<T>)
}
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.controller
import com.cobblemon.mod.common.api.pokemon.transformation.TransformationLike
import com.cobblemon.mod.common.api.pokemon.transformation.proxy.PreProcessor
import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgress
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.controller.CobblemonClientTransformationController
import com.cobblemon.mod.common.pokemon.transformation.controller.CobblemonServerTransformationController
/**
* Responsible for holding all available [TransformationLike]s in the [Pokemon].
* Also handles all the networking behind them.
* For the Cobblemon default implementations see [CobblemonClientTransformationController] & [CobblemonServerTransformationController].
*
* @author Licious
* @since April 28th, 2022
*/
interface TransformationController<T : TransformationLike, out I : PreProcessor> : MutableSet<T> {
/**
* Resolves the [Pokemon] attached to this controller.
*
* @return The [Pokemon] this controller is attached to.
*/
fun pokemon(): Pokemon
/**
* Starts the given transformation on this controller.
* All the necessary networking will be handled.
*
* @param transformation The [TransformationLike] starting.
*/
fun start(transformation: T)
/** Reverts the active [TransformationLike] for this controller's [pokemon]. */
fun revert() // TODO dunno if i like this
/**
* Returns an immutable collection of [TransformationProgress]es attached to this controller.
*
* @return The currently tracked [TransformationProgress]es.
*/
fun progress(): Collection<TransformationProgress<*>>
/**
* Attaches a [TransformationProgress] to this controller.
*
* @param P The type of [TransformationProgress].
* @param progress The [TransformationProgress] being attached for tracking.
* @return The instance being tracked.
*/
fun <P : TransformationProgress<*>> trackProgress(progress: P): P
/**
* Attempts to find an [TransformationProgress] in [progress] or creates and tracks using [trackProgress].
*
* @param P The type of [TransformationProgress].
* @param predicate The lambda attempting to find specific progress. This needs to ensure the [TransformationProgress] is of type [P] or the cast will fail.
* @param progressFactory The lambda creating a new instance of type [P] if [predicate] can't find an element.
* @return The found match in [progress] or the created and now tracked with [trackProgress].
*/
fun <P : TransformationProgress<*>> progressFirstOrCreate(predicate: (progress: TransformationProgress<*>) -> Boolean, progressFactory: () -> P): P
fun asIntermediate(): I
/**
* Guard for [start] to prevent a transformation from executing while the [pokemon] is busy or if the transformation
* is not in the controller.
*
* @param transformation the [TransformationLike] to validate.
* @return whether this controller is capable of starting the transformation.
*/
fun canStart(transformation: T): Boolean
/**
* Whether this controller is capable of executing [start] for a transformation. This checks if the [pokemon] is busy
* or if there is a transformation ready in the controller.
*
* @return whether this controller is capable of starting a transformation.
*/
fun canStart(): Boolean
/**
* Validates the [TransformationLike] currently active for this controller's [pokemon] and determines whether it can be
* reverted and if the pokemon is capable of reverting right now.
*
* @return whether the transformation can be reverted.
*/
fun canRevert(): Boolean
/**
* Retrieves the [TransformationLike] in this controller that matches the provided id.
*
* @param id the identifier of the transformation.
* @return the transformation matching this id.
*/
fun getById(id: String) = this.firstOrNull { transformation -> transformation.id.equals(id, true) }
}
@@ -6,11 +6,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution
package com.cobblemon.mod.common.api.pokemon.transformation.evolution
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.evolution.StandardPreEvolution
import com.cobblemon.mod.common.pokemon.transformation.evolution.StandardPreEvolution
/**
* Represents the previous stage in the evolutionary line of a given Pokémon.
@@ -6,25 +6,36 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution.progress
package com.cobblemon.mod.common.api.pokemon.transformation.progress
import com.cobblemon.mod.common.api.data.Identifiable
import com.cobblemon.mod.common.api.pokemon.evolution.EvolutionController
import com.cobblemon.mod.common.api.pokemon.transformation.controller.TransformationController
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.pokemon.Pokemon
import com.google.gson.JsonObject
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
/**
* Represents a container of progression towards the completion of an evolution requirement.
* To register an evolution progress use [EvolutionProgressTypes.registerType].
* Represents a container of progression towards the completion of a [TransformationRequirement].
* To register a transformation progress use [TransformationProgressTypes.registerType].
*
* @param T The type of the progression.
*
* @author Licious
* @since January 27th, 2023
*/
interface EvolutionProgress<T> : Identifiable {
interface TransformationProgress<T : TransformationProgress.Progress> : Identifiable {
/**
* Represents progression towards a [TransformationRequirement].
*
* @property amount The value required to be complete.
*
* @author Segfault Guy
* @since April 18th, 2025
*/
interface Progress {
val amount: Int
}
/**
* Gets the current progress of type [T].
@@ -47,7 +58,7 @@ interface EvolutionProgress<T> : Identifiable {
fun reset()
/**
* Used to decide if the given progress should be kept in the [pokemon]s [EvolutionController].
* Used to decide if the given progress should be kept in the [pokemon]s [TransformationController].
*
* @param pokemon The [Pokemon] being queried.
* @return True if the progress is still required otherwise false.
@@ -55,16 +66,16 @@ interface EvolutionProgress<T> : Identifiable {
fun shouldKeep(pokemon: Pokemon): Boolean
/**
* The [EvolutionProgressType] of this.
* The [TransformationProgressType] of this.
*
* @return The [EvolutionProgressType] of this.
* @return The [TransformationProgressType] of this.
*/
fun type(): EvolutionProgressType<*>
fun type(): TransformationProgressType<*>
companion object {
@JvmStatic
fun codec() = EvolutionProgressTypes.codec()
fun codec() = TransformationProgressTypes.codec()
}
}
@@ -6,10 +6,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution.progress
package com.cobblemon.mod.common.api.pokemon.transformation.progress
import com.mojang.serialization.MapCodec
data class EvolutionProgressType<T : EvolutionProgress<*>>(
data class TransformationProgressType<T : TransformationProgress<*>>(
val codec: MapCodec<T>
)
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.progress
import com.cobblemon.mod.common.pokemon.transformation.progress.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import com.mojang.serialization.Lifecycle
import net.minecraft.core.MappedRegistry
import net.minecraft.core.Registry
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import kotlin.jvm.Throws
/**
* Registry of [TransformationProgressType]s.
*
* @author Licious
* @since July 6th, 2024
*/
object TransformationProgressTypes {
private const val ID_KEY = "id"
private val REGISTRY: Registry<TransformationProgressType<*>> = MappedRegistry(
ResourceKey.createRegistryKey(cobblemonResource("transformation_progress")), Lifecycle.stable()
)
@JvmStatic
val DAMAGE_TAKEN = this.registerType(DamageTakenTransformationProgress.ID, TransformationProgressType(DamageTakenTransformationProgress.CODEC))
@JvmStatic
val DEFEAT = this.registerType(DefeatTransformationProgress.ID, TransformationProgressType(DefeatTransformationProgress.CODEC))
@JvmStatic
val LAST_BATTLE_CRITICAL_HITS = this.registerType(LastBattleCriticalHitsTransformationProgress.ID, TransformationProgressType(LastBattleCriticalHitsTransformationProgress.CODEC))
@JvmStatic
val RECOIL = this.registerType(RecoilTransformationProgress.ID, TransformationProgressType(RecoilTransformationProgress.CODEC))
@JvmStatic
val USE_MOVE = this.registerType(UseMoveTransformationProgress.ID, TransformationProgressType(UseMoveTransformationProgress.CODEC))
/**
* Registers a transformation progress.
*
* @param T The type of the [TransformationProgress].
* @param id The [ResourceLocation].
* @param type The [TransformationProgressType] record.
* @return The registered [TransformationProgressType] of type [T].
*
* @throws [IllegalStateException] if [Registry.register] fails.
*/
@Throws(IllegalStateException::class)
@JvmStatic
fun <T : TransformationProgress<*>> registerType(id: ResourceLocation, type: TransformationProgressType<T>): TransformationProgressType<T> {
return Registry.register(REGISTRY, id, type)
}
/**
* Creates a codec instance of this registry.
*
* @return The generated [Codec] of [TransformationProgress].
*/
fun codec(): Codec<TransformationProgress<*>> = REGISTRY.byNameCodec().dispatch(
ID_KEY,
{ it.type() },
{ it.codec }
)
}
@@ -6,13 +6,14 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution
package com.cobblemon.mod.common.api.pokemon.transformation.proxy
import com.cobblemon.mod.common.api.pokemon.transformation.controller.TransformationController
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* TODO
*/
fun interface PreProcessor {
fun create(pokemon: Pokemon): EvolutionController<*, *>
fun create(pokemon: Pokemon): TransformationController<*, *>
}
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.proxy
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
import com.cobblemon.mod.common.api.pokemon.transformation.TransformationDisplay
import com.cobblemon.mod.common.api.pokemon.transformation.TransformationLike
import com.cobblemon.mod.common.api.pokemon.transformation.controller.TransformationController
/**
* Responsible for holding the different [TransformationController] implementations based on the logical side.
* If you haven't already checked your side previously you can use [isClient].
*
* @param C The type of [TransformationDisplay] on the client side.
* @param S The type of [Transformation] on the server side.
* @param CI The type of [PreProcessor] on the client side.
* @param SI The type of [PreProcessor] on the server side.
*
* @author Licious, Segfault Guy
* @since January 19th, 2025
*/
interface TransformationProxy<C : TransformationDisplay, S : Transformation, CI : PreProcessor, SI : PreProcessor> {
/**
* Checks if the current logical side is the client.
*
* @return True if the current logical side is the client.
*/
fun isClient(): Boolean
/**
* Returns the current [TransformationController].
* Keep in mind this will have type erasure and as such most operations will not be possible.
* It's recommended to work with [client] or [server] when possible.
*
* @return The current [TransformationController].
*/
fun current(): TransformationController<out TransformationLike, *>
/**
* The client side implementation of the [TransformationController].
*
* @throws [ClassCastException] if called from the server side.
*
* @return The client side implementation of the [TransformationController] if possible.
*/
fun client(): TransformationController<C, CI>
/**
* The server side implementation of the [TransformationController].
*
* @throws [ClassCastException] if called from the client side.
*
* @return The server side implementation of the [TransformationController] if possible.
*/
fun server(): TransformationController<S, SI>
}
@@ -6,23 +6,22 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.evolution.requirements.template
package com.cobblemon.mod.common.api.pokemon.transformation.requirement
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.world.entity.LivingEntity
/**
* An [EvolutionRequirement] that expects a [LivingEntity] to be attached to the [Pokemon].
* A [TransformationRequirement] that expects a [LivingEntity] to be attached to the [Pokemon].
* It can be the [PokemonEntity] if present or the [LivingEntity] that owns it.
*
* Whenever an entity can't be resolved [EvolutionRequirement.check] will never succeed.
* Whenever an entity can't be resolved [TransformationRequirement.check] will never succeed.
*
* @author Licious
* @since March 21st, 2022
*/
interface EntityQueryRequirement : EvolutionRequirement {
interface EntityQueryRequirement : TransformationRequirement {
override fun check(pokemon: Pokemon): Boolean {
val queriedEntity = pokemon.entity ?: pokemon.getOwnerPlayer() ?: return false
return this.check(pokemon, queriedEntity)
@@ -32,7 +31,7 @@ interface EntityQueryRequirement : EvolutionRequirement {
* Checks if the given [Pokemon] & [LivingEntity] satisfies the requirement.
*
* @param pokemon The [Pokemon] being queried.
* @param queriedEntity The [LivingEntity] that can be compared in context of a [Level].
* @param queriedEntity The [LivingEntity] that can be compared in context of a [net.minecraft.world.level.Level].
* @return If the requirement was satisfied.
*/
fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.requirement
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
/**
* A [TransformationRequirement] that expects the [Pokemon] to have an owner.
* It can be the [ServerPlayer] or the [NPCEntity] that owns it.
*
* Whenever an owner can't be resolved [TransformationRequirement.check] will never succeed.
*
* @author Polymeta
* @since May 6th, 2025
*/
interface OwnerQueryRequirement : TransformationRequirement {
override fun check(pokemon: Pokemon): Boolean {
val owner = pokemon.getOwnerEntity() ?: return false
return when (owner) {
is ServerPlayer -> {
this.checkPlayer(pokemon, owner)
}
is NPCEntity -> {
this.checkNPC(pokemon, owner)
}
else -> false
}
}
/**
* Checks if the given [Pokemon] & [ServerPlayer] satisfies the requirement.
*
* @param pokemon The [Pokemon] being queried.
* @param owner The [ServerPlayer] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
fun checkPlayer(pokemon: Pokemon, owner: ServerPlayer): Boolean
/**
* Checks if the given [Pokemon] & [NPCEntity] satisfies the requirement.
* This method mainly exists as NPCs are different to players in a lot of ways (e.g., lack of advancements),
* so they need to be handled differently, either by checking an NPC config or always returning true/false
*
* @param pokemon The [Pokemon] being queried.
* @param owner The [NPCEntity] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
fun checkNPC(pokemon: Pokemon, owner: NPCEntity): Boolean
}
@@ -6,23 +6,22 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution.requirement
package com.cobblemon.mod.common.api.pokemon.transformation.requirement
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.ContextTrigger
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Represents a requirement for an evolution.
* Represents a requirement for a transformation.
* Requirements are constant and will never change regardless of the backing action.
* For dynamic requirements see [ContextEvolution].
*
* See [Evolution.requirements] & [Evolution.test] for usage.
* See [Transformation.requirements] & [Transformation.test] for usage.
*
* @author Licious
* @since March 19th, 2022
*/
fun interface EvolutionRequirement {
fun interface TransformationRequirement {
/**
* Checks if the given [Pokemon] satisfies the requirement.
@@ -32,4 +31,10 @@ fun interface EvolutionRequirement {
*/
fun check(pokemon: Pokemon): Boolean
/**
* Callback after the [Transformation] has been performed.
*
* @param pokemon The [Pokemon] being queried.
*/
fun fulfill(pokemon: Pokemon) {}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.trigger
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.triggers.ItemInteractionTrigger
import com.cobblemon.mod.common.pokemon.transformation.triggers.TradeTrigger
/**
* Represents a [TransformationTrigger] of a [Pokemon] that can only occur during specific actions and with added context.
* For the default implementations see [ItemInteractionTrigger] & [TradeTrigger].
*
* @param TC The context that is serialized from JSON during species loading. This is what the [Context] provided
* to [testTrigger] is expected to match against.
*
* @author Licious
* @since March 19th, 2022
*/
interface ContextTrigger<TC>: TransformationTrigger {
/**
* Represents context provided for [TransformationTrigger] evaluation.
*
* @author Segfault Guy
* @since October 15th, 2023
*/
interface Context
/** The target context required for this [Transformation] to be successfully triggered. */
val requiredContext: TC
/**
* Checks if the given context is valid for the [requiredContext].
*
* @param context The [Context] given at runtime when querying the [Transformation].
* @return If the context matched the [requiredContext].
*/
fun testContext(context: Context): Boolean = false
/**
* Evaluates the triggering condition of a [Transformation].
*
* @param context The [Context] of the [Transformation].
* @return Whether the triggering condition is satisfied. If [context] is null, bypasses the condition check.
*/
override fun testTrigger(context: Context?) = context?.let { testContext(it) } ?: true
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.trigger
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.triggers.LevelUpTrigger
/**
* Represents a [TransformationTrigger] of a [Pokemon] that is attempted periodically without any additional context or actions.
* For the default implementation see [LevelUpTrigger].
*
* Triggers that implement this marker will be tested every second.
*
* @author Licious
* @since March 19th, 2022
*/
interface PassiveTrigger : TransformationTrigger
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.trigger
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
/**
* Represents a condition that starts a [Transformation].
*
* @author Segfault Guy
* @since Sept 8th, 2023
*/
interface TransformationTrigger {
/**
* Evaluates the triggering condition of a [Transformation].
*
* @param context The optional [ContextTrigger.Context] for [ContextTrigger] evaluation.
* @return Whether the triggering condition is satisfied.
*/
fun testTrigger(context: ContextTrigger.Context?): Boolean = true
}
@@ -18,6 +18,5 @@ package com.cobblemon.mod.common.api.riding
enum class RidingStyle {
LAND,
LIQUID,
AIR,
COMPOSITE
AIR
}
@@ -28,7 +28,8 @@ import net.minecraft.world.phys.Vec3
*/
interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBehaviourState> {
val key: ResourceLocation
val style: RidingStyle
fun getRidingStyle(settings: Settings, state: State): RidingStyle
fun isActive(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -25,7 +26,10 @@ import net.minecraft.world.phys.Vec3
class RidingController<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(val behaviour: RidingBehaviour<Settings, State>) : RidingBehaviour<Settings, State> {
override val key = behaviour.key
override val style = behaviour.style
override fun getRidingStyle(settings: Settings, state: State): RidingStyle {
return behaviour.getRidingStyle(settings, state)
}
override fun isActive(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.isActive(settings, state, vehicle)
@@ -175,4 +179,5 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.createDefaultState(settings)
}
}
@@ -38,7 +38,10 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
}
override val key: ResourceLocation = KEY
override val style = RidingStyle.AIR
override fun getRidingStyle(settings: BirdAirSettings, state: BirdAirState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<BirdAirSettings, BirdAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
@@ -35,7 +35,10 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
override val key = KEY
override val style = RidingStyle.LIQUID
override fun getRidingStyle(settings: DolphinSettings, state: DolphinState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
@@ -33,7 +33,10 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
}
override val key = KEY
override val style = RidingStyle.LAND
override fun getRidingStyle(settings: GenericLandSettings, state: GenericLandState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<GenericLandSettings, GenericLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
@@ -35,7 +35,10 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
}
override val key = KEY
override val style = RidingStyle.LIQUID
override fun getRidingStyle(settings: GenericSwimSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<GenericSwimSettings, RidingBehaviourState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
@@ -35,7 +35,10 @@ class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourSta
}
override val key = KEY
override val style = RidingStyle.AIR
override fun getRidingStyle(settings: GliderAirSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<GliderAirSettings, RidingBehaviourState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
@@ -39,7 +39,10 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
}
override val key = KEY
override val style = RidingStyle.AIR
override fun getRidingStyle(settings: HelicopterSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<HelicopterSettings, RidingBehaviourState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
@@ -33,7 +33,10 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
}
override val key = KEY
override val style = RidingStyle.AIR
override fun getRidingStyle(settings: HoverSettings, state: HoverState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<HoverSettings, HoverState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
@@ -35,7 +35,10 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
}
override val key = KEY
override val style = RidingStyle.AIR
override fun getRidingStyle(settings: JetAirSettings, state: JetAirState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<JetAirSettings, JetAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
@@ -34,7 +34,10 @@ class SwimDashBehaviour : RidingBehaviour<SwimDashSettings, SwimDashState> {
}
override val key = KEY
override val style = RidingStyle.LIQUID
override fun getRidingStyle(settings: SwimDashSettings, state: SwimDashState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<SwimDashSettings, SwimDashState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
@@ -53,7 +53,10 @@ class VehicleLandBehaviour : RidingBehaviour<VehicleLandSettings, VehicleLandSta
}
override val key = KEY
override val style = RidingStyle.LAND
override fun getRidingStyle(settings: VehicleLandSettings, state: VehicleLandState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<VehicleLandSettings, VehicleLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
@@ -30,8 +30,14 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
override val key = KEY
override val style = RidingStyle.COMPOSITE
override fun getRidingStyle(settings: CompositeSettings, state: CompositeState): RidingStyle {
return chooseBehaviour(settings, state) {
behaviour, behaviourSettings, behaviourState ->
behaviour.getRidingStyle(behaviourSettings, behaviourState)
}
}
override fun createDefaultState(settings: CompositeSettings): CompositeState {
val defaultBehaviour = RidingBehaviours.get(settings.defaultBehaviour.key)
val defaultState = defaultBehaviour.createDefaultState(settings.defaultBehaviour)
@@ -51,7 +57,7 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
): T {
val defaultBehaviour = RidingBehaviours.get(settings.defaultBehaviour.key)
val alternativeBehaviour = RidingBehaviours.get(settings.alternateBehaviour.key)
return when (state.activeController.get()) {
return when (state.activeBehaviour.get()) {
settings.defaultBehaviour.key -> action(
defaultBehaviour,
settings.defaultBehaviour,
@@ -63,7 +69,7 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
state.alternateBehaviourState
)
else -> error("Invalid controller: ${state.activeController.get()}")
else -> error("Invalid controller: ${state.activeBehaviour.get()}")
}
}
@@ -364,28 +370,28 @@ open class CompositeSettings : RidingBehaviourSettings {
}
class CompositeState(
private val defaultBehaviour: ResourceLocation,
val defaultBehaviour: ResourceLocation,
val defaultBehaviourState: RidingBehaviourState,
val alternateBehaviourState: RidingBehaviourState
) : RidingBehaviourState() {
var activeController = ridingState(defaultBehaviour, Side.CLIENT)
var activeBehaviour = ridingState(defaultBehaviour, Side.CLIENT)
var lastTransition = ridingState(-100L, Side.BOTH)
override val rideVelocity: SidedRidingState<Vec3>
get() = when (activeController.get()) {
get() = when (activeBehaviour.get()) {
defaultBehaviour -> defaultBehaviourState.rideVelocity
else -> alternateBehaviourState.rideVelocity
}
override val stamina: SidedRidingState<Float>
get() = when (activeController.get()) {
get() = when (activeBehaviour.get()) {
defaultBehaviour -> defaultBehaviourState.stamina
else -> alternateBehaviourState.stamina
}
override fun reset() {
super.reset()
activeController.set(defaultBehaviour, forced = true)
activeBehaviour.set(defaultBehaviour, forced = true)
lastTransition.set(-100L, forced = true)
defaultBehaviourState.reset()
alternateBehaviourState.reset()
@@ -394,7 +400,7 @@ class CompositeState(
override fun copy(): CompositeState {
val state =
CompositeState(defaultBehaviour, defaultBehaviourState.copy(), alternateBehaviourState.copy())
state.activeController.set(activeController.get(), forced = true)
state.activeBehaviour.set(activeBehaviour.get(), forced = true)
state.lastTransition.set(lastTransition.get(), forced = true)
state.rideVelocity.set(rideVelocity.get(), forced = true)
state.stamina.set(stamina.get(), forced = true)
@@ -403,7 +409,7 @@ class CompositeState(
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is CompositeState) return false
if (previous.activeController.get() != activeController.get()) return true
if (previous.activeBehaviour.get() != activeBehaviour.get()) return true
if (defaultBehaviourState.shouldSync(previous.defaultBehaviourState)) return true
if (alternateBehaviourState.shouldSync(previous.alternateBehaviourState)) return true
return super.shouldSync(previous)
@@ -411,14 +417,14 @@ class CompositeState(
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeResourceLocation(activeController.get())
buffer.writeResourceLocation(activeBehaviour.get())
defaultBehaviourState.encode(buffer)
alternateBehaviourState.encode(buffer)
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
activeController.set(buffer.readResourceLocation(), forced = true)
activeBehaviour.set(buffer.readResourceLocation(), forced = true)
defaultBehaviourState.decode(buffer)
alternateBehaviourState.decode(buffer)
}
@@ -44,10 +44,10 @@ interface CompositeRidingStrategy<T : CompositeSettings> {
toState.stamina.set(fromState.stamina.get())
toState.rideVelocity.set(fromState.rideVelocity.get())
fromState.reset()
state.activeController.set(toSettings.key)
state.activeBehaviour.set(toSettings.key)
state.lastTransition.set(vehicle.level().gameTime)
val behaviour = RidingBehaviours.get(toSettings.key)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, behaviour.style == RidingStyle.AIR)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, behaviour.getRidingStyle(toSettings, toState) == RidingStyle.AIR)
}
}
@@ -9,13 +9,11 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -47,7 +45,7 @@ object FallStrategy : CompositeRidingStrategy<FallCompositeSettings> {
defaultSettings: RidingBehaviourSettings,
vehicle: PokemonEntity
): Boolean {
if (state.activeController.get() == defaultSettings.key) return false
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (!vehicle.onGround()) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
@@ -61,7 +59,7 @@ object FallStrategy : CompositeRidingStrategy<FallCompositeSettings> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
if (state.activeController.get() == alternateSettings.key) return false
if (state.activeBehaviour.get() == alternateSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
@@ -8,13 +8,11 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.player.Player
@@ -45,7 +43,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
defaultSettings: RidingBehaviourSettings,
vehicle: PokemonEntity
): Boolean {
if (state.activeController.get() == defaultSettings.key) return false
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (!vehicle.onGround()) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
@@ -58,7 +56,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
if (state.activeController.get() == alternativeSettings.key) return false
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
@@ -45,7 +45,7 @@ object RunStrategy : CompositeRidingStrategy<CompositeSettings> {
entity: PokemonEntity,
driver: Player
): Boolean {
if (state.activeController.get() == defaultSettings.key) return false
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (!entity.onGround()) return false
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
@@ -60,7 +60,7 @@ object RunStrategy : CompositeRidingStrategy<CompositeSettings> {
driver: Player,
input: Vec3
): Boolean {
if (state.activeController.get() == alternativeSettings.key) return false
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return driver.isSprinting && input.z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
@@ -55,6 +55,7 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
CobblemonSpawnPools.onServerLoad(server)
}
precalculate()
this.observable.emit(this)
}
val details = mutableListOf<SpawnDetail>()
@@ -12,16 +12,14 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.PokemonGainedEvent
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.PassiveEvolution
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.PassiveTrigger
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.pokemon.OriginalTrainerType
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm
import com.cobblemon.mod.common.util.*
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerPlayer
import net.minecraft.network.chat.Component
import java.util.*
@@ -143,14 +141,16 @@ open class PlayerPartyStore(
}
}
// Passive evolutions
pokemon.lockedEvolutions.filterIsInstance<PassiveEvolution>().forEach { it.attemptEvolution(pokemon) }
val removeList = mutableListOf<Evolution>()
pokemon.evolutionProxy.server().forEach {
if (!it.test(pokemon) && it is LevelUpEvolution && !it.permanent)
removeList.add(it)
}
removeList.forEach { pokemon.evolutionProxy.server().remove(it) }
// Passive evolutions and form changes
pokemon.triggerTransformations(PassiveTrigger::class.java)
// Reevaluate active form
val form = pokemon.form
if (form is TemporaryForm && !form.reevaluate(pokemon)) pokemon.formProxy.server().revert() // TODO check locks?
// Reevaluate transformation requirements and remove invalid transformations from the controller
pokemon.formProxy.server().reevaluate()
pokemon.evolutionProxy.server().reevaluate()
}
// Friendship
// ToDo expand this down the line just a very basic implementation for the first releases
@@ -127,7 +127,8 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
// No PP or disabled or something
return false
}
val availableTargets = (gimmickMove?.target ?: move.target).targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val availableTargets = (if (gimmickID != null && validGimmickMove) gimmickMove.target else move.target)
.targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val pnx = targetPnx ?: return false // If the targets list is non-null then they need to have specified a target
val (_, targetPokemon) = activeBattlePokemon.actor.battle.getActorAndActiveSlotFromPNX(pnx)
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.battles.interpreter.*
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
@@ -22,9 +21,6 @@ import com.cobblemon.mod.common.battles.interpreter.instructions.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.runOnServer
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import java.util.UUID
import kotlin.collections.Iterator
import kotlin.collections.filter
@@ -36,7 +32,6 @@ import kotlin.collections.mutableMapOf
import kotlin.collections.set
import kotlin.collections.toMutableList
import kotlin.collections.toTypedArray
import kotlin.time.measureTime
@Suppress("KotlinPlaceholderCountMatchesArgumentCount", "UNUSED_PARAMETER")
object ShowdownInterpreter {
@@ -72,7 +67,7 @@ object ShowdownInterpreter {
updateInstructionParser["-copyboost"] = { _, _, message, _ -> CopyBoostInstruction(message) }
updateInstructionParser["-crit"] = { _, instructionSet, message, _ -> CritInstruction(message, instructionSet) }
updateInstructionParser["-curestatus"] = { _, _, message, _ -> CureStatusInstruction(message) }
updateInstructionParser["detailschange"] = { _, _, message, _ -> FormeChangeInstruction(message) }
updateInstructionParser["detailschange"] = { _, _, message, _ -> FormChangeInstruction(message) }
updateInstructionParser["-endability"] = { _, _, message, _ -> EndAbilityInstruction(message) }
updateInstructionParser["-end"] = { _, _, message, _ -> EndInstruction(message) }
updateInstructionParser["-enditem"] = { _, _, message, _ -> EndItemInstruction(message) }
@@ -81,7 +76,7 @@ object ShowdownInterpreter {
updateInstructionParser["-fieldactivate"] = { _, _, message, _ -> FieldActivateInstruction(message) }
updateInstructionParser["-fieldend"] = { _, _, message, _ -> FieldEndInstruction(message) }
updateInstructionParser["-fieldstart"] = { _, _, message, _ -> FieldStartInstruction(message) }
updateInstructionParser["-formechange"] = { _, _, message, _ -> FormeChangeInstruction(message) }
updateInstructionParser["-formechange"] = { _, _, message, _ -> FormChangeInstruction(message) }
updateInstructionParser["-hitcount"] = { _, _, message, _ -> HitCountInstruction(message) }
updateInstructionParser["-immune"] = { _, _, message, _ -> ImmuneInstruction(message) }
updateInstructionParser["-invertboost"] = { _, _, message, _ -> InvertBoostInstruction(message) }
@@ -12,10 +12,10 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.pokemon.transformation.progress.LastBattleCriticalHitsTransformationProgress
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-crit|POKEMON
@@ -39,9 +39,10 @@ class CritInstruction(val message: BattleMessage, val instructionSet: Instructio
} else {
battle.broadcastChatMessage(battleLang("crit").yellow())
}
if (LastBattleCriticalHitsEvolutionProgress.supports(battlePokemon.effectedPokemon)) {
val progress = battlePokemon.effectedPokemon.evolutionProxy.current().progressFirstOrCreate({ it is LastBattleCriticalHitsEvolutionProgress }) { LastBattleCriticalHitsEvolutionProgress() }
progress.updateProgress(LastBattleCriticalHitsEvolutionProgress.Progress(progress.currentProgress().amount + 1))
if (LastBattleCriticalHitsTransformationProgress.supports(battlePokemon.effectedPokemon)) {
// TODO refactor and apply to forms
val progress = battlePokemon.effectedPokemon.evolutionProxy.current().progressFirstOrCreate({ it is LastBattleCriticalHitsTransformationProgress }) { LastBattleCriticalHitsTransformationProgress() }
progress.updateProgress(LastBattleCriticalHitsTransformationProgress.Progress(progress.currentProgress().amount + 1))
}
}
battle.minorBattleActions[pokemon.uuid] = message
@@ -25,10 +25,9 @@ import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.transformation.progress.DamageTakenTransformationProgress
import com.cobblemon.mod.common.pokemon.transformation.progress.RecoilTransformationProgress
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
@@ -75,7 +74,7 @@ class DamageInstruction(
private fun doRecoilEvoChecks(battlePokemon: BattlePokemon) {
battlePokemon.effectedPokemon.let { pokemon ->
if (RecoilEvolutionProgress.supports(pokemon)) {
if (RecoilTransformationProgress.supports(pokemon)) {
val healthStr = privateMessage.argumentAt(1) ?: throw UnsupportedOperationException(
"Cant get recoil string"
)
@@ -84,9 +83,10 @@ class DamageInstruction(
?: throw UnsupportedOperationException("Cant get recoil string")
val difference = pokemon.currentHealth - newHealth
if (difference > 0) {
// TODO refactor and apply to forms
val progress = pokemon.evolutionProxy.current()
.progressFirstOrCreate({ it is RecoilEvolutionProgress }) { RecoilEvolutionProgress() }
progress.updateProgress(RecoilEvolutionProgress.Progress(progress.currentProgress().recoil + difference))
.progressFirstOrCreate({ it is RecoilTransformationProgress }) { RecoilTransformationProgress() }
progress.updateProgress(RecoilTransformationProgress.Progress(progress.currentProgress().amount + difference))
}
}
}
@@ -180,9 +180,10 @@ class DamageInstruction(
battlePokemon.effectedPokemon.currentHealth = remainingHealth
if (difference > 0) {
battlePokemon.effectedPokemon.let { pokemon ->
if (DamageTakenEvolutionProgress.supports(pokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DamageTakenEvolutionProgress }) { DamageTakenEvolutionProgress() }
progress.updateProgress(DamageTakenEvolutionProgress.Progress(progress.currentProgress().amount + difference))
if (DamageTakenTransformationProgress.supports(pokemon)) {
// TODO refactor and apply to forms
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DamageTakenTransformationProgress }) { DamageTakenTransformationProgress() }
progress.updateProgress(DamageTakenTransformationProgress.Progress(progress.currentProgress().amount + difference))
}
}
}
@@ -0,0 +1,108 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.battle.BattleTransformPokemonPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
/**
* Format: |-formechange|POKEMON|SPECIES|HP STATUS or |detailschange|POKEMON|DETAILS|HP STATUS
*
* POKEMON has temporarily (-formechange) or permanently (detailschange) changed form to SPECIES/DETAILS.
* @author Segfault Guy
* @since December 9th, 2024
*/
class FormChangeInstruction(val message: BattleMessage): ActionEffectInstruction {
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("formchange")
lateinit var battlePokemon: BattlePokemon
lateinit var speciesName: String
lateinit var formId: String
lateinit var typeKey: String
lateinit var pnx: String
lateinit var form: FormData
override fun preActionEffect(battle: PokemonBattle) {
val details = message.argumentAt(1)?.split(',')?.get(0)?.lowercase() ?: return
battlePokemon = message.battlePokemon(0, battle) ?: return
speciesName = details.substringBefore('-')
formId = details.substringAfter('-').ifBlank { speciesName }
typeKey = if (message.id == "detailschange") "permanent" else "temporary"
pnx = message.pnxAndUuid(0)?.first ?: return
ShowdownInterpreter.broadcastOptionalAbility(battle, message.effect(), battlePokemon)
battle.dispatchGo {
battle.minorBattleActions[battlePokemon.uuid] = message
form = battlePokemon.effectedPokemon.species.getFormByShowdownId(formId)
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formId))
}
}
override fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime) {
battle.dispatch {
if (form is TemporaryForm) {
val actionEffect = (form as TemporaryForm).actionEffect
val providers = mutableListOf<Any>(battle)
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers,
level = battle.players.firstOrNull()?.level()
)
(form as TemporaryForm).forceStart(battlePokemon.effectedPokemon.entity!!, context)
}
else {
battlePokemon.effectedPokemon.formProxy.server().revert()
}
return@dispatch GO
}
}
override fun postActionEffect(battle: PokemonBattle) {
battle.dispatchWaiting {
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formId))
val pokemonName = battlePokemon.getName()
val lang = when (formId) {
"busted", "hero", "complete" -> return@dispatchWaiting
"school", "wishiwashi", "meteor", "minior" -> battleLang("formechange.$formId", pokemonName)
speciesName -> battleLang("formechange.default.temporary.end", pokemonName)
else -> battleLang("formechange.default.$typeKey", pokemonName, formId)
}
battle.sendSidedUpdate(
source = battlePokemon.actor,
allyPacket = BattleTransformPokemonPacket(pnx, battlePokemon, true),
opponentPacket = BattleTransformPokemonPacket(pnx, battlePokemon, false)
)
battle.broadcastChatMessage(lang)
}
}
}
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-formechange|POKEMON|SPECIES|HP STATUS or |detailschange|POKEMON|DETAILS|HP STATUS
*
* POKEMON has temporarily (-formechange) or permanently (detailschange) changed form to SPECIES/DETAILS.
* @author Segfault Guy
* @since December 9th, 2024
*/
class FormeChangeInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val battlePokemon = message.battlePokemon(0, battle) ?: return
val details = message.argumentAt(1)?.split(',')?.get(0)?.lowercase() ?: return
val speciesName = details.substringBefore('-')
val formName = details.substringAfterLast('-').ifBlank { speciesName }
val typeKey = if (message.id == "detailschange") "permanent" else "temporary"
ShowdownInterpreter.broadcastOptionalAbility(battle, message.effect(), battlePokemon)
battle.dispatchWaiting {
battle.minorBattleActions[battlePokemon.uuid] = message
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName))
val pokemonName = battlePokemon.getName()
val lang = when(formName) {
"busted", "hero", "complete" -> return@dispatchWaiting
"school", "wishiwashi", "meteor", "minior" -> battleLang("formechange.$formName", pokemonName)
speciesName -> battleLang("formechange.default.temporary.end", pokemonName)
else -> battleLang("formechange.default.$typeKey", pokemonName, formName)
}
battle.broadcastChatMessage(lang)
}
}
}
@@ -26,7 +26,7 @@ import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
import com.cobblemon.mod.common.pokemon.transformation.progress.UseMoveTransformationProgress
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
@@ -70,9 +70,10 @@ class MoveInstruction(
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull() { poke -> poke.getPNX() == it }?.battlePokemon }
userPokemon.effectedPokemon.let { pokemon ->
if (UseMoveEvolutionProgress.supports(pokemon, move)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is UseMoveEvolutionProgress && it.currentProgress().move == move }) { UseMoveEvolutionProgress() }
progress.updateProgress(UseMoveEvolutionProgress.Progress(move, progress.currentProgress().amount + 1))
// TODO refactor and apply to forms
if (UseMoveTransformationProgress.supports(pokemon, move)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is UseMoveTransformationProgress && it.currentProgress().move == move }) { UseMoveTransformationProgress() }
progress.updateProgress(UseMoveTransformationProgress.Progress(move, progress.currentProgress().amount + 1))
}
}
@@ -27,8 +27,8 @@ import com.cobblemon.mod.common.pokemon.Nature
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.server
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import java.util.function.Function
@@ -42,10 +42,12 @@ open class BattlePokemon(
companion object {
fun safeCopyOf(pokemon: Pokemon): BattlePokemon {
//TOOD figure out a closer registry access (might have to break some method signatures for this (1.7?)
val effectedPokemon = pokemon.clone(registryAccess = server()?.registryAccess() ?: throw IllegalStateException("No registry access available"))
val effectedPokemon = pokemon.clone()
BattleCloneProperty.isBattleClone().apply(effectedPokemon)
UncatchableProperty.uncatchable().apply(effectedPokemon)
val shouldRevertForm = (effectedPokemon.form as? TemporaryForm)?.revertBeforeBattle ?: (effectedPokemon.form as? TemporaryForm)?.battleOnly ?: false
if (shouldRevertForm)
effectedPokemon.formProxy.server().revert()
return BattlePokemon(
originalPokemon = pokemon,
effectedPokemon = effectedPokemon,
@@ -53,13 +55,18 @@ open class BattlePokemon(
)
}
fun playerOwned(pokemon: Pokemon): BattlePokemon = BattlePokemon(
originalPokemon = pokemon,
effectedPokemon = pokemon,
postBattleEntityOperation = { entity ->
entity.effects.wipe()
}
)
fun playerOwned(pokemon: Pokemon): BattlePokemon {
val shouldRevertForm = (pokemon.form as? TemporaryForm)?.revertBeforeBattle ?: (pokemon.form as? TemporaryForm)?.battleOnly ?: false
if (shouldRevertForm)
pokemon.formProxy.server().revert()
return BattlePokemon(
originalPokemon = pokemon,
effectedPokemon = pokemon,
postBattleEntityOperation = { entity ->
entity.effects.wipe()
}
)
}
}
val struct = QueryStruct(
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
import com.cobblemon.mod.common.battles.runner.socket.SocketShowdownService
import com.google.gson.JsonArray
import java.util.UUID
@@ -157,12 +157,16 @@ class GraalShowdownService : ShowdownService {
val jsArray = this.context.eval("js", "new Array();")
var index = 0L
PokemonSpecies.species.forEach { species ->
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownSpecies(species, null)))
species.forms.forEach { form ->
if (form != species.standardForm) {
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownSpecies(species, form)))
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownStandardForm(species.standardForm)))
species.permanentForms.forEach { permForm ->
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownPermanentForm(permForm)))
permForm.changeableForms.forEach { tempForm ->
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownTemporaryForm(tempForm)))
}
}
species.temporaryForms.forEach { tempForm ->
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownTemporaryForm(tempForm)))
}
}
receiveSpeciesDataFn.execute(jsArray)
}
@@ -123,8 +123,8 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
return gson.fromJson(response, JsonArray::class.java)
}
private fun sendSpeciesData(species: Species, form: FormData?) {
writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n")
private fun sendSpeciesData(species: PokemonSpecies.ShowdownSpecies) {
writer.write(">receiveSpeciesData ${gson.toJson(species)}\n")
acknowledge()
}
@@ -137,12 +137,16 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
writer.write(">resetSpeciesData\n")
acknowledge()
PokemonSpecies.species.forEach { species ->
sendSpeciesData(species, null)
species.forms.forEach { form ->
if (form != species.standardForm) {
sendSpeciesData(species, form)
sendSpeciesData(PokemonSpecies.ShowdownStandardForm(species.standardForm))
species.permanentForms.forEach { permForm ->
sendSpeciesData(PokemonSpecies.ShowdownPermanentForm(permForm))
permForm.changeableForms.forEach { tempForm ->
sendSpeciesData(PokemonSpecies.ShowdownTemporaryForm(tempForm))
}
}
species.temporaryForms.forEach { form ->
sendSpeciesData(PokemonSpecies.ShowdownTemporaryForm(form))
}
}
}
@@ -344,4 +344,6 @@ object CobblemonClient {
}
}
/** Checks if the player is preoccupied and cannot accept an action. */
fun isBusy() = battle != null || trade != null
}
@@ -14,7 +14,9 @@ import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.ForcePassActionResponse
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import java.util.UUID
@@ -80,6 +82,10 @@ class ClientBattle(
}
}
fun pendingGimmick(gimmick: ShowdownMoveset.Gimmick): Boolean {
return pendingActionRequests.none { (it.response as? MoveActionResponse)?.gimmickID == gimmick.id }
}
fun getPokemonFromPNX(pnx: String): Pair<ClientBattleActor, ActiveClientBattlePokemon> {
val actor = sides.flatMap { it.actors }.find { it.showdownId == pnx.substring(0, 2) }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown actor")
@@ -104,6 +104,11 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
// force a model update - handles edge case where the PosableState's tracked PosableModel isn't updated until the LivingEntityRenderer render is run
currentModel = VaryingModelRepository.getPoser(identifier, this)
currentEntity.refreshRiding()
}
else if (data == PokemonEntity.FORM) {
val formId = currentEntity.entityData.get(PokemonEntity.FORM)
currentEntity.pokemon.form = currentEntity.pokemon.species.getFormByShowdownId(formId)
currentEntity.refreshRiding()
} else if (data == PokemonEntity.ASPECTS) {
currentAspects = currentEntity.entityData.get(PokemonEntity.ASPECTS)
currentEntity.pokemon.shiny = currentAspects.contains("shiny")
@@ -75,6 +75,7 @@ fun drawProfilePokemon(
scale: Float = 20F,
applyProfileTransform: Boolean = true,
applyBaseScale: Boolean = false,
doQuirks: Boolean = false,
r: Float = 1F,
g: Float = 1F,
b: Float = 1F,
@@ -101,7 +102,7 @@ fun drawProfilePokemon(
context.put(RenderContext.ASPECTS, state.currentAspects)
context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.PROFILE)
context.put(RenderContext.POSABLE_STATE, state)
context.put(RenderContext.DO_QUIRKS, false)
context.put(RenderContext.DO_QUIRKS, doQuirks)
state.currentModel = model
@@ -321,6 +321,7 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
scale = 18F * (ballState?.scale ?: 1F) * if (isCompact) 0.65F else 1.0f,
contextScale = species.getForm(state.currentAspects).baseScale,
reversed = reversed,
doQuirks = false,
state = state,
partialTicks = partialTicks
)
@@ -13,11 +13,11 @@ import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGimmickButton.GimmickTile
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.toRGB
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.sounds.SoundEvents
@@ -30,7 +30,7 @@ import net.minecraft.sounds.SoundEvents
* @author Segfault Guy
* @since July 8th, 2023
*/
abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Float, val y: Float) {
abstract class BattleGimmickButton(val gimmick: ShowdownMoveset.Gimmick, val x: Float, val y: Float) {
companion object {
const val WIDTH = 36
@@ -61,24 +61,32 @@ abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Floa
var toggled = false
private val sfx = SimpleSoundInstance.forUI(SoundEvents.ANVIL_LAND, 1.0F)
private val texture = gimmick.id
val selectable: Boolean
get() {
val battle = CobblemonClient.battle ?: return false
return battle.pendingGimmick(gimmick)
}
fun render(matrices: PoseStack, mouseX: Int, mouseY: Int, delta: Float) {
fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = matrices,
matrixStack = graphics.pose(),
texture = cobblemonResource("textures/gui/battle/battle_gimmick_${texture}.png"),
x = x * 2,
y = y * 2,
height = HEIGHT,
width = WIDTH,
vOffset = if (toggled || isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if ((toggled || isHovered(mouseX.toDouble(), mouseY.toDouble()) && selectable)) HEIGHT else 0,
textureHeight = HEIGHT * 2,
scale = SCALE
scale = SCALE,
alpha = if (selectable) 1.0F else 0.5F
)
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + XOFF && mouseY >= y && mouseY <= y + YOFF
fun toggle(): Boolean {
if (!selectable)
return toggled
toggled = !toggled
Minecraft.getInstance().soundManager.play(sfx)
return toggled
@@ -74,7 +74,8 @@ class BattleMoveSelection(
BattleGimmickButton.create(gimmick, this, backButton.x + xOff, backButton.y)
}
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F )
val shiftButton = if (request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c'))
BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F ) else null
open class MoveTile(
val moveSelection: BattleMoveSelection,
@@ -190,31 +191,30 @@ class BattleMoveSelection(
}
backButton.render(context, mouseX, mouseY, delta)
gimmickButtons.forEach {
it.render(context.pose(), mouseX, mouseY, delta)
}
if(this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')) {
shiftButton.render(context, mouseX, mouseY, delta)
it.render(context, mouseX, mouseY, delta)
}
shiftButton?.render(context, mouseX, mouseY, delta)
}
override fun mousePrimaryClicked(mouseX: Double, mouseY: Double): Boolean {
val move = moveTiles.find { it.isHovered(mouseX, mouseY) }
val gimmick = gimmickButtons.find { it.isHovered(mouseX, mouseY) }
if (move != null) {
if(this.request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
if (this.request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
move.onClick()
} else {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move))
return true
} else if (move.selectable) {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move, move.response.gimmickID, move.move.gimmickMove))
playDownSound(Minecraft.getInstance().soundManager)
return true
}
return true
} else if (backButton.isHovered(mouseX, mouseY)) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.changeActionSelection(null)
} else if (gimmick != null) {
} else if (gimmick != null && gimmick.selectable) {
gimmickButtons.filter { it != gimmick }.forEach { it.toggled = false }
moveTiles = if (gimmick.toggle()) gimmick.tiles else baseTiles
} else if(shiftButton.isHovered(mouseX,mouseY)) {
} else if (shiftButton?.isHovered(mouseX,mouseY) == true) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
@@ -254,6 +254,7 @@ class BattleSwitchPokemonSelection(
matrixStack = matrixStack,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
doQuirks = false,
scale = 4.5F,
partialTicks = deltaTicks
)
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.InBattleGimmickMove
import com.cobblemon.mod.common.battles.InBattleMove
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
@@ -22,7 +23,6 @@ import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection.Companion
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -41,9 +41,11 @@ import kotlin.math.floor
import kotlin.math.sin
class BattleTargetSelection(
battleGUI: BattleGUI,
request: SingleActionRequest,
val move: InBattleMove
battleGUI: BattleGUI,
request: SingleActionRequest,
val move: InBattleMove,
val gimmickID: String?,
gimmickMove: InBattleGimmickMove?
) : BattleActionSelection(
battleGUI = battleGUI,
request = request,
@@ -80,10 +82,10 @@ class BattleTargetSelection(
}
val targets = request.activePokemon.getAllActivePokemon()
val targetType = if (gimmickID != null && gimmickMove != null) gimmickMove.target else move.target
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val selectableTargetList = move.target.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(move.target) else null
val selectableTargetList = targetType.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(targetType) else null
val baseTiles = targets.mapIndexed { index, target ->
val isAlly = target.isAllied(request.activePokemon)
@@ -123,7 +125,7 @@ class BattleTargetSelection(
private val isMultiTarget = multiTargetList?.firstOrNull { it.getPNX() == target.getPNX() } != null
private val isCurrentPokemon = request.activePokemon.battlePokemon?.uuid == target.battlePokemon!!.uuid
open val response: MoveActionResponse get() = MoveActionResponse(targetSelection.move.id, responseTarget)
open val response: MoveActionResponse get() = MoveActionResponse(targetSelection.move.id, responseTarget, targetSelection.gimmickID)
val state = FloatingState()
open val selectable: Boolean get() = isMultiTarget || (responseTarget != null)
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokemon.PokemonSortMode
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.storage.pc.search.Search
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
@@ -19,6 +20,9 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.iconCosmeticItemResource
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.iconHeldItemResource
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
@@ -55,20 +59,40 @@ class PCGUI(
companion object {
const val BASE_WIDTH = 349
const val BASE_HEIGHT = 205
const val INFO_BOX_WIDTH = 63
const val INFO_BOX_HEIGHT = 69
const val RIGHT_PANEL_WIDTH = 82
const val RIGHT_PANEL_HEIGHT = 169
const val TYPE_SPACER_WIDTH = 128
const val TYPE_SPACER_WIDTH = 126
const val TYPE_SPACER_HEIGHT = 12
const val PC_SPACER_WIDTH = 342
const val PC_SPACER_HEIGHT = 14
const val PORTRAIT_SIZE = 66
const val SCALE = 0.5F
const val STAT_INFO = 0
const val STAT_IV = 1
const val STAT_EV = 2
private val statLabels = arrayOf(
lang("ui.stats.hp"),
lang("ui.stats.atk"),
lang("ui.stats.def"),
lang("ui.stats.sp_atk"),
lang("ui.stats.sp_def"),
lang("ui.stats.speed")
)
private val stats = arrayOf(Stats.HP, Stats.ATTACK, Stats.DEFENCE, Stats.SPECIAL_ATTACK, Stats.SPECIAL_DEFENCE, Stats.SPEED)
private val baseResource = cobblemonResource("textures/gui/pc/pc_base.png")
private val portraitBackgroundResource = cobblemonResource("textures/gui/pc/portrait_background.png")
private val infoBoxResource = cobblemonResource("textures/gui/pc/info_box.png")
private val infoBoxStatResource = cobblemonResource("textures/gui/pc/info_box_stats.png")
private val buttonOptionsResource = cobblemonResource("textures/gui/pc/pc_icon_options.png")
private val buttonWallpaperResource = cobblemonResource("textures/gui/pc/pc_button_set_wallpaper.png")
private val buttonInfoArrow = cobblemonResource("textures/gui/pc/info_arrow.png")
private val topSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_top.png")
private val bottomSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_bottom.png")
@@ -87,6 +111,9 @@ class PCGUI(
internal var previewPokemon: Pokemon? = null
var isPreviewInParty: Boolean? = null
private var showCosmeticItem = false
private var currentStatIndex = 0
private val optionButtons: MutableList<IconButton> = mutableListOf()
var search: Search = Search.DEFAULT
@@ -244,6 +271,37 @@ class PCGUI(
this.markingsWidget = MarkingsWidget(x + 29, y + 96.5, null, false)
this.addRenderableWidget(markingsWidget)
// Held/Cosmetic Item Button
addRenderableWidget(
SummaryButton(
buttonX = x + 67F,
buttonY = y + 107F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
)
addRenderableWidget(
IconButton(
pX = x + 1,
pY = y + 157,
buttonWidth = 10,
buttonHeight = 16,
resource = buttonInfoArrow,
label = "switch"
) {
currentStatIndex = (currentStatIndex + 1) % 3
}
)
this.setPreviewPokemon(null)
super.init()
@@ -280,33 +338,68 @@ class PCGUI(
height = BASE_HEIGHT
)
// Render Info Box
blitk(
matrixStack = matrices,
texture = if (currentStatIndex > 0) infoBoxStatResource else infoBoxResource,
x = x + 9,
y = y + 128,
width = INFO_BOX_WIDTH,
height = INFO_BOX_HEIGHT
)
val labelX = x + 9 + (INFO_BOX_WIDTH / 2)
// Render Info Labels
drawScaledText(
context = context,
text = lang("ui.info.nature").bold(),
x = x + 39,
y = y + 129.5,
centered = true,
scale = SCALE
)
when (currentStatIndex) {
STAT_INFO -> {
drawScaledText(
context = context,
text = lang("ui.info.nature").bold(),
x = labelX,
y = y + 129.5,
centered = true,
scale = SCALE
)
drawScaledText(
context = context,
text = lang("ui.info.ability").bold(),
x = x + 39,
y = y + 146.5,
centered = true,
scale = SCALE
)
drawScaledText(
context = context,
text = lang("ui.info.ability").bold(),
x = labelX,
y = y + 146.5,
centered = true,
scale = SCALE
)
drawScaledText(
context = context,
text = lang("ui.moves").bold(),
x = x + 39,
y = y + 163.5,
centered = true,
scale = SCALE
)
drawScaledText(
context = context,
text = lang("ui.moves").bold(),
x = labelX,
y = y + 163.5,
centered = true,
scale = SCALE
)
}
STAT_IV -> {
drawScaledText(
context = context,
text = lang("ui.stats.ivs").bold(),
x = labelX,
y = y + 129.5,
centered = true,
scale = SCALE
)
}
STAT_EV -> {
drawScaledText(
context = context,
text = lang("ui.stats.evs").bold(),
x = labelX,
y = y + 129.5,
centered = true,
scale = SCALE
)
}
}
// Render Pokemon Info
val pokemon = previewPokemon
@@ -398,19 +491,19 @@ class PCGUI(
)
}
// Held Item
val heldItem = pokemon.heldItemNoCopy()
// Held/Cosmetic Item
val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy()
val itemX = x + 3
val itemY = y + 98
if (!heldItem.isEmpty) {
context.renderItem(heldItem, itemX, itemY)
context.renderItemDecorations(Minecraft.getInstance().font, heldItem, itemX, itemY)
if (!displayedItem.isEmpty) {
context.renderItem(displayedItem, itemX, itemY)
context.renderItemDecorations(Minecraft.getInstance().font, displayedItem, itemX, itemY)
}
drawScaledText(
context = context,
text = lang("held_item"),
x = x + 27,
text = lang("${if (showCosmeticItem) "cosmetic" else "held"}_item"),
x = x + 24,
y = y + 108.5,
scale = SCALE
)
@@ -431,7 +524,7 @@ class PCGUI(
blitk(
matrixStack = matrices,
texture = if (pokemon.secondaryType != null) typeSpacerDoubleResource else typeSpacerSingleResource,
x = (x + 7) / SCALE,
x = (x + 9) / SCALE,
y = (y + 118.5) / SCALE,
width = TYPE_SPACER_WIDTH,
height = TYPE_SPACER_HEIGHT,
@@ -439,7 +532,7 @@ class PCGUI(
)
TypeIcon(
x = x + 39,
x = x + 40.5,
y = y + 117,
type = pokemon.primaryType,
secondaryType = pokemon.secondaryType,
@@ -449,43 +542,91 @@ class PCGUI(
centeredX = true
).render(context)
// Nature
val natureText = reformatNatureTextIfMinted(pokemon)
drawScaledText(
context = context,
text = natureText,
x = x + 39,
y = y + 137,
centered = true,
shadow = true,
scale = SCALE,
pMouseX = mouseX,
pMouseY = mouseY
)
when (currentStatIndex) {
STAT_INFO -> {
// Nature
val natureText = reformatNatureTextIfMinted(pokemon)
drawScaledText(
context = context,
text = natureText,
x = labelX,
y = y + 137,
centered = true,
shadow = true,
scale = SCALE,
pMouseX = mouseX,
pMouseY = mouseY
)
// Ability
drawScaledText(
context = context,
text = pokemon.ability.displayName.asTranslated(),
x = x + 39,
y = y + 154,
centered = true,
shadow = true,
scale = SCALE
)
// Ability
drawScaledText(
context = context,
text = pokemon.ability.displayName.asTranslated(),
x = labelX,
y = y + 154,
centered = true,
shadow = true,
scale = SCALE
)
// Moves
val moves = pokemon.moveSet.getMoves()
for (i in moves.indices) {
drawScaledText(
context = context,
text = moves[i].displayName,
x = x + 39,
y = y + 170.5 + (7 * i),
centered = true,
shadow = true,
scale = SCALE
)
// Moves
val moves = pokemon.moveSet.getMoves()
for (i in moves.indices) {
drawScaledText(
context = context,
text = moves[i].displayName,
x = labelX,
y = y + 170.5 + (7 * i),
centered = true,
shadow = true,
scale = SCALE
)
}
}
STAT_IV -> {
for (i in statLabels.indices) {
drawScaledText(
context = context,
text = statLabels[i],
x = x + 13,
y = y + 139 + (10 * i),
shadow = true,
scale = SCALE
)
drawScaledText(
context = context,
text = pokemon.ivs.get(stats[i]).toString().text(),
x = x + 65,
y = y + 139 + (10 * i),
centered = true,
shadow = true,
scale = SCALE
)
}
}
STAT_EV -> {
for (i in statLabels.indices) {
drawScaledText(
context = context,
text = statLabels[i],
x = x + 13,
y = y + 139 + (10 * i),
shadow = true,
scale = SCALE
)
drawScaledText(
context = context,
text = pokemon.evs.get(stats[i]).toString().text(),
x = x + 65,
y = y + 139 + (10 * i),
centered = true,
shadow = true,
scale = SCALE
)
}
}
}
} else {
@@ -537,17 +678,14 @@ class PCGUI(
super.render(context, mouseX, mouseY, delta)
// Item Tooltip
if (pokemon != null && !pokemon.heldItemNoCopy().isEmpty) {
if (pokemon != null) {
val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy()
val itemX = x + 3
val itemY = y + 98
val itemHovered =
mouseX.toFloat() in (itemX.toFloat()..(itemX.toFloat() + 16)) && mouseY.toFloat() in (itemY.toFloat()..(itemY.toFloat() + 16))
if (itemHovered) context.renderTooltip(
Minecraft.getInstance().font,
pokemon.heldItemNoCopy(),
mouseX,
mouseY
)
if (!displayedItem.isEmpty) {
val itemHovered = mouseX.toFloat() in (itemX.toFloat()..(itemX.toFloat() + 16)) && mouseY.toFloat() in (itemY.toFloat()..(itemY.toFloat() + 16))
if (itemHovered) context.renderTooltip(Minecraft.getInstance().font, displayedItem, mouseX, mouseY)
}
}
}
@@ -30,8 +30,11 @@ import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksWi
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveSwapScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.StatWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.transformations.EvolutionTestSelectScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.transformations.FormSelectScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.RevertFormPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
@@ -79,6 +82,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
const val PARTY = 0
const val MOVE_SWAP = 1
const val EVOLVE = 2
const val FORM = 3
// Resources
private val baseResource = cobblemonResource("textures/gui/summary/summary_base.png")
@@ -93,6 +97,8 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private val tabIconMoves = cobblemonResource("textures/gui/summary/summary_tab_icon_moves.png")
private val tabIconStats = cobblemonResource("textures/gui/summary/summary_tab_icon_stats.png")
private val tabIconMarks = cobblemonResource("textures/gui/summary/summary_tab_icon_marks.png")
private val formButtonResource = cobblemonResource("textures/gui/summary/summary_form_test_button.png")
private val revertButtonResource = cobblemonResource("textures/gui/summary/summary_revert_test_button.png")
val iconShinyResource = cobblemonResource("textures/gui/summary/icon_shiny.png")
val iconHeldItemResource = cobblemonResource("textures/gui/summary/icon_item_held.png")
val iconCosmeticItemResource = cobblemonResource("textures/gui/summary/icon_item_cosmetic.png")
@@ -176,8 +182,46 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
},
text = lang("ui.evolve"),
resource = evolveButtonResource,
renderRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() && CobblemonClient.battle == null },
clickRequirement = { selectedPokemon.evolutionProxy.client().isNotEmpty() && CobblemonClient.battle == null }
renderRequirement = { selectedPokemon.evolutionProxy.client().canStart() },
clickRequirement = { selectedPokemon.evolutionProxy.client().canStart() }
)
)
// TODO form and revert button are temp for debugging
// Form Button
addRenderableWidget(
SummaryButton(
buttonX = x + 4F,
buttonY = y + 30F,
buttonWidth = 10,
buttonHeight = 10,
clickAction = {
momentarily { displaySideScreen(if (sideScreenIndex == FORM) PARTY else FORM) }
},
resource = formButtonResource,
renderRequirement = { selectedPokemon.formProxy.client().canStart() },
clickRequirement = { selectedPokemon.formProxy.client().canStart() }
)
)
// Temporary form revert button
addRenderableWidget(
SummaryButton(
buttonX = x + 4F,
buttonY = y + 50F,
buttonWidth = 10,
buttonHeight = 10,
clickAction = {
momentarily {
Minecraft.getInstance().player?.clientSideCloseContainer()
playSound(CobblemonSounds.GUI_CLICK)
sendToServer(RevertFormPacket(selectedPokemon.uuid))
}
},
resource = revertButtonResource,
renderRequirement = { selectedPokemon.formProxy.client().canRevert() },
clickRequirement = { selectedPokemon.formProxy.client().canRevert() }
)
)
@@ -327,7 +371,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
this.party.getOrNull(newSelection)?.let { this.selectedPokemon = it }
listenToMoveSet()
displayMainScreen(mainScreenIndex)
children().find { it is EvolutionSelectScreen }?.let(this::removeWidget)
children().find { it is EvolutionTestSelectScreen }?.let(this::removeWidget)
if (::modelWidget.isInitialized) {
modelWidget.pokemon = selectedPokemon.asRenderablePokemon()
@@ -469,12 +513,22 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
EVOLVE -> {
saveActiveMark()
saveMarkings()
sideScreen = EvolutionSelectScreen(
sideScreen = EvolutionTestSelectScreen(
x + 216,
y + 23,
pokemon = selectedPokemon
)
}
FORM -> {
saveActiveMark()
saveMarkings()
sideScreen = FormSelectScreen(
x + 216,
y + 23,
pokemon = selectedPokemon
)
}
}
val element = sideScreen
if (element is Renderable && element is NarratableEntry) {
@@ -674,7 +728,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (sideScreenIndex == MOVE_SWAP || sideScreenIndex == EVOLVE) sideScreen.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
if (sideScreenIndex == MOVE_SWAP || sideScreenIndex == EVOLVE || sideScreenIndex == FORM) sideScreen.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
if (mainScreenIndex == MOVES) mainScreen.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}

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