Compare commits

...
Author SHA1 Message Date
Gertimoshka 64f7556bdc Dont forget the licenses 2026-03-10 22:12:16 +02:00
Gertimoshka 9fea42bce0 Controlify Binds English Localization 2026-02-24 12:08:51 +02:00
Gertimoshka 7abe83c413 Controller Hotbar navigation buttons will now zoom with with pokedex and switch party when holding the sendout button 2026-02-23 15:33:53 +02:00
Gertimoshka 2d70f48176 Additional Changes + Fixes 2026-02-13 16:28:53 +02:00
JPAKx4 8f9c1c40d7 Merge tag '1.7.3' into controllify-compat-ui-navigation 2026-02-09 15:22:27 -06:00
Gertimoshka 89c9cd9fb4 Added Missing Button Guides 2026-02-09 19:54:21 +02:00
Gertimoshka 9335eb9650 And... Done, Gameplay Important GUIs complete 2026-02-08 11:31:06 +02:00
Gertimoshka 7a7be6f25a Even more GUIs done for Controlify Support 2026-02-07 23:28:48 +02:00
Paul e6fda6137e Teeny tiny changelog [skip-ci] 2026-01-31 10:47:13 +10:00
Paul afae39acc8 Missed a bit of changelog [skip-ci] 2026-01-31 09:21:40 +10:00
Paul 37264aff0f Changelog stuff and no snapshot 2026-01-31 09:17:43 +10:00
Paul 2b60e6de77 Merge remote-tracking branch 'origin/lang-updates' 2026-01-31 09:12:27 +10:00
Hiroku 4f29e7457c Merge branch 'feature/riding-scrape' into 'main'
scrape riding data and fix script

See merge request cable-mc/cobblemon!2141
2026-01-30 09:43:44 +00:00
Polymeta 04a6ab31f4 rescrape data
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2026-01-30 09:26:22 +00:00
Hiroku a2cdc847dd Merge branch 'packet-sync-error-fix' into 'main'
Fixed packet sync issue with pokemon interactions, added "chance" requirement type

Closes #1933 and #1938

See merge request cable-mc/cobblemon!2147
2026-01-30 03:04:14 +00:00
阿鱼 0bca678f56 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (9061 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-01-30 02:59:44 +00:00
Paul 067c3607aa Tweak /pctake to check the PC's box count and use that for constraints 2026-01-30 12:20:17 +10:00
Hiroku 823c1f6444 Merge branch 'main' into 'main'
Fixes PcTake not respecting defaultBoxCount

See merge request cable-mc/cobblemon!2146
2026-01-30 02:18:38 +00:00
Paul bfc00635cd Fixed packet sync issue with pokemon interactions, added "chance" requirement type 2026-01-30 12:16:01 +10:00
Hiroku b2e5b85ac5 Merge branch 'Custom-Ball-Resources-updated' into 'main'
updated ball texture locations missing from last update

See merge request cable-mc/cobblemon!2145
2026-01-30 00:24:49 +00:00
阿鱼 2c27476954 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.7% (9034 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-01-29 13:40:10 +00:00
Gertimoshka 76ed55ebe8 Fixes to when Button Guides are Rendered 2026-01-29 09:52:26 +02:00
Gertimoshka b004b13eda Finally PC Block Controller Support (+Pasture) 2026-01-29 09:30:28 +02:00
Auristite aab78d84fc Fixes PcTake not respecting defaultBoxCount
Command is also missing lang option for "cobblemon.command.pctake.removed" (Removing Pokémon from self)
2026-01-29 04:46:25 +00:00
Zack the Ghast cb632c2a65 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.9% (6154 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2026-01-29 01:28:38 +00:00
Dragonwhisper92 8d219f1338 updated ball texture locations 2026-01-28 23:45:08 +00:00
Zack the Ghast 1e6a4ed984 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.7% (6143 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2026-01-28 21:10:25 +00:00
geumu 64240c31fc Translated using Weblate (Korean)
Currently translated at 100.0% (9061 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ko/
2026-01-28 17:35:35 +00:00
Zack the Ghast d2222c3858 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.6% (6129 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2026-01-28 14:44:02 +00:00
geumu c7f31c3e14 Translated using Weblate (Korean)
Currently translated at 100.0% (9061 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ko/
2026-01-28 14:44:01 +00:00
Zack the Ghast 740e846b2d Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.5% (6125 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2026-01-28 08:26:35 +00:00
Zack the Ghast 06f9d60595 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.5% (6123 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2026-01-27 04:12:54 +00:00
Gertimoshka dbe8a1a9f5 A Working Version of Interaction Wheel 2026-01-26 23:03:30 +02:00
JPAKx4 25e8e3c990 Try to begin fixing the interactionWheel 2026-01-26 13:46:41 -06:00
Gertimoshka b84012f98f Rapid Fire 3 Easier Guis for Controlify Support 2026-01-26 21:35:53 +02:00
Zack the Ghast d54439ce14 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.3% (6103 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2026-01-26 02:31:37 +00:00
JPAKx4 a0979b28d3 Merge tag '1.7.2' into controllify-compat-ui-navigation 2026-01-25 09:58:19 -06:00
Teggy 32ee4bb72a Translated using Weblate (Spanish)
Currently translated at 84.3% (7644 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-01-25 13:02:37 +00:00
Polymeta e7d2a46cc0 Bumping snapshot version 2026-01-25 06:57:31 +00:00
Yasito 3ee94ec076 Translated using Weblate (French)
Currently translated at 100.0% (9061 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-25 03:06:53 +00:00
Yasito 589293abc4 Translated using Weblate (French (Canada))
Currently translated at 100.0% (9061 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-25 03:06:49 +00:00
Paul 279865cc7c Resnaps your shot 2026-01-25 11:53:29 +10:00
Paul b597b149c3 Snapnot 2026-01-25 10:18:32 +10:00
Paul b7f592f2a8 Relocate mongodb packages 2026-01-25 10:18:02 +10:00
Paul 70e17c22e7 Merge remote-tracking branch 'origin/lang-updates' 2026-01-25 10:17:49 +10:00
Yasito 51b18ca0f6 Translated using Weblate (French)
Currently translated at 99.6% (9027 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-25 00:16:58 +00:00
Yasito a83f243a3b Translated using Weblate (French (Canada))
Currently translated at 99.9% (9059 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-25 00:16:55 +00:00
Weblate f5b088c708 Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/
2026-01-25 00:14:04 +00:00
Yasito dc454cbb68 Translated using Weblate (French)
Currently translated at 99.5% (9022 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-25 00:13:56 +00:00
Yasito 3b99e38da2 Translated using Weblate (French (Canada))
Currently translated at 99.9% (9057 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-25 00:13:54 +00:00
Paul 483af57964 Merge remote-tracking branch 'origin/lang-updates' 2026-01-25 10:12:48 +10:00
Dmytro Bunnua a941bdddf3 Translated using Weblate (Ukrainian)
Currently translated at 76.9% (6971 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/uk/
2026-01-24 18:24:12 +00:00
Teggy 246ba4a93b Translated using Weblate (Spanish)
Currently translated at 83.9% (7611 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-01-24 18:24:09 +00:00
Gertimoshka f459c64073 Button Guides for our Screens 2026-01-24 15:02:56 +02:00
Paul 3347c084ed More changelog [skip-ci] 2026-01-24 22:42:58 +10:00
Paul 8175cee454 More translations 2026-01-24 22:11:15 +10:00
Paul 37d54053a5 Merge remote-tracking branch 'origin/lang-updates' 2026-01-24 22:10:10 +10:00
Dmytro Bunnua b8846bd74c Translated using Weblate (Ukrainian)
Currently translated at 76.8% (6966 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/uk/
2026-01-24 11:47:21 +00:00
Paul 1716e135c4 Changelog [skip-ci] 2026-01-24 21:45:14 +10:00
Paul 0ac804cfcb Cleanup 2026-01-24 21:20:50 +10:00
Hiroku aecc132814 Merge branch 'pokeballUIResourcePathChange' into 'main'
ball_icon resource location changes

See merge request cable-mc/cobblemon!2113
2026-01-24 11:13:26 +00:00
Hiroku 8b47876dff Merge branch 'Add-Plaques-to-Village-Pokecenters' into 'main'
Adding Plaques to Village Pokecenters

See merge request cable-mc/cobblemon!2139
2026-01-24 09:27:56 +00:00
Slippy ffc108953a Adding Plaques to Village Pokecenters 2026-01-24 01:51:33 -06:00
Paul 0a106174fe Changelog cleanup [skip-ci] 2026-01-24 15:59:27 +10:00
Paul 90210bee3c Fixed cottonee cries being inconsistent due to not having appropriate pose type coverage 2026-01-24 15:13:08 +10:00
Paul 8603cd4ad0 Merge remote-tracking branch 'origin/main' into Expression-stuffs 2026-01-24 13:56:14 +10:00
Hiroku 7b2c6827c2 Merge branch 'Expression-stuffs' into 'main'
Expression stuffs

See merge request cable-mc/cobblemon!2138
2026-01-24 03:55:02 +00:00
Paul 902b4a6d21 Fixed croagunk and toxicroak playing cry animations without the sound 2026-01-24 13:54:41 +10:00
Hiroku a847cb5926 Merge branch 'fix-plain-aprijuice-prefix' into 'main'
Fix Aprijuice 'Plain' prefix

See merge request cable-mc/cobblemon!2137
2026-01-24 03:46:05 +00:00
Paul 050c6f5adc Merge remote-tracking branch 'origin/Expression-stuffs' into Expression-stuffs 2026-01-24 13:41:23 +10:00
Paul fb95fb3cf7 Various cry corrections from implementation 2026-01-24 13:41:11 +10:00
Carmendar d507367411 Bird stamina changes. 2026-01-23 21:30:43 -06:00
Paul faa19cebcb Fixed perma zero stamina on horses when landing from bird 2026-01-24 13:16:38 +10:00
Paul aced374367 Fixed birds falling out of the sky and not gliding all chill like 2026-01-24 12:40:04 +10:00
Paul d424a6f1d3 Fixed stamina stat flowing to crazy negative from bird sometimes 2026-01-24 11:58:25 +10:00
Carmendar 71bb1c32de stamina expression changes for Bird and Hover. Changes to Mantine riding. 2026-01-23 18:56:17 -06:00
Carmendar f726fcedb9 Merge branch 'main' into implement_pokemon 2026-01-23 18:09:48 -06:00
Weblate 77344ccc2f Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/
2026-01-23 17:29:44 +00:00
Dmytro Bunnua 90bc16be90 Translated using Weblate (Ukrainian)
Currently translated at 76.8% (6960 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/uk/
2026-01-23 17:29:37 +00:00
Gustavo Puerari d1b743a01f Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.3% (8908 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-01-23 17:29:33 +00:00
Teggy aed651c0a0 Translated using Weblate (Spanish)
Currently translated at 83.9% (7608 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-01-23 17:29:28 +00:00
Faintful 4c477e1675 Add changelog 2026-01-23 17:36:05 +01:00
Faintful d1cdc6789c Handle case for LOW quality Aprijuice in getName() for proper 'Plain' prefix 2026-01-23 17:33:04 +01:00
geumu cd3127b983 Translated using Weblate (Korean)
Currently translated at 100.0% (9061 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ko/
2026-01-23 13:22:11 +00:00
Watuneni iteite 68f5eff048 Translated using Weblate (Japanese)
Currently translated at 100.0% (9061 of 9061 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-01-23 13:22:06 +00:00
Paul c3d2918a2b Fixed a crash when joining a server and a player is riding nearby 2026-01-23 17:49:57 +10:00
Paul 0432525c06 Throh resize 2026-01-23 17:04:45 +10:00
Paul a60f27e282 Fixed implemented flag missing from the new 1.7.2 pokemon, subtle size changes 2026-01-23 17:04:14 +10:00
Test 5f0182cb7e fix type asset 2026-01-23 01:48:49 -05:00
Paul 4a3ae9407b Translation changelog [skip-ci] 2026-01-23 16:44:32 +10:00
Paul 766c2838d2 Merge remote-tracking branch 'origin/lang-updates' 2026-01-23 14:34:19 +10:00
Hiroku 4cb95589b4 Merge branch 'more-fixes' into 'main'
More fixes

See merge request cable-mc/cobblemon!2134
2026-01-23 04:30:49 +00:00
Hiroku a8d3c2dd52 Merge branch '1.7.2-spawns' into 'main'
Added spawns. Fixed spawns. Updated tags.

Closes #1896

See merge request cable-mc/cobblemon!2131
2026-01-23 04:13:07 +00:00
Test cb5d7b6ad7 rm unused 2026-01-22 23:05:38 -05:00
Test c7dcc64595 sync repo assets 2026-01-22 23:00:10 -05:00
Paul 6573e08da7 Fix for packet handling to ensure all packets are handled on main and without extra delays on Fabric, closes #1903 2026-01-23 13:37:36 +10:00
Paul cd695ade4a Apparently I put more changelog in than was meant to be there 2026-01-23 13:22:37 +10:00
Paul 55cb413cfc Merge remote-tracking branch 'origin/main' into 1.7.2-spawns 2026-01-23 13:11:50 +10:00
Paul 4f1bf7fcbb Changelog for spawns [skip-ci] 2026-01-23 13:11:42 +10:00
Test 154ee80ead fix some block issues 2026-01-22 21:32:51 -05:00
Test 1c9f9bb198 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2026-01-22 20:38:08 -05:00
Test 6836343f11 ride summary 2026-01-22 20:37:46 -05:00
Gertimoshka 8a7abb9a11 Starter Selection Controlified 2026-01-22 23:56:47 +02:00
RedGenesectNinja aa0d1cd943 Updated changelog 2026-01-22 14:44:46 -06:00
Hiroku 798f1da8b3 Merge branch 'main' into 'main'
Issue 1926: Make SpawnBaitInfluence apply all effects correctly

See merge request cable-mc/cobblemon!2130
2026-01-22 13:44:44 +00:00
Andy d98efb52b6 Add changelog line and extend author tag 2026-01-22 08:38:13 -05:00
Kenji_64 2f27a03f08 Alolan Raichu can look around in the menu now (yay) 2026-01-22 13:36:19 +01:00
Kenji_64 e33025b1fa Fixed the weird eye clipping on Dragapult (fr this time) 2026-01-22 13:36:00 +01:00
Kenji_64 ade2481f42 Treecko line has their cries back 2026-01-22 13:35:39 +01:00
Hiroku 576e2301ce Merge branch 'open-actor-classes' into 'main'
Mark the actor classes as open so they can be extended.

See merge request cable-mc/cobblemon!2129
2026-01-22 12:19:06 +00:00
Polymeta a568d45d75 [skip-ci] tidy up changelog take two 2026-01-22 09:58:38 +00:00
Hiroku bc19a7e4c1 Merge branch 'decor-split' into 'main'
Decor split

See merge request cable-mc/cobblemon!2127
2026-01-22 09:42:35 +00:00
Polymeta 616e438f17 [skip-ci] tidy up changelog 2026-01-22 09:40:47 +00:00
Polymeta 9bb11c46d5 Merge branch 'implement_pokemon' into 'main'
Implement pokemon

Closes #1921 and #1800

See merge request cable-mc/cobblemon!2063
2026-01-22 09:28:25 +00:00
Carmendar 5f470bb703 Honchkrow and Mantine camera fixes. 2026-01-21 23:25:53 -06:00
Andy 276f13cea5 1926: Re-add affectWeight comment 2026-01-22 00:08:34 -05:00
Andy ac069687f1 1926: Changed SpawnBaitInfluence to apply all bait effects properly in affectWeight 2026-01-22 00:07:14 -05:00
Deltric f248c22eed Fix initialPos being able to change and changelog 2026-01-21 22:54:53 -06:00
RedGenesectNinja 65a5687f92 Fixed Dusknoir's stomach mouth lips glowing while it sleeps 2026-01-21 22:27:11 -06:00
Deltric ea50fa534b Mark the actor classes as open so they can be extended. 2026-01-21 21:35:03 -06:00
RedGenesectNinja 4bd3c4af58 Updated changelog 2026-01-21 21:20:16 -06:00
RedGenesectNinja 283d80f7ed Implemented Croagunk and Toxicroak's animations 2026-01-21 21:19:22 -06:00
Veraxiel 0238ca80f0 Added spawns. Fixed spawns. Updated tags. 2026-01-21 18:53:40 -05:00
RedGenesectNinja d24c5fe874 Fixed a mistake I made with file placement 2026-01-21 15:32:14 -06:00
RedGenesectNinja e65e21f2ff Fixed Chimecho's cry not playing a sound 2026-01-21 15:21:02 -06:00
Carmendar e1d417718f Added Flygon jump and Mantine ride anims with temp ride stats. 2026-01-21 02:25:05 -06:00
Polymeta d4308b0a4f Merge branch 'fix-battle-memory-leak' into 'main'
Fixed memory leak from battles not being cleaned up after ending.

See merge request cable-mc/cobblemon!2075
2026-01-21 08:09:44 +00:00
Test bc0d530484 loisence 2026-01-20 19:19:53 -05:00
Jerzykv 56650418d2 Implemented Throh and Sawk 2026-01-20 20:32:21 +01:00
Jerzykv 0543b84d34 The battle idle should probably play the battle idle animation 2026-01-20 19:57:45 +01:00
Jerzykv 1f50687a59 Re-added Honchkrow's driving license 2026-01-20 19:53:25 +01:00
Polymeta ed04faf0d8 Merge branch 'dispenser-fix' into 'main'
Resolved Dispenser Behaviors not working

See merge request cable-mc/cobblemon!2118
2026-01-20 14:46:55 +00:00
Aram 1ed020f3e0 Resolved Dispenser Behaviors not working 2026-01-20 14:46:54 +00:00
Polymeta 7e385d8c94 Merge branch 'default-offest' into 'main'
[FEAT] Add `default_seat` option to seat offset posers

See merge request cable-mc/cobblemon!1999
2026-01-20 14:46:44 +00:00
Polymeta ee509b5438 Merge branch 'molang-inventory-and-move-utility' into 'main'
molang additions: Move struct accessor and player inventory utils

See merge request cable-mc/cobblemon!2103
2026-01-20 14:46:34 +00:00
Jerzykv ac9394131c Implemented Litleo and Pyroar - Changelog 2026-01-20 12:01:09 +01:00
Jerzykv d058a306a7 Implemented Litleo and Pyroar 2026-01-20 11:58:48 +01:00
Jerzykv b9be0c3fcb Swirlix and Slurpuff cry fixes 2026-01-20 10:14:16 +01:00
Jerzykv b59b46568a Swirlix and Slurpuff poser changes 2026-01-20 10:14:11 +01:00
Jerzykv 408cbc8c49 Slurpuff and Aromatisse scale changes 2026-01-20 10:13:55 +01:00
Test 1537816132 log of change 2026-01-20 00:56:36 -05:00
Test 1705639950 loot 2026-01-20 00:50:04 -05:00
Test ce32541dd6 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into decor-split 2026-01-19 23:45:28 -05:00
Test 3ffea4f4dd split items 2026-01-19 23:44:20 -05:00
Hiroku f942d7c34a Merge branch 'misc-additions-fixes' into 'main'
Couple of misc API additions and bug fixes.

See merge request cable-mc/cobblemon!2125
2026-01-19 02:16:09 +00:00
Deltric 0f1ada4624 Pass in pnx to the event 2026-01-18 18:52:27 -06:00
Deltric b4ee4629bf Couple of misc API additions and bug fixes. 2026-01-18 17:57:28 -06:00
Gertimoshka 7be8beb71e Pokedex GUI Controlify Support + Rendering Layering fixes 2026-01-18 20:47:56 +02:00
Paul d0b64e2d82 Respect the BYPASSES_INVULNERABILITY damage tag 2026-01-18 22:12:25 +10:00
Jerzykv a8ce3c2175 Fixed Wishiwashi's School Form, Fixes #1921 2026-01-18 12:39:16 +01:00
Jerzykv 80ae127379 Implemented Spritzee and Aromatisse. Smells good. 2026-01-18 12:36:48 +01:00
Jerzykv bd3a9fb3af Implemented Swirlix and Slurpuff. How sweet! 2026-01-18 11:28:15 +01:00
Instrafe d0eaccb81b Fixed Ferroseed and Ferrothorn's posers not identifying a root bone (woops) 2026-01-17 12:12:08 -05:00
Paul 1ed2042283 Fixed /calculateseatpositions expecting no underscore when actually we always use an underscore 2026-01-17 22:32:57 +10:00
Deltric 0b531d967e Merge remote-tracking branch 'origin/fix-battle-memory-leak' into fix-battle-memory-leak
# Conflicts:
#	common/src/main/resources/data/cobblemon/showdown.zip
2026-01-17 03:42:38 -06:00
Deltric b0d1aec9ab Update showdown 2026-01-17 03:42:21 -06:00
Deltric f4d47098b5 Fixed memory leak from battles not being cleaned up after ending. 2026-01-17 03:42:08 -06:00
Instrafe 1989b8aa92 Implemented remodels for Ferroseed and Ferrothorn 2026-01-16 17:06:45 -05:00
Carmendar e290fe9c75 Modified Hover expressions and buffs/nerfs for hover mounts. 2026-01-15 21:23:34 -06:00
Jerzykv 06a50cfeeb Changelog for the cottons 2026-01-15 11:12:17 +01:00
Jerzykv 00888c8ad5 Implemented Cottonee and Whimsicott remodels 2026-01-15 11:11:44 +01:00
Gertimoshka 637274c7ee Battle GUI now has working Controller Support 2026-01-12 19:44:51 +02:00
RedGenesectNinja 3963e83717 Implemented Ducklett and Swanna's cries with placeholder animations 2026-01-12 01:04:46 -06:00
Konoha 7893e365da yolo: drop item on give_item if inventory is full & sneak in a fix for active_dialogue 2026-01-11 18:50:52 -05:00
Hiroku b6a7051b2c Fixed galarian weezing blink crash, closes #1800 2026-01-11 04:28:37 +00:00
Jerzykv 2cb1cf2f83 tiny fix on Togekiss 2026-01-10 13:36:40 +01:00
阿鱼 f13a4e0e24 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-01-08 18:45:37 +00:00
Dragonwhisper92 17e1bbd6a1 oops changelog extra '-' removed 2026-01-07 20:00:26 +00:00
Dragonwhisper92 c36ff0a0fe ball_icon resource location changes
Changed the resource location of pokeball icons in the gui to use the balls namespace instead of cobblemon, allowing mods to properly use their own name space for these textures
2026-01-07 19:57:36 +00:00
Jerzykv 8dbb350230 minor fix to apricorn textures 2026-01-05 21:56:02 +01:00
Jerzykv 52f9ffb1f6 fixed Politoed's drop again. thanks scraper 2026-01-05 12:23:07 +01:00
Lucas 030195e3ff Translated using Weblate (French)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-03 08:52:44 +00:00
Yasito d21a52ae68 Translated using Weblate (French)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-03 08:52:43 +00:00
Yasito 3c1f1dbacb Translated using Weblate (French (Canada))
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-03 08:52:42 +00:00
Lucas 787c4851ff Translated using Weblate (French)
Currently translated at 99.9% (9021 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:02:21 +00:00
Yasito bf6e7fafd3 Translated using Weblate (French)
Currently translated at 99.9% (9021 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:02:20 +00:00
Lucas ea6df764c0 Translated using Weblate (French)
Currently translated at 99.9% (9020 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:01:41 +00:00
Yasito f242de272d Translated using Weblate (French)
Currently translated at 99.9% (9020 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:01:41 +00:00
Lucas 5232e2e9bc Translated using Weblate (French)
Currently translated at 99.9% (9019 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:01:09 +00:00
Yasito ca35898243 Translated using Weblate (French)
Currently translated at 99.9% (9019 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:01:08 +00:00
Lucas 869457d235 Translated using Weblate (French)
Currently translated at 99.9% (9018 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:00:52 +00:00
Yasito 6736c8160b Translated using Weblate (French)
Currently translated at 99.9% (9018 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:00:51 +00:00
Lucas 243c50550d Translated using Weblate (French)
Currently translated at 99.9% (9017 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:00:37 +00:00
Yasito fe7d3ef23a Translated using Weblate (French)
Currently translated at 99.9% (9017 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:00:36 +00:00
Lucas 57ad77fc39 Translated using Weblate (French)
Currently translated at 99.9% (9016 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 21:00:10 +00:00
Lucas d70e7bd1b3 Translated using Weblate (French)
Currently translated at 99.9% (9015 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:59:34 +00:00
Yasito 254cc931ea Translated using Weblate (French)
Currently translated at 99.9% (9015 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:59:34 +00:00
Lucas 01fe5cffe8 Translated using Weblate (French)
Currently translated at 99.9% (9014 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:59:19 +00:00
Yasito 3e18995a37 Translated using Weblate (French)
Currently translated at 99.9% (9014 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:59:18 +00:00
Yasito ff161c9c9b Translated using Weblate (French)
Currently translated at 99.9% (9013 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:58:36 +00:00
Lucas 6b7b4f326d Translated using Weblate (French)
Currently translated at 99.9% (9013 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:58:35 +00:00
Lucas f50f7dec29 Translated using Weblate (French)
Currently translated at 99.8% (9012 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:58:17 +00:00
Yasito a5def20c58 Translated using Weblate (French)
Currently translated at 99.8% (9012 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:58:16 +00:00
Yasito b23204f9c9 Translated using Weblate (French)
Currently translated at 99.8% (9011 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:57:51 +00:00
Lucas 4972de0aaa Translated using Weblate (French)
Currently translated at 99.8% (9011 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:57:50 +00:00
Lucas 58a1f3d7b4 Translated using Weblate (French)
Currently translated at 99.8% (9010 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:57:33 +00:00
Yasito f516edb858 Translated using Weblate (French)
Currently translated at 99.8% (9010 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:57:32 +00:00
Lucas 161aa952fa Translated using Weblate (French)
Currently translated at 99.8% (9008 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:55:43 +00:00
Yasito 3b26a13ad6 Translated using Weblate (French)
Currently translated at 99.8% (9008 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:55:43 +00:00
Lucas 014189217a Translated using Weblate (French)
Currently translated at 99.8% (9007 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:55:29 +00:00
Yasito 978debb40c Translated using Weblate (French)
Currently translated at 99.8% (9007 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:55:28 +00:00
Lucas 5be707811e Translated using Weblate (French)
Currently translated at 99.8% (9006 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:55:13 +00:00
Yasito ac44a98f8a Translated using Weblate (French)
Currently translated at 99.8% (9006 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:55:12 +00:00
Lucas 03ecc2ae8d Translated using Weblate (French)
Currently translated at 99.8% (9005 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:54:13 +00:00
Yasito 1518379107 Translated using Weblate (French)
Currently translated at 99.8% (9005 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:54:12 +00:00
Yasito a58c8c59e1 Translated using Weblate (French)
Currently translated at 99.8% (9004 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:53:58 +00:00
Lucas 37959b5048 Translated using Weblate (French)
Currently translated at 99.8% (9004 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:53:58 +00:00
Yasito cbcc9fc6f1 Translated using Weblate (French (Canada))
Currently translated at 99.7% (9002 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-02 20:53:57 +00:00
Yasito d236aaa7da Translated using Weblate (French)
Currently translated at 99.7% (9000 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:50:35 +00:00
Lucas a2cd5a671c Translated using Weblate (French)
Currently translated at 99.7% (9000 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:50:35 +00:00
Lucas 1d6a45bf4d Translated using Weblate (French)
Currently translated at 99.7% (8998 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:49:59 +00:00
Yasito 5bfe703ed7 Translated using Weblate (French)
Currently translated at 99.7% (8998 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:49:58 +00:00
Yasito fc8485388e Translated using Weblate (French)
Currently translated at 99.7% (8997 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:45:38 +00:00
Lucas a5593b3131 Translated using Weblate (French)
Currently translated at 99.7% (8997 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:45:38 +00:00
Yasito 9541bf3267 Translated using Weblate (French (Canada))
Currently translated at 99.6% (8991 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-02 20:45:37 +00:00
Lucas 1037fe0a0e Translated using Weblate (French)
Currently translated at 99.6% (8989 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:38:19 +00:00
Yasito a6eea3198a Translated using Weblate (French)
Currently translated at 99.6% (8989 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:38:19 +00:00
Yasito b28e2313a8 Translated using Weblate (French (Canada))
Currently translated at 99.6% (8988 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-02 20:38:19 +00:00
Lucas 0f0b4e6c86 Translated using Weblate (French)
Currently translated at 99.6% (8988 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:37:45 +00:00
Yasito e033467054 Translated using Weblate (French)
Currently translated at 99.6% (8988 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:37:45 +00:00
Yasito 0f092bf6bf Translated using Weblate (French (Canada))
Currently translated at 99.6% (8987 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-02 20:37:44 +00:00
Lucas 66ba762360 Translated using Weblate (French)
Currently translated at 99.6% (8987 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:37:01 +00:00
Yasito c7c11838a0 Translated using Weblate (French)
Currently translated at 99.6% (8987 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2026-01-02 20:37:01 +00:00
Yasito 301d80fabe Translated using Weblate (French (Canada))
Currently translated at 99.6% (8986 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-01-02 20:37:00 +00:00
Konoha fade5502d9 molang additions: Move struct accessor and player inventory utils 2026-01-01 15:31:40 -05:00
Gertimoshka 63786357b9 Finally working Summary GUI for contrilify with Visuals 2025-12-31 15:40:55 +02:00
Test fc720916fb wall block properties 2025-12-31 00:24:39 -05:00
Jerzykv ce55035408 changelog 2025-12-30 14:46:38 +01:00
Jerzykv d4665ddaeb implemented Falinks remodel 2025-12-30 14:44:46 +01:00
Jerzykv 4375c6f667 fixed Revavroom's hitbox. Who implemented this guy (it was me) 2025-12-30 14:43:24 +01:00
Gertimoshka fa3e85b054 Summary GUI Controllify Support, Functional Implementation 2025-12-25 16:54:51 +02:00
Gertimoshka 61c1c6b21f Additional Fixes for Rebase 2025-12-23 09:46:12 +02:00
Gertimoshka a347844986 Summary GUI - navigation for moves tab and party tab 2025-12-23 09:45:14 +02:00
Gertimoshka d78a92f069 Rebasing for New Changes 2025-12-23 09:44:57 +02:00
Test e5699c1cdc plaques, policies 2025-12-22 01:34:10 -05:00
RedGenesectNinja f4a7c6e873 Implemented the remodels for Chingling and Chimecho 2025-12-21 22:40:17 -06:00
geumu 3bef1dad4a Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-20 19:34:20 +00:00
JPAKx4 ba3207d176 Work on a framework to make UI stuff not suck 2025-12-20 12:23:48 -06:00
geumu da50ee59cf Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-20 14:40:56 +00:00
arvid6 6e8985d97e Translated using Weblate (Spanish)
Currently translated at 84.2% (7599 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-12-19 08:15:16 +00:00
geumu 19c4c0d50d Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-19 08:15:15 +00:00
阿鱼 b23110950e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-12-19 08:15:14 +00:00
Watuneni iteite 60c213c44b Translated using Weblate (Japanese)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-12-19 08:15:13 +00:00
geumu 3df8dea71b Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-18 17:09:19 +00:00
geumu 3cca50b7cc Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-18 07:49:53 +00:00
geumu 1ff5622694 Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-18 06:01:40 +00:00
JPAKx4 762f9ce5f1 Merge branch 'main' into controllify-compat 2025-12-17 22:24:43 -06:00
JPAKx4 6c12198344 Improve InteractWheelProcessor.kt and use controlify sensitivity 2025-12-17 22:24:29 -06:00
Alexis Salgueiro 411f388bd3 Translated using Weblate (French)
Currently translated at 99.5% (8984 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-12-18 00:21:32 +00:00
geumu 5b8d35604a Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-18 00:21:30 +00:00
geumu 619d002bc4 Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-17 07:19:25 +00:00
geumu 7049656f64 Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-16 06:33:05 +00:00
geumu e6e7c2982d Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-15 12:47:01 +00:00
Watuneni iteite 82686b0511 Translated using Weblate (Japanese)
Currently translated at 99.4% (8976 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-12-12 03:20:38 +00:00
JPAKx4 f4eca30163 Fix merge issues 2025-12-11 10:40:53 -06:00
arvid6 2814183012 Translated using Weblate (Spanish)
Currently translated at 84.2% (7597 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-12-11 16:38:11 +00:00
JPAKx4 bbc3c2a8d6 Merge branch 'main' into default-offest
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/camera/MountedCameraRenderer.kt
2025-12-11 10:27:50 -06:00
geumu 7eeb7feeae Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-11 06:27:41 +00:00
geumu b2f850c1c8 Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-10 15:57:39 +00:00
Nico Merenq 23db346070 Translated using Weblate (Russian)
Currently translated at 77.7% (7012 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-12-09 19:49:13 +00:00
geumu d3b7d739bb Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-09 19:49:11 +00:00
JPAKx4 99a0ec7f0d Merge remote-tracking branch 'origin/controllify-compat' into controllify-compat
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/integration/controlify/CobblemonControlifyEntrypoint.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/integration/controlify/gui/InteractWheelProcessor.kt
2025-12-08 22:38:47 -06:00
JPAKx4 aacebc9726 Add changelog, handle horse sprinting, add all UIs to virtual mouse. 2025-12-08 22:37:43 -06:00
JPAKx4 378c85b7dd More work on keybinds for Controlify compat 2025-12-08 22:20:26 -06:00
JPAKx4 c4614ff418 Add radial menu support for controlify 2025-12-08 22:20:26 -06:00
JPAK 8cbbd2de27 Move compat to common for neoforge and fabric support. 2025-12-08 22:20:26 -06:00
JPAKx4 067c59a636 Continue work on controlify compat 2025-12-08 22:20:26 -06:00
JPAKx4 98a02438da Begin ControlifyCompat 2025-12-08 22:20:25 -06:00
geumu eb6c134f4b Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-08 10:48:16 +00:00
JPAKx4 26b25c8c39 More work on keybinds for Controlify compat 2025-12-07 17:01:04 -06:00
geumu aa0b8d88e8 Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-07 12:09:09 +00:00
JPAKx4 bfd78c1a85 Merge remote-tracking branch 'origin/controllify-compat' into controllify-compat 2025-12-06 17:10:33 -06:00
JPAKx4 239485ccb5 Add radial menu support for controlify 2025-12-06 17:10:20 -06:00
JPAKx4 ff959daaef Add radial menu support for controlify 2025-12-06 16:57:09 -06:00
Deltric 3cf9425247 Fixed memory leak from battles not being cleaned up after ending. 2025-12-06 01:35:53 -06:00
JPAK 32bb340260 Move compat to common for neoforge and fabric support. 2025-12-05 21:36:59 -06:00
Carmendar b5578e72bd Merge branch 'main' into implement_pokemon 2025-12-05 01:30:51 -06:00
Carmendar 3aea0909a4 Merge branch 'main' into implement_pokemon 2025-12-05 01:27:25 -06:00
Teggy 5adef3336e Translated using Weblate (Spanish)
Currently translated at 84.2% (7597 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-12-05 06:23:53 +00:00
geumu 1a96b418bc Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-05 06:23:51 +00:00
geumu e373e86b60 Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-04 15:33:38 +00:00
geumu 6f3035184c Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-04 09:30:13 +00:00
Jerzykv 7ae8b1957c fixed Arcanine's texture having a line 2025-12-04 10:27:42 +01:00
geumu 824b9718ee Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-04 08:07:32 +00:00
geumu ef43f6c087 Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-03 21:23:55 +00:00
geumu c8a70caf79 Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-03 16:30:22 +00:00
JPAKx4 b7bfc6d0a1 Continue work on controlify compat 2025-12-03 07:01:01 -06:00
geumu ba11758e1f Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-03 12:53:16 +00:00
geumu f0412d6b1d Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-03 10:05:08 +00:00
Jerzykv f5f87ce1a3 fixed Dewgong swimming in battle 2025-12-03 06:57:40 +01:00
Jerzykv f28b269734 fixed Camerupt jumping in battle 2025-12-03 06:57:31 +01:00
Jerzykv d545efd843 adjusted Tinkaton & Tinkatuff profile scaling 2025-12-03 06:57:07 +01:00
Zack the Ghast f8682c2123 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.2% (8866 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2025-12-02 05:47:12 +00:00
geumu f62e31a1eb Translated using Weblate (Korean)
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-02 05:46:50 +00:00
Watuneni iteite ea623b08a6 Translated using Weblate (Japanese)
Currently translated at 99.4% (8971 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-12-02 05:46:48 +00:00
geumu ce3d5a413c Translated using Weblate (Korean)
Currently translated at 95.8% (8650 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-01 13:03:30 +00:00
geumu d23fd6e23c Translated using Weblate (Korean)
Currently translated at 84.1% (7595 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-12-01 05:33:07 +00:00
geumu 59395a254d Translated using Weblate (Korean)
Currently translated at 81.2% (7328 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-11-30 20:59:27 +00:00
geumu 5d7c6c8d6f Translated using Weblate (Korean)
Currently translated at 80.6% (7276 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-11-30 08:10:30 +00:00
JPAKx4 d86879095f Begin ControlifyCompat 2025-11-29 17:02:35 -06:00
violet 6529293d21 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-29 02:44:47 -05:00
violet a1fce624ea something was forgotten in the changelog... 2025-11-29 02:44:34 -05:00
geumu b7773f7471 Translated using Weblate (Korean)
Currently translated at 79.1% (7144 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-11-28 23:27:54 +00:00
geumu f927e47bfe Translated using Weblate (Korean)
Currently translated at 77.9% (7030 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ko/
2025-11-28 15:25:08 +00:00
Watuneni iteite 48a37b2018 Translated using Weblate (Japanese)
Currently translated at 99.2% (8953 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-11-28 15:25:07 +00:00
阿鱼 f6b2da6ed6 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (9022 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-11-27 11:09:47 +00:00
Watuneni iteite 275779b20e Translated using Weblate (Japanese)
Currently translated at 99.2% (8953 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-11-26 22:24:14 +00:00
阿鱼 2102f8ff88 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.6% (8991 of 9022 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-11-26 15:42:52 +00:00
JPAKx4 8f462aab40 Add default_seat option to seat offset posers, and remove shouldRotate as it's not needed for positioning the camera. 2025-11-18 00:56:40 -06:00
890 changed files with 54627 additions and 7068 deletions

No files matched your search

+65 -49
View File
@@ -1,11 +1,12 @@
# Changelog
## [1.7.2 (MONTH Xth, YEAR)](#1-7-2)
## [1.7.2 (January 25th, 2026)](#1-7-2)
### Additions
- Added riding statistics: `Distance by Pokémon on Land`, `Distance by Pokémon in Air` and `Distance by Pokémon in Liquid`.
- Added Saccharine wood furniture for Adorn.
- Added the Partner Mark, which party Pokémon have a very small chance to earn as the player walks, provided they have enough friendship.
- Added trades for Saccharine Saplings, Hearty Grains, Chipped Pot, and Masterpiece Teacup to the Wandering Trader.
- Added brewing recipe for Throat Spray.
- Added official Controlify Support
- Added new effects
- Mental Restoration: Reduces "Time Since Last Rest" statistic, which controls Insomnia, by half a minute per second per effect level
- Negative Effect Cleanse: Removes all negative effects
@@ -15,64 +16,79 @@
- White Herb - Negative Effect Cleanse
- Milk - Effect Cleanse
- Moomoo Milk - Effect Cleanse
- Added `label` as an alternate key to the `tag` property.
- Added Saccharine wood furniture for Adorn.
- Added decorative block models for existing items: Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Ether, Elixir, Max Ether, Max Elixir, X Accuracy, X Attack, X Defence, X Special Attack, X Special Defence, X Speed, Dire Hit, Guard Spec, Weakness Policy, Blunder Policy, Cleanse Tag, and Spell Tag.
- Added decorative sign blocks called Plaques.
- Added ride style labels to ride stats in Pokédex, as well as ride style icons in the summary GUI.
- Added more seasonings for sinister tea:
- Mental Herb - Mental Restoration I for 10 seconds, which staves off Insomnia.
- White Herb - Removes negative effects.
- Milk, Moomoo Milk - Removes effects.
- Added dispenser support for:
- Applying a Honey Bottle to Saccharine Logs.
- Washing honey off of Saccharine Slathered Logs.
- Applying and removing honey on Saccharine Leaves.
- Added riding statistics: `Distance by Pokémon on Land`, `Distance by Pokémon in Air` and `Distance by Pokémon in Liquid`.
### New rideable Pokémon
- Honchkrow
- Mantine
### Pokémon Added
#### Gen 5
- Throh
- Sawk
#### Gen 6
- Litleo
- Pyroar
- Spritzee
- Aromatisse
- Swirlix
- Slurpuff
### Model updates for the following Pokémon
- Falinks
- Cottonee
- Whimsicott
- Ferroseed
- Ferrothorn
- Chingling
- Chimecho
### Animation updates for the following Pokémon
- Mantine (ride animations)
- Flygon (ride jump animation)
- Honchkrow (ride animations)
- Croagunk
- Toxicroak
### Changes
- Added a new config option, `Enable In-Flight Dismounting` (default: off), which lets you dismount while riding a Pokémon in the air. This is the textbook definition of caveat emptor.
- Ride sounds have been separated into stereo for passengers and mono for other players. Riding will now sound more spacious.
- Added a new config option, `Enable In-Flight Dismounting` (default: off), which lets you dismount while riding a Pokémon in the air.
- Battle AI now uses a smarter threshold for switching, reducing unnecessary switches.
- AI will always use the most damaging move when at low HP and when it is not switching, improving endgame decision-making.
- Added support for optional message variants in battle activate instructions for more context-specific battle text.
- Example: `this.add('-activate', pokemon, 'ability: example', '[msg]message1');` will parse to lang key `cobblemon.battle.activate.example.message1`
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
- Added `label` as an alternate key to the `tag` property.
## [1.7.3 (January 31st, 2026)](#1-7-3)
### Fixes
- Fixed an issue in the OmniPathNodeMaker/Navigation that prevented Combees from pathing into and out of Saccharine leaves.
- Fixed orientation not getting reset after crashing while flying and getting back on the Pokémon.
- Fixed Furfrou not being trimmable on NeoForge.
- Fixed shiny Pokémon not respecting the silent flag on spawn.
- Fixed honey from Saccharine Leaves being harvestable at less than the max age.
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
- Fix all Pokémon being saved to chunks and never despawning.
- Fixed possible error on world generation when Combees are placed in naturally generated bee nests.
- Fixed PokeSnacks crashing if there was no available spawn.
- Fixed Pokémon spawn influences not being specific to regional forms
- Fixed crashing when viewing another player in spectator mode
- Fixed missing apricorn textures for Adorn blocks
- Fixed crashing when riding due to malformed json file, instead resetting it
- Fixed mochi items consuming two items at a time
- Fix item interaction sometimes not working properly when playing on servers.
- Fixed Soothe Bell not being properly tagged as a held item.
- Fixed Mint Leaves not being usable for filling the Resurrection Machine.
- Added Saccharine Boats and Saccharine Boat with Chests to relevant boat tags.
- Fixed the `run_script` Molang function to not fail if the environment's context is null.
- Fixed issue #1917
- Fixed the movesets of certain Pokémon being incorrectly sanitized upon reload.
- Fixed the ordering of Aprijuice's riding stat boosts to always have the same order.
- Fixed the Molang functions `date_local_time`, `date_of`, `date_is_after` to use the correct date format.
- Fixed the bottom half of pasture blocks having an incorrect hit-box when facing north or south.
- Fixed Galarian Mr. Mime incorrectly being rideable.
- Fixed gimmicks not being usable in double / triple battles
- Fixed gimmick buttons showing when already used in the same turn in double / triple battles
- Fixed Apricorn block model rotation for counters/sinks/cupboards/drawers to match that of most recent version of Adorn.
- Fixed Fresh Start Mochi EV reset not syncing health changes properly.
- Fixed requirements such as `biomeCondition` crashing the game when used to define Pokémon interactions.
- Fixed Pokémon interactions not playing sound effects when the volume was not specified.
- Fixed Pokémon interaction cooldowns taking longer than intended when the Pokémon was in the player’s party.
- Fixed Pokémon interactions occasionally crashing the game when spamming an interaction with a 0 cooldown.
- Fixed Pokémon fullness decreasing more than intended when the Pokémon was pastured.
- Fixed crashing when sending a Pokémon out after editing its form
- Fixed Super Potion recipes incorrectly using Hondew Berry instead of Aguav Berry.
- Fixed mounted Pokémon not playing sounds.
- Fixed players failing to join servers randomly.
- Fixed some Pokémon having no stamina for flying.
- Fixed `/pctake` not respecting the actual size of the PC.
### Developer
- Changed the `owner` parameter in the `OwnerQueryRequirement` interface from `ServerPlayer` to `Player`. This method is now also called on the client to verify whether a Pokémon interaction succeeded, so make sure to update your implementations to handle both server and client contexts.
- Poké Ball GUI textures now respect the ball's namespace, enabling proper texture loading for custom balls from other mods.
### Molang & Datapacks
- Ride sounds can now be set as exclusive to passengers
- Ride sounds no longer play when submerged unless specified with a new setting
- Exception handling has been added to `run_molang`, resolving some crashes caused by malformed MoLang expressions.
### Particles
- Added a `chance` requirement type for Pokémon interactions.
### Localization
- Updated translations for:
- French
- Canadian French
- Korean
- Portuguese
- Simplified Chinese
- Spanish
+137
View File
@@ -0,0 +1,137 @@
# Changelog
## [1.7.2 (January 25th, 2026)](#1-7-2)
### Additions
- Added Saccharine wood furniture for Adorn.
- Added the Partner Mark, which party Pokémon have a very small chance to earn as the player walks, provided they have enough friendship.
- Added decorative block models for existing items: Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Ether, Elixir, Max Ether, Max Elixir, X Accuracy, X Attack, X Defence, X Special Attack, X Special Defence, X Speed, Dire Hit, Guard Spec, Weakness Policy, Blunder Policy, Cleanse Tag, and Spell Tag.
- Added decorative sign blocks called Plaques.
- Added ride style labels to ride stats in Pokédex, as well as ride style icons in the summary GUI.
- Added more seasonings for sinister tea:
- Mental Herb - Mental Restoration I for 10 seconds, which staves off Insomnia.
- White Herb - Removes negative effects.
- Milk, Moomoo Milk - Removes effects.
- Added dispenser support for:
- Applying a Honey Bottle to Saccharine Logs.
- Washing honey off of Saccharine Slathered Logs.
- Applying and removing honey on Saccharine Leaves.
- Added riding statistics: `Distance by Pokémon on Land`, `Distance by Pokémon in Air` and `Distance by Pokémon in Liquid`.
### New rideable Pokémon
- Honchkrow
- Mantine
### Pokémon Added
#### Gen 5
- Throh
- Sawk
#### Gen 6
- Litleo
- Pyroar
- Spritzee
- Aromatisse
- Swirlix
- Slurpuff
### Model updates for the following Pokémon
- Falinks
- Cottonee
- Whimsicott
- Ferroseed
- Ferrothorn
- Chingling
- Chimecho
### Animation updates for the following Pokémon
- Mantine (ride animations)
- Flygon (ride jump animation)
- Honchkrow (ride animations)
- Croagunk
- Toxicroak
### Changes
- Added a new config option, `Enable In-Flight Dismounting` (default: off), which lets you dismount while riding a Pokémon in the air. This is the textbook definition of caveat emptor.
- Ride sounds have been separated into stereo for passengers and mono for other players. Riding will now sound more spacious.
- Battle AI now uses a smarter threshold for switching, reducing unnecessary switches.
- AI will always use the most damaging move when at low HP and when it is not switching, improving endgame decision-making.
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
- Added `label` as an alternate key to the `tag` property.
### Fixes
- Fixed crashing when viewing another player in spectator mode. Some said this was reason to release this update a month ago. They were probably right.
- Fixed crashing when sending a Pokémon out after editing its form.
- Fixed all Pokémon being saved to chunks and never despawning even when they should be.
- Fixed Poké Snacks crashing if there was no available spawn.
- Fixed Poké Snack / Bait effects so that EV Yield, Type, and Egg Group filters now all apply together when weighting spawns. And you thought you were going crazy.
- Fixed Furfrou not being trimmable on NeoForge specifically.
- Fixed honey from Saccharine Leaves being harvestable at less than the max age.
- Fixed possible error on world generation when Combee are placed in naturally generated bee nests.
- Fixed Galarian Weezing crashing the world if it decides to blink the wrong way. The same could happen to you!
- Fixed Galarian Mr. Mime incorrectly being rideable. Some would argue that regular Mr. Mime shouldn't be rideable, but that would make them wrong.
- Fixed missing apricorn textures for Adorn blocks.
- Fixed Apricorn block model rotation for counters/sinks/cupboards/drawers to match that of most recent version of Adorn.
- Fixed item interaction sometimes not working properly when playing on servers.
- Fixed Mint Leaves not being usable for filling the Resurrection Machine.
- Fixed Super Potion recipes incorrectly using Hondew Berry instead of Aguav Berry. They look similar, to be fair.
- Fixed Wishiwashi not schooling properly.
- Fixed battle item duplication issue. How did it work? Wouldn't you like to know, weather boy.
- Fixed the ordering of Aprijuice's riding stat boosts so they always have the same order.
- Fixed the movesets of certain Pokémon being incorrectly sanitized upon reload, causing some forms to keep losing moves.
- Fixed selected leading Pokémon in battle not being used when levels are set.
- Fixed the bottom half of pasture blocks having an incorrect hit-box when facing north or south.
- Fixed gimmicks not being usable in double and triple battles.
- Fixed gimmick buttons showing when already used in the same turn in double and triple battles.
- Fixed mochi items consuming two items at a time.
- Fixed Fresh Start Mochi's EV reset not syncing health changes properly.
- Fixed Pokémon spawn influences not being specific to regional forms.
- Fixed memory leak from battles not being cleaned up after ending.
- Fixed orientation not getting reset after crashing while flying and getting back on the Pokémon.
- Fixed shiny Pokémon not respecting the `silent` flag on spawn.
- Fixed 'Plain' prefix not being applied to all low-quality Aprijuice.
- Fixed Pokémon interactions not playing sound effects when the volume was not specified.
- Fixed Pokémon interaction cooldowns taking longer than intended when the Pokémon was in the player’s party.
- Fixed Pokémon interactions occasionally crashing the game when spamming an interaction with a 0 cooldown.
- Fixed Pokémon fullness decreasing more than intended when the Pokémon was pastured.
- Fixed Scraggy line, Milcery line, and Morpeko spawning on unnatural blocks.
- Fixed invulnerability-bypassed damage being resisted by invulnerable NPC entities.
- Fixed Fabric network packets not being handled immediately, fixing incompatibilities with Supplementaries, Prometheus, Sleep Tight, and others.
- Fixed killer not being set early enough on a wild Pokémon entity from `WinInstruction`.
- Fixed requirements such as `biomeCondition` crashing the game when used to define Pokémon interactions.
- Fixed an issue that prevented registering custom dispenser behaviours (no longer overrides vanilla behaviour).
- Fixed an issue navigation that prevented Combee from pathing into and out of Saccharine Leaves.
- Added Saccharine Boats and Saccharine Boat with Chests to relevant boat tags.
- Fixed Soothe Bell not being properly tagged as a held item.
- Fixed `/calculateseatpositions` expecting a locator format that even we don't use. It needs an underscore after "seat".
- Fixed the `run_script` Molang function to not fail if the environment's context is null.
- Fixed the Molang functions `date_local_time`, `date_of`, `date_is_after` to use the correct date format.
### Developer
- Changed the `owner` parameter in the `OwnerQueryRequirement` interface from `ServerPlayer` to `Player`. This method is now also called on the client to verify whether a Pokémon interaction succeeded, so make sure to update your implementations to handle both server and client contexts.
- Added the `pnx` to the `BattleFaintedEvent` and `FormeChangeEvent`.
- Added register methods for custom instructions to `ShowdownInterpreter`.
- Changed callback operations in `BattlePokemon` to allow multiple callbacks and be mutable.
- Added `display_name`, `description`, and `max_pp` functions to the `MoveTemplate` struct.
- Changed the implementations of `AIBattleActor` to be open classes to allow extension.
- Changed the resource location of Poké Ball icons in the GUI to use the balls namespace instead of Cobblemon's, allowing mods to properly use their own namespace for these textures.
### Molang & Datapacks
- Ride sounds can now be set as exclusive to passengers.
- Ride sounds no longer play when submerged unless specified with a new setting.
- Exception handling has been added to `run_molang`, resolving some crashes caused by malformed MoLang expressions.
- Added the `create_itemstack`, `has_inventory_space`, `set_inventory_slot`, and `give_item` MoLang functions for item and inventory utility.
- Added the `get_move_from_id` MoLang function for general move info queries without requiring a Moveset object to access.
- Added support for optional message variants in battle activate instructions for more context-specific battle text.
- Example: `this.add('-activate', pokemon, 'ability: example', '[msg]message1');` will parse to lang key `cobblemon.battle.activate.example.message1`.
### Localization
- Updated translations for:
- French
- Canadian French
- Simplified Chinese
- Ukrainian
- Brazilian Portuguese
- Spanish
- Korean
- Japanese
+9
View File
@@ -32,6 +32,10 @@ repositories {
maven(url = "https://api.modrinth.com/maven")
maven(url = "https://maven.neoforged.net/releases")
maven(url = "https://maven.gegy.dev")
exclusiveContent {
forRepository { maven(url = "https://maven.isxander.dev/releases") }
filter { includeGroup("dev.isxander") }
}
mavenLocal()
}
@@ -62,6 +66,11 @@ dependencies {
// Unit Testing
testImplementation(libs.bundles.unitTesting)
// Controlify Compat, same as flywheel so we pick one and only use the api
modCompileOnlyApi("dev.isxander:controlify:2.4.3+1.21.1-fabric") {
isTransitive = false
}
}
tasks.withType<Test> {
@@ -0,0 +1,26 @@
/*
* 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.mixin.accessor;
import net.minecraft.world.item.Item;
import net.minecraft.core.dispenser.DispenseItemBehavior;
import net.minecraft.world.level.block.DispenserBlock;
import java.util.Map;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(DispenserBlock.class)
public interface DispenserBlockAccessor {
@Accessor("DISPENSER_REGISTRY")
static Map<Item, DispenseItemBehavior> getItemBehaviors() {
throw new AssertionError();
}
}
@@ -21,12 +21,14 @@ import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import kotlin.Pair;
import kotlin.jvm.functions.Function4;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.phys.Vec3;
import org.joml.Vector2d;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -146,6 +148,12 @@ public abstract class MouseHandlerMixin {
pokeVehicle.clampPassengerRotation(player);
}
for(Function4<LocalPlayer, Double, Double, Double, Vector2d> rideFun : CobblemonClient.INSTANCE.getRidingTurnInputHandler().values()) {
Vector2d vec = rideFun.invoke(player, cursorDeltaX, cursorDeltaY, d);
cursorDeltaX = vec.x;
cursorDeltaY = vec.y;
}
// If the ride is not orientation controllable then return and perform the normal turnPlayer logic
if (!(vehicle instanceof OrientationControllable controllableVehicle) || !controllableVehicle.getOrientationController().isActive()) {
xMouseSmoother.reset();
@@ -0,0 +1,53 @@
/*
* 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.mixin.integration.controlify;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import dev.isxander.controlify.controller.ControllerEntity;
import dev.isxander.controlify.ingame.InGameInputHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Inventory;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(InGameInputHandler.class)
public class InGameInputHandlerMixin {
@Shadow @Final private ControllerEntity controller;
@Redirect(
method = "handleKeybinds()V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/player/Inventory;swapPaint(D)V"
)
)
public void cobblemon$onHotbarSwitch(Inventory instance, double direction) {
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
usageContext.adjustZoom(direction);
return;
}
if (CobblemonControlifyEntrypoint.Companion.getSEND_OUT_POKEMON().on(controller).digitalNow()) {
CobblemonClient.INSTANCE.getStorage().shiftSelected(direction < 0);
PartySendBinding.INSTANCE.actioned();
return;
}
instance.swapPaint(direction);
}
}
@@ -68,7 +68,7 @@ import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.block.DispenserBehaviorRegistry
import com.cobblemon.mod.common.block.dispenser.DispenserBehaviorRegistry
import com.cobblemon.mod.common.client.CobblemonPack
import com.cobblemon.mod.common.command.argument.*
import com.cobblemon.mod.common.config.CobblemonConfig
@@ -165,7 +165,7 @@ object Cobblemon {
val statistics: CobblemonStats = CobblemonStats
@JvmStatic
val builtinPacks = listOf<CobblemonPack>(
val builtinPacks = listOf(
CobblemonPack(id = "adorncompatibility", name = "Adorn Compatibility", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.ALWAYS_ENABLED, neededMods = setOf("adorn")),
CobblemonPack(id = "gyaradosjump", name = "Gyarados Jump Patterns", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED),
CobblemonPack(id = "regionbiasforms", name = "Region Bias Forms", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED),
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.apricorn.Apricorn
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.block.*
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.LecternBlock
@@ -41,11 +42,11 @@ import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockSetType
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BlockStateProperties.LIT
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.block.state.properties.WoodType
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.phys.shapes.Shapes
@Suppress("SameParameterValue", "HasPlatformType", "MemberVisibilityCanBePrivate", "unused")
object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<Block>>, Block>() {
@@ -243,9 +244,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).mapColor(MapColor.PLANT).ignitedByLava().noCollission().instabreak().sound(CobblemonSounds.REVIVAL_HERB_SOUNDS)))
@JvmField
val POKE_CAKE = this.create("poke_cake", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(LIT)) 3 else 0 }, false))
val POKE_CAKE = this.create("poke_cake", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, false))
@JvmField
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(LIT)) 3 else 0 }, true))
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, true))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@@ -477,6 +478,99 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val GIMMIGHOUL_CHEST = create("gimmighoul_chest", GildedChestBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.CHEST).noOcclusion().sound(CobblemonSounds.GILDED_CHEST_SOUNDS), GildedChestBlock.Type.FAKE))
@JvmField
val WHITE_PLAQUE = create("white_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val LIGHT_GRAY_PLAQUE = create("light_gray_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val GRAY_PLAQUE = create("gray_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val BLACK_PLAQUE = create("black_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val BROWN_PLAQUE = create("brown_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val RED_PLAQUE = create("red_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val ORANGE_PLAQUE = create("orange_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val YELLOW_PLAQUE = create("yellow_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val LIME_PLAQUE = create("lime_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val GREEN_PLAQUE = create("green_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val CYAN_PLAQUE = create("cyan_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val LIGHT_BLUE_PLAQUE = create("light_blue_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val BLUE_PLAQUE = create("blue_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val PURPLE_PLAQUE = create("purple_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val MAGENTA_PLAQUE = create("magenta_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val PINK_PLAQUE = create("pink_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val CLEANSE_TAG = create("cleanse_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
@JvmField
val SPELL_TAG = create("spell_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
@JvmField
val BLUNDER_POLICY = create("blunder_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val GUARD_SPEC = create("guard_spec", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_ACCURACY = create("x_${Stats.ACCURACY.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_ATTACK = create("x_${Stats.ATTACK.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_DEFENSE = create("x_${Stats.DEFENCE.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_SP_ATK = create("x_${Stats.SPECIAL_ATTACK.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_SP_DEF = create("x_${Stats.SPECIAL_DEFENCE.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
@JvmField
@@ -507,6 +507,23 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
@JvmField val WHITE_PLAQUE = blockItem("white_plaque", CobblemonBlocks.WHITE_PLAQUE)
@JvmField val LIGHT_GRAY_PLAQUE = blockItem("light_gray_plaque", CobblemonBlocks.LIGHT_GRAY_PLAQUE)
@JvmField val GRAY_PLAQUE = blockItem("gray_plaque", CobblemonBlocks.GRAY_PLAQUE)
@JvmField val BLACK_PLAQUE = blockItem("black_plaque", CobblemonBlocks.BLACK_PLAQUE)
@JvmField val BROWN_PLAQUE = blockItem("brown_plaque", CobblemonBlocks.BROWN_PLAQUE)
@JvmField val RED_PLAQUE = blockItem("red_plaque", CobblemonBlocks.RED_PLAQUE)
@JvmField val ORANGE_PLAQUE = blockItem("orange_plaque", CobblemonBlocks.ORANGE_PLAQUE)
@JvmField val YELLOW_PLAQUE = blockItem("yellow_plaque", CobblemonBlocks.YELLOW_PLAQUE)
@JvmField val LIME_PLAQUE = blockItem("lime_plaque", CobblemonBlocks.LIME_PLAQUE)
@JvmField val GREEN_PLAQUE = blockItem("green_plaque", CobblemonBlocks.GREEN_PLAQUE)
@JvmField val CYAN_PLAQUE = blockItem("cyan_plaque", CobblemonBlocks.CYAN_PLAQUE)
@JvmField val LIGHT_BLUE_PLAQUE = blockItem("light_blue_plaque", CobblemonBlocks.LIGHT_BLUE_PLAQUE)
@JvmField val BLUE_PLAQUE = blockItem("blue_plaque", CobblemonBlocks.BLUE_PLAQUE)
@JvmField val PURPLE_PLAQUE = blockItem("purple_plaque", CobblemonBlocks.PURPLE_PLAQUE)
@JvmField val MAGENTA_PLAQUE = blockItem("magenta_plaque", CobblemonBlocks.MAGENTA_PLAQUE)
@JvmField val PINK_PLAQUE = blockItem("pink_plaque", CobblemonBlocks.PINK_PLAQUE)
private val berries = mutableMapOf<ResourceLocation, BerryItem>()
// Plants
@JvmField val CHERI_BERRY = berryItem("cheri", StatusCuringBerryItem(CobblemonBlocks.CHERI_BERRY, Statuses.PARALYSIS))
@@ -699,15 +716,15 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION))
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
@JvmField
val SUPER_POTION = create("super_potion", PotionItem(PotionType.SUPER_POTION))
val SUPER_POTION = create("super_potion", PotionItem(PotionType.SUPER_POTION, CobblemonBlocks.SUPER_POTION))
@JvmField
val HYPER_POTION = create("hyper_potion", PotionItem(PotionType.HYPER_POTION))
val HYPER_POTION = create("hyper_potion", PotionItem(PotionType.HYPER_POTION, CobblemonBlocks.HYPER_POTION))
@JvmField
val MAX_POTION = create("max_potion", PotionItem(PotionType.MAX_POTION))
val MAX_POTION = create("max_potion", PotionItem(PotionType.MAX_POTION, CobblemonBlocks.MAX_POTION))
@JvmField
val FULL_RESTORE = create("full_restore", PotionItem(PotionType.FULL_RESTORE))
val FULL_RESTORE = create("full_restore", PotionItem(PotionType.FULL_RESTORE, CobblemonBlocks.FULL_RESTORE))
@JvmField
val HEAL_POWDER = create("heal_powder", HealPowderItem())
@@ -792,28 +809,27 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SERIOUS_MINT = mintItem("serious_mint", MintItem(Natures.SERIOUS))
@JvmField val X_ACCURACY = create("x_${Stats.ACCURACY.identifier.path}", XStatItem(Stats.ACCURACY))
@JvmField val X_ATTACK = create("x_${Stats.ATTACK.identifier.path}", XStatItem(Stats.ATTACK))
@JvmField val X_DEFENSE = create("x_${Stats.DEFENCE.identifier.path}", XStatItem(Stats.DEFENCE))
@JvmField val X_SP_ATK = create("x_${Stats.SPECIAL_ATTACK.identifier.path}", XStatItem(Stats.SPECIAL_ATTACK))
@JvmField val X_SP_DEF = create("x_${Stats.SPECIAL_DEFENCE.identifier.path}", XStatItem(Stats.SPECIAL_DEFENCE))
@JvmField val X_SPEED = create("x_${Stats.SPEED.identifier.path}", XStatItem(Stats.SPEED))
@JvmField val X_ACCURACY = create("x_${Stats.ACCURACY.identifier.path}", XStatItem(Stats.ACCURACY, block = CobblemonBlocks.X_ACCURACY))
@JvmField val X_ATTACK = create("x_${Stats.ATTACK.identifier.path}", XStatItem(Stats.ATTACK, block = CobblemonBlocks.X_ATTACK))
@JvmField val X_DEFENSE = create("x_${Stats.DEFENCE.identifier.path}", XStatItem(Stats.DEFENCE, block = CobblemonBlocks.X_DEFENSE))
@JvmField val X_SP_ATK = create("x_${Stats.SPECIAL_ATTACK.identifier.path}", XStatItem(Stats.SPECIAL_ATTACK, block = CobblemonBlocks.X_SP_ATK))
@JvmField val X_SP_DEF = create("x_${Stats.SPECIAL_DEFENCE.identifier.path}", XStatItem(Stats.SPECIAL_DEFENCE, block = CobblemonBlocks.X_SP_DEF))
@JvmField val X_SPEED = create("x_${Stats.SPEED.identifier.path}", XStatItem(Stats.SPEED, block = CobblemonBlocks.X_SPEED))
@JvmField val DIRE_HIT = create("dire_hit", DireHitItem())
@JvmField val GUARD_SPEC = create("guard_spec", GuardSpecItem())
@JvmField val DIRE_HIT = create("dire_hit", DireHitItem(CobblemonBlocks.DIRE_HIT))
@JvmField val GUARD_SPEC = create("guard_spec", GuardSpecItem(CobblemonBlocks.GUARD_SPEC))
@JvmField val BURN_HEAL = create("burn_heal", StatusCureItem("item.cobblemon.burn_heal", Statuses.BURN))
@JvmField val PARALYZE_HEAL = create("paralyze_heal", StatusCureItem("item.cobblemon.paralyze_heal", Statuses.PARALYSIS))
@JvmField val ICE_HEAL = create("ice_heal", StatusCureItem("item.cobblemon.ice_heal", Statuses.FROZEN))
@JvmField val ANTIDOTE = create("antidote", StatusCureItem("item.cobblemon.antidote", Statuses.POISON, Statuses.POISON_BADLY))
@JvmField val AWAKENING = create("awakening", StatusCureItem("item.cobblemon.awakening", Statuses.SLEEP))
@JvmField val FULL_HEAL = create("full_heal", StatusCureItem("item.cobblemon.full_heal", block = CobblemonBlocks.FULL_HEAL))
@JvmField val ANTIDOTE = create("antidote", StatusCureItem("item.cobblemon.antidote", Statuses.POISON, Statuses.POISON_BADLY, block = CobblemonBlocks.ANTIDOTE))
@JvmField val AWAKENING = create("awakening", StatusCureItem("item.cobblemon.awakening", Statuses.SLEEP, block = CobblemonBlocks.AWAKENING))
@JvmField val BURN_HEAL = create("burn_heal", StatusCureItem("item.cobblemon.burn_heal", Statuses.BURN, block = CobblemonBlocks.BURN_HEAL))
@JvmField val ICE_HEAL = create("ice_heal", StatusCureItem("item.cobblemon.ice_heal", Statuses.FROZEN, block = CobblemonBlocks.ICE_HEAL))
@JvmField val PARALYZE_HEAL = create("paralyze_heal", StatusCureItem("item.cobblemon.paralyze_heal", Statuses.PARALYSIS, block = CobblemonBlocks.PARALYZE_HEAL))
@JvmField val FULL_HEAL = create("full_heal", StatusCureItem("item.cobblemon.full_heal"))
@JvmField val ETHER = create("ether", EtherItem(max = false))
@JvmField val MAX_ETHER = create("max_ether", EtherItem(max = true))
@JvmField val ELIXIR = create("elixir", ElixirItem(max = false))
@JvmField val MAX_ELIXIR = create("max_elixir", ElixirItem(max = true))
@JvmField val ETHER = create("ether", EtherItem(max = false, CobblemonBlocks.ETHER))
@JvmField val MAX_ETHER = create("max_ether", EtherItem(max = true, CobblemonBlocks.MAX_ETHER))
@JvmField val ELIXIR = create("elixir", ElixirItem(max = false, CobblemonBlocks.ELIXIR))
@JvmField val MAX_ELIXIR = create("max_elixir", ElixirItem(max = true, CobblemonBlocks.MAX_ELIXIR))
@JvmField
val ABILITY_CAPSULE = this.create("ability_capsule", AbilityChangeItem(AbilityChanger.COMMON_ABILITY))
@@ -922,7 +938,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BLACK_SLUDGE = heldItem("black_sludge")
@JvmField
val BLUNDER_POLICY = heldItem("blunder_policy")
val BLUNDER_POLICY = itemNameBlockItem("blunder_policy", CobblemonBlocks.BLUNDER_POLICY)
@JvmField
val CELL_BATTERY = heldItem("cell_battery")
@JvmField
@@ -930,7 +946,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CLEANSE_TAG = heldItem("cleanse_tag")
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField
val CLEAR_AMULET = heldItem("clear_amulet")
@JvmField
@@ -1038,7 +1054,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SOFT_SAND = heldItem("soft_sand")
@JvmField
val SPELL_TAG = heldItem("spell_tag")
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
@JvmField
val SMOKE_BALL = heldItem("smoke_ball")
@JvmField
@@ -1056,7 +1072,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val UTILITY_UMBRELLA = heldItem("utility_umbrella")
@JvmField
val WEAKNESS_POLICY = heldItem("weakness_policy")
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
@JvmField
val WIDE_LENS = heldItem("wide_lens")
@JvmField
@@ -1553,6 +1569,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
}
}
)
private fun heldItem(name: String, item: Item, remappedName: String? = null) = create(
name = name,
entry = item.also {
@@ -21,12 +21,15 @@ import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallb
import com.cobblemon.mod.common.client.net.cooking.ToggleCookingPotLidHandler
import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.PokemonInteractionsSyncHandler
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.*
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
@@ -34,7 +37,11 @@ import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
import com.cobblemon.mod.common.client.net.pasture.*
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHandler
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
@@ -52,7 +59,14 @@ import com.cobblemon.mod.common.client.net.storage.party.SetPartyPokemonHandler
import com.cobblemon.mod.common.client.net.storage.party.SetPartyReferenceHandler
import com.cobblemon.mod.common.client.net.storage.pc.*
import com.cobblemon.mod.common.client.net.toast.ToastPacketHandler
import com.cobblemon.mod.common.client.net.trade.*
import com.cobblemon.mod.common.client.net.trade.TradeAcceptanceChangedHandler
import com.cobblemon.mod.common.client.net.trade.TradeCancelledHandler
import com.cobblemon.mod.common.client.net.trade.TradeCompletedHandler
import com.cobblemon.mod.common.client.net.trade.TradeOfferExpiredHandler
import com.cobblemon.mod.common.client.net.trade.TradeOfferNotificationHandler
import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.OpenBehaviourEditorPacket
@@ -69,12 +83,20 @@ import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.*
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.pasture.*
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
@@ -94,19 +116,43 @@ import com.cobblemon.mod.common.net.messages.client.storage.party.InitializePart
import com.cobblemon.mod.common.net.messages.client.storage.party.MoveClientPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyReferencePacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.*
import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.OpenPCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.RenamePCBoxPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCBoxPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangePCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.*
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCompletedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferExpiredPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotificationPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.battle.*
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
import com.cobblemon.mod.common.net.messages.server.BattleChallengeResponsePacket
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamResponsePacket
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
@@ -131,7 +177,11 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPack
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.*
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
@@ -142,11 +192,21 @@ import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokem
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.*
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
import com.cobblemon.mod.common.net.messages.server.trade.CancelTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptancePacket
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.*
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
import com.cobblemon.mod.common.net.serverhandling.battle.TeamLeaveHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHandler
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
@@ -172,7 +232,11 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdateRiderRotationHandler
@@ -186,7 +250,12 @@ import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemo
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.serverhandling.storage.pc.*
import com.cobblemon.mod.common.net.serverhandling.trade.*
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHandler
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
import com.cobblemon.mod.common.util.server
import net.minecraft.server.level.ServerPlayer
@@ -332,7 +401,7 @@ object CobblemonNetwork {
// Data registries
list.add(PacketRegisterInfo(AbilityRegistrySyncPacket.ID, AbilityRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CobblemonMechanicsSyncPacket.ID, CobblemonMechanicsSyncPacket::decode, CobblemonMechanicsSyncHandler))
list.add(PacketRegisterInfo(PokemonInteractionsSyncPacket.ID, PokemonInteractionsSyncPacket::decode, PokemonInteractionsSyncHandler))
list.add(PacketRegisterInfo(PokemonInteractionsSyncPacket.ID, PokemonInteractionsSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -304,7 +304,8 @@ open class PokemonBattle(
.forEach{it.pokemon.heal()}
actors.forEach { actor ->
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperation(entity) }
battlePokemon.postBattlePokemonOperations.forEach { it(battlePokemon) }
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperations.forEach { it(entity) } }
if (battlePokemon.effectedPokemon.entity != null
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
@@ -498,8 +499,8 @@ open class PokemonBattle(
}
fun stop() {
end()
writeShowdownAction(">forcetie") // This will terminate the Showdown connection
end()
}
fun checkForInputDispatch() {
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.events.battles
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
@@ -25,7 +26,8 @@ import com.cobblemon.mod.common.util.asArrayValue
data class BattleFaintedEvent(
override val battle: PokemonBattle,
val killed: BattlePokemon,
val context: BattleContext
val context: BattleContext,
val pnx: String
) : BattleEvent {
val structContext = mutableMapOf<String, MoValue>(
"battle" to battle.struct,
@@ -33,6 +35,7 @@ data class BattleFaintedEvent(
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct },
"pokemon" to killed.struct,
"context" to context.getStruct()
"context" to context.getStruct(),
"pnx" to StringValue(pnx)
)
}
}
@@ -25,7 +25,8 @@ import com.cobblemon.mod.common.util.asArrayValue
data class FormeChangeEvent(
override val battle: PokemonBattle,
val pokemon: BattlePokemon,
val formeName: String
val formeName: String,
val pnx: String
) : BattleEvent {
val context = mutableMapOf(
"battle" to battle.struct,
@@ -33,6 +34,7 @@ data class FormeChangeEvent(
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct },
"pokemon" to pokemon.struct,
"forme" to StringValue(formeName)
"forme" to StringValue(formeName),
"pnx" to StringValue(pnx)
)
}
}
@@ -59,40 +59,32 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
.setPrettyPrinting()
.create()
val speciesInteractions = mutableListOf<PokemonInteractionSet>()
val generalInteractions = mutableListOf<PokemonInteractionSet>()
val interactions = mutableListOf<PokemonInteractionSet>()
override fun sync(player: ServerPlayer) {
PokemonInteractionsSyncPacket(
speciesInteractions,
generalInteractions,
).sendToPlayer(player)
PokemonInteractionsSyncPacket(interactions).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
speciesInteractions.clear()
generalInteractions.clear()
val split = data.entries.partition { it.value.requirements.any { requirement -> requirement is PokemonPropertiesRequirement && requirement.target.species != null } }
speciesInteractions.addAll(split.first.map {it.value})
generalInteractions.addAll(split.second.map {it.value})
Cobblemon.LOGGER.info("Loaded {} Pokémon interaction sets", data.size)
interactions.clear()
val split = data.entries
.partition {
it.value.requirements.any { requirement -> requirement is PokemonPropertiesRequirement && requirement.target.species != null }
}
// Put the species specific interactions first as they take priority
interactions.addAll(split.first.map {it.value})
interactions.addAll(split.second.map {it.value})
Cobblemon.LOGGER.info("Loaded {} Pokémon interaction sets", interactions.size)
}
fun findInteraction(pokemon: PokemonEntity): PokemonInteraction? {
val setCheck: (PokemonInteractionSet) -> Boolean = { it.requirements.all { req -> req.check(pokemon.pokemon) }}
val interactionCheck: (PokemonInteraction) -> Boolean = { it.requirements.all { req -> req.check(pokemon.pokemon) } && !pokemon.pokemon.isOnInteractionCooldown(it.grouping) }
// species-specific interactions take priority
val validInteractions = speciesInteractions
return interactions
.filter(setCheck)
.flatMap { it.interactions }
.filter(interactionCheck)
.toMutableList()
if (validInteractions.isEmpty()) { // if all species-specific interactions are absent/on cooldown, fallback to more generic ones
validInteractions.addAll(generalInteractions.filter(setCheck).flatMap { it.interactions }
.filter(interactionCheck)
)
}
return validInteractions.randomOrNull()
.firstOrNull(interactionCheck)
}
}
@@ -43,17 +43,9 @@ data class PokemonInteractionSet(
companion object {
fun decode(buffer: FriendlyByteBuf): PokemonInteractionSet {
val requirements = buffer.readList { it ->
CobblemonRequirementAdapter.decode(it)
}
val interactions = buffer.readList { it ->
PokemonInteraction.decode(it)
}
return PokemonInteractionSet(
requirements,
interactions,
)
val requirements = buffer.readList(CobblemonRequirementAdapter::decode)
val interactions = buffer.readList(PokemonInteraction::decode)
return PokemonInteractionSet(requirements, interactions)
}
}
}
@@ -368,6 +368,11 @@ object MoLangFunctions {
val b = format.parse(dateB)
DoubleValue(a.after(b))
},
"get_move_from_id" to java.util.function.Function { params ->
val moveId = params.getString(0)
val moveTemplate = Moves.getByName(moveId)
return@Function moveTemplate?.struct ?: DoubleValue.ZERO
},
"create_simple_party_provider" to java.util.function.Function { params ->
val partyProvider = SimplePartyProvider()
return@Function partyProvider.struct
@@ -378,6 +383,13 @@ object MoLangFunctions {
val pickupItem = ObtainableItem(item = item, pickupPriority = pickupPriority)
return@Function pickupItem.struct
},
"create_itemstack" to java.util.function.Function { params ->
val itemId = params.getString(0).asIdentifierDefaultingNamespace()
val item = BuiltInRegistries.ITEM.get(itemId) ?: return@Function DoubleValue.ZERO
val count = params.getIntOrNull(1) ?: 1
val itemStack = ItemStack(item, count)
return@Function ObjectValue(itemStack)
},
"file" to java.util.function.Function { MoLangLoadedFilesCache.struct }
)
val biomeFunctions = mutableListOf<(Holder<Biome>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
@@ -583,6 +595,62 @@ object MoLangFunctions {
}
return@put items
}
map.put("has_inventory_space") { _ ->
val inventory = player.inventory
return@put if (inventory.getFreeSlot() != -1) DoubleValue.ONE else DoubleValue.ZERO
}
map.put("set_inventory_slot") { params ->
val slot = params.getInt(0)
val inventory = player.inventory
if (slot !in 0 until inventory.containerSize) return@put DoubleValue.ZERO
val value = params.getOrNull<MoValue>(1) ?: return@put DoubleValue.ZERO
val stack: ItemStack? = when (value) {
is ObjectValue<*> -> value.obj as? ItemStack
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(id) ?: return@put DoubleValue.ZERO
ItemStack(item)
}
else -> null
}
if (stack == null) return@put DoubleValue.ZERO
inventory.setItem(slot, stack)
return@put DoubleValue.ONE
}
map.put("give_item") { params ->
val inventory = player.inventory
val freeSlot = inventory.getFreeSlot()
val value = params.getOrNull<MoValue>(0) ?: return@put DoubleValue.ZERO
val dropIfFull = params.getBooleanOrNull(1) ?: false
val stack: ItemStack? = when (value) {
is ObjectValue<*> -> value.obj as? ItemStack
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(id) ?: return@put DoubleValue.ZERO
ItemStack(item)
}
else -> null
}
if (stack == null) return@put DoubleValue.ZERO
if (freeSlot == -1) {
if (dropIfFull) {
player.drop(stack, false)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
inventory.setItem(freeSlot, stack)
return@put DoubleValue.ONE
}
map.put("face") { params -> ObjectValue(PlayerDialogueFaceProvider(player.uuid, params.getBooleanOrNull(0) != false)) }
map.put("swing_hand") { _ -> player.swing(player.usedItemHand) }
map.put("food_level") { _ -> DoubleValue(player.foodData.foodLevel) }
@@ -626,8 +694,7 @@ object MoLangFunctions {
}
map.put("active_dialogue") { _ ->
if (player.isInDialogue) {
player.activeDialogue?.dialogueId.toString()
return@put DoubleValue.ONE
return@put player.activeDialogue?.toMoLangStruct() ?: DoubleValue.ZERO
} else {
DoubleValue.ZERO
}
@@ -58,12 +58,15 @@ open class MoveTemplate(
val struct: MoStruct by lazy {
QueryStruct(hashMapOf())
.addFunction("name") { StringValue(name) }
.addFunction("display_name") { StringValue(displayName.string) }
.addFunction("description") { StringValue(description.string) }
.addFunction("type") { StringValue(elementalType.showdownId) }
.addFunction("damage_category") { StringValue(damageCategory.name) }
.addFunction("power") { DoubleValue(power) }
.addFunction("target") { StringValue(target.name) }
.addFunction("accuracy") { DoubleValue(accuracy) }
.addFunction("pp") { DoubleValue(pp) }
.addFunction("max_pp") { DoubleValue(maxPp) }
.addFunction("priority") { DoubleValue(priority) }
.addFunction("crit_ratio") { DoubleValue(critRatio) }
}
@@ -102,6 +102,12 @@ object SpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
@JvmStatic
fun register(name: String, provider: SpeciesFeatureProvider<*>) = register(name, provider, isCoded = true)
@JvmStatic
fun registerType(key: String, clazz: Class<out SpeciesFeatureProvider<*>>) {
types[key] = clazz
}
private fun registerFromAssets(identifier: ResourceLocation, provider: SpeciesFeatureProvider<*>) = register(identifier.path, provider, isCoded = false)
@JvmStatic
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.events.Cancelable
open class EventObservable<T> : SimpleObservable<T>() {
@JvmOverloads
inline fun post(vararg events: T, then: (T) -> Unit = {}) {
// Issuing generic-typed pass-throughs to varargs doesn't work
// properly unless the source is a vararg as well, complicated story.
@@ -41,10 +41,6 @@ interface CompositeRidingStrategy<T : CompositeSettings> {
toState: RidingBehaviourState,
toSettings: RidingBehaviourSettings
) {
if (vehicle.level().isClientSide) {
val fromSettings = if (settings.defaultBehaviour == toSettings) settings.alternateBehaviour else settings.defaultBehaviour
}
toState.stamina.set(fromState.stamina.get())
toState.rideVelocity.set(fromState.rideVelocity.get())
fromState.reset()
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -144,11 +145,12 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
) {
val sprintTimerTickLength = 7 // Emulate vanillas tick timer length
var hasDoubleTapped = false
val forward = Minecraft.getInstance().player?.input?.up == true
// If you are not sprinting and you just pressed down and theres no
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
if (!state.forwardHeldLastTick.get() && forward && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
state.sprintTickTimer.set(sprintTimerTickLength)
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && state.sprintTickTimer.get() != 0) { // If has double tapped
} else if (!state.forwardHeldLastTick.get() && forward && state.sprintTickTimer.get() != 0) { // If has double tapped
hasDoubleTapped = true
}else if(!state.sprinting.get() && state.sprintTickTimer.get() > 0) {
state.sprintTickTimer.set(state.sprintTickTimer.get() - 1)
@@ -156,9 +158,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state.sprintTickTimer.set(0)
}
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
val tryingToSprint = hasDoubleTapped || CobblemonClient.ridingSprintHandler.any { it.value.invoke() }
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
if (state.stamina.get() <= 0.0f || !forward) {
// If stamina runs out or the player is not holding forward then stop sprinting
state.sprinting.set(false)
if (state.stamina.get() <= 0.0f) {
@@ -172,8 +174,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state.sprinting.set(true)
}
// Take not of whether forward was held last tick
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
// Take note of whether forward was held last tick
state.forwardHeldLastTick.set(forward)
}
fun tickStamina(
@@ -187,17 +189,16 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return
}
var newStam = stam
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalHorse.staminaExpr!!)).toFloat() / 20.0f
if (state.sprinting.get()) {
newStam = max(0.0f,stam - stamDrainRate)
val newStam = if (state.sprinting.get()) {
max(0.0f,stam - stamDrainRate)
} else {
newStam = min(1.0f,stam + stamDrainRate * 4)
min(1.0f,stam + stamDrainRate * 4)
}
state.stamina.set(newStam)
state.stamina.set(newStam.coerceIn(0F, 1F))
}
override fun updatePassengerRotation(
@@ -100,7 +100,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
input: Vec3
) {
if(vehicle.level().isClientSide) {
if (vehicle.level().isClientSide) {
handleBoosting(state, vehicle)
tickStamina(settings, state, vehicle)
}
@@ -147,7 +147,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
val stamDrainRate = (1.0f / boostTime).toFloat()
val newStam = if (state.boosting.get()) max(0.0f,stam - stamDrainRate)
else min(1.0f,stam + stamDrainRate)
else (stam + stamDrainRate).coerceIn(0F, 1F)
state.stamina.set(newStam)
}
@@ -26,7 +26,7 @@ import net.minecraft.world.entity.Entity
/**
* A [SpawningInfluence] that applies some number of SpawnBait effects.
*
* @author Hiroku, Plastered_Crab
* @author Hiroku, Plastered_Crab, ppVon
* @since March 18th, 2025
*/
open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time: Int, entity: PokemonEntity?) -> Unit = { _, _ -> }) : SpawningInfluence {
@@ -50,85 +50,84 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
}
}
// EV related bait effects
// EV, Type, and Egg Group related bait effects
override fun affectWeight(detail: SpawnDetail, spawnablePosition: SpawnablePosition, weight: Float): Float {
val merged = SpawnBaitUtils.mergeEffects(effects)
val hasRelevantEffects =
merged.any { it.type == Effects.EV } ||
merged.any { it.type == Effects.TYPING } ||
merged.any { it.type == Effects.EGG_GROUP }
if (!hasRelevantEffects) {
return super.affectWeight(detail, spawnablePosition, weight)
}
val pokemonDetail = detail as? PokemonSpawnDetail
?: return super.affectWeight(detail, spawnablePosition, weight)
val detailSpecies = pokemonDetail.pokemon.species
?.let { PokemonSpecies.getByName(it) }
?: return super.affectWeight(detail, spawnablePosition, weight)
val formData = detailSpecies.getForm(pokemonDetail.pokemon.aspects)
var newWeight = weight
var used = false
// if bait exists and any effects are related to EV yields
if (merged.any { it.type == Effects.EV }){
if (detail is PokemonSpawnDetail) {
val detailSpecies = detail.pokemon.species?.let { PokemonSpecies.getByName(it) }
val baitEVStat = effects.firstOrNull { it.type == Effects.EV }?.subcategory?.path?.let { Stats.getStat(it) }
if (merged.any { it.type == Effects.EV }) {
val evEffect = effects.firstOrNull { it.type == Effects.EV }
val baitEVStat = evEffect?.subcategory?.path?.let { Stats.getStat(it) }
if (detailSpecies != null && baitEVStat != null) {
val detailAspects = detail.pokemon.aspects
val formData = detailSpecies.getForm(detailAspects)
val evYieldValue = formData.evYield[baitEVStat]?.toFloat() ?: 0f
return when {
evYieldValue > 0 -> {
markUsed()
super.affectWeight(detail, spawnablePosition, weight)
}
else -> {
markUsed()
super.affectWeight(detail, spawnablePosition, 0f)
}
}
if (baitEVStat != null) {
val evYieldValue = formData.evYield[baitEVStat]?.toFloat() ?: 0f
used = true
if (evYieldValue <= 0f) {
newWeight = 0f
} else {
LOGGER.debug("Boosted weight value");
}
}
}
// if bait exists and any effects are related to Typing
if (merged.any { it.type == Effects.TYPING }){
if (detail is PokemonSpawnDetail) {
val detailSpecies = detail.pokemon.species?.let { PokemonSpecies.getByName(it) }
val baitEffect = effects.firstOrNull { it.type == Effects.TYPING }
val baitTypingEffect = baitEffect?.subcategory?.path?.let { ElementalTypes.get(it) }
if (newWeight > 0f && merged.any { it.type == Effects.TYPING }) {
val baitEffect = effects.firstOrNull { it.type == Effects.TYPING }
val baitTypingEffect = baitEffect?.subcategory?.path?.let { ElementalTypes.get(it) }
if (detailSpecies != null && baitTypingEffect != null) {
val detailAspects = detail.pokemon.aspects
val formData = detailSpecies.getForm(detailAspects)
val isMatchingType = formData.types.contains(baitTypingEffect)
return when {
isMatchingType -> {
markUsed()
super.affectWeight(detail, spawnablePosition, weight * baitEffect.value.toFloat())
}
else -> super.affectWeight(detail, spawnablePosition, weight)
}
if (baitEffect != null && baitTypingEffect != null) {
val isMatchingType = formData.types.contains(baitTypingEffect)
used = true
if (isMatchingType) {
newWeight *= baitEffect.value.toFloat()
}
}
}
// if bait exists and any effects are related to Egg Groups
if (merged.any { it.type == Effects.EGG_GROUP }) {
if (detail is PokemonSpawnDetail) {
val detailSpecies = detail.pokemon.species?.let { PokemonSpecies.getByName(it) }
if (newWeight > 0f && merged.any { it.type == Effects.EGG_GROUP }) {
val eggGroupEffects = effects.filter { it.type == Effects.EGG_GROUP }
if (detailSpecies != null) {
val detailAspects = detail.pokemon.aspects
val formData = detailSpecies.getForm(detailAspects)
// Collect all the egg group effects
val eggGroupEffects = effects.filter { it.type == Effects.EGG_GROUP }
// Check if any of the egg group effects match the species' egg groups
val matchingEffect = eggGroupEffects.firstOrNull { effect ->
val effectEggGroupKey = effect.subcategory?.path ?: return@firstOrNull false
val eggGroup = EggGroup.fromIdentifier(effectEggGroupKey)
if (eggGroup == null) {
LOGGER.warn("Unknown egg group identifier: $effectEggGroupKey")
return@firstOrNull false
}
formData.eggGroups.contains(eggGroup)
}
if (matchingEffect != null) {
markUsed()
val multiplier = matchingEffect.value
return super.affectWeight(detail, spawnablePosition, (weight * multiplier).toFloat())
}
val matchingEffect = eggGroupEffects.firstOrNull { effect ->
val key = effect.subcategory?.path ?: return@firstOrNull false
val eggGroup = EggGroup.fromIdentifier(key)
if (eggGroup == null) {
LOGGER.warn("Unknown egg group identifier: $key")
return@firstOrNull false
}
formData.eggGroups.contains(eggGroup)
}
if (matchingEffect != null) {
used = true
newWeight *= matchingEffect.value.toFloat()
}
}
return super.affectWeight(detail, spawnablePosition, weight)
if (used) markUsed()
return super.affectWeight(detail, spawnablePosition, newWeight)
}
}
@@ -258,17 +258,22 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
@JvmOverloads
fun toBattleTeam(clone: Boolean = false, healPokemon: Boolean = false, leadingPokemon: UUID? = null) : List<BattlePokemon> {
var leadingPokemonId = leadingPokemon
val result = this.mapNotNull {
return@mapNotNull if (clone) {
BattlePokemon.safeCopyOf(it)
BattlePokemon.safeCopyOf(it).also { copy ->
if (leadingPokemon != null && it.uuid == leadingPokemonId) {
leadingPokemonId = copy.uuid
}
}
} else {
BattlePokemon.playerOwned(it)
}.also { if (healPokemon) it.effectedPokemon.heal() }
}.toMutableList()
// reposition lead to front of the party
if (leadingPokemon != null && result.first().uuid != leadingPokemon) {
result.find { it.uuid == leadingPokemon }?.let { lead ->
if (leadingPokemonId != null && result.first().uuid != leadingPokemonId) {
result.find { it.uuid == leadingPokemonId }?.let { lead ->
result.remove(lead)
result.add(0, lead)
}
@@ -42,6 +42,7 @@ object CobblemonItemTags {
@JvmField val DAWN_STONE_ORES = create("dawn_stone_ores")
@JvmField val POTTERY_SHERDS = create("decorated_pot_sherds")
@JvmField val DEEP_SEAS = create("deep_seas")
@JvmField val DISPLAY_CASE_ITEMS = create("display_case_items")
@JvmField val DUSK_STONE_ORES = create("dusk_stone_ores")
@JvmField val ETHERS = create("ethers")
@JvmField val EVOLUTION_ITEMS = create("evolution_items")
@@ -57,6 +58,7 @@ object CobblemonItemTags {
@JvmField val HANGING_SIGNS = create("hanging_signs")
@JvmField val HERBS = create("herbs")
@JvmField val ICE_STONE_ORES = create("ice_stone_ores")
@JvmField val ITEM_BLOCKS = create("item_blocks")
@JvmField val LEAF_STONE_ORES = create("leaf_stone_ores")
@JvmField val MACHINES = create("machines")
@JvmField val MINT_LEAF = create("mint_leaf")
@@ -65,6 +67,7 @@ object CobblemonItemTags {
@JvmField val MOCHIS = create("mochis")
@JvmField val MOON_STONE_ORES = create("moon_stone_ores")
@JvmField val MUTATED_BERRIES = create("mutated_berries")
@JvmField val PLAQUES = create("plaques")
@JvmField val PLANTS = create("plants")
@JvmField val POKE_BALLS = create("poke_balls")
@JvmField val POKE_RODS = create("poke_rods")
@@ -63,8 +63,8 @@ object BattleBuilder {
): BattleStartResult {
val adjustLevel = battleFormat.adjustLevel
val team1 = partyAccessor(player1).toBattleTeam(clone = cloneParties || adjustLevel > 0, healPokemon = healFirst, leadingPokemonPlayer1).sortedBy { it.health <= 0 }
val team2 = partyAccessor(player2).toBattleTeam(clone = cloneParties || adjustLevel > 0, healPokemon = healFirst, leadingPokemonPlayer2).sortedBy { it.health <= 0 }
val team1 = partyAccessor(player1).toBattleTeam(clone = cloneParties || adjustLevel > 0, healPokemon = healFirst, leadingPokemonPlayer1)
val team2 = partyAccessor(player2).toBattleTeam(clone = cloneParties || adjustLevel > 0, healPokemon = healFirst, leadingPokemonPlayer2)
val battlePartyStores = mutableListOf<PlayerPartyStore>()
@@ -221,6 +221,7 @@ object BattleRegistry {
fun closeBattle(battle: PokemonBattle) {
battle.onEndHandlers.forEach { it(battle) }
battleMap.remove(battle.battleId)
ShowdownService.service.endBattle(battle)
}
@JvmStatic
@@ -300,4 +300,35 @@ object ShowdownInterpreter {
} ?:
MissingContext()
}
}
/**
* Registers a new update instruction parser.
* @param id The ID of the instruction to register.
* @param callback The callback function to create the instruction.
*/
@JvmStatic
fun registerUpdateInstructionParser(id: String, callback: (PokemonBattle, InstructionSet, BattleMessage, Iterator<BattleMessage>) -> InterpreterInstruction) {
updateInstructionParser[id] = callback
}
/**
* Registers a new split instruction parser.
* @param id The ID of the instruction to register.
* @param callback The callback function to create the instruction.
*/
@JvmStatic
fun registerSplitInstructionParser(id: String, callback: (PokemonBattle, BattleActor, InstructionSet, BattleMessage, BattleMessage, Iterator<BattleMessage>) -> InterpreterInstruction) {
splitInstructionParser[id] = callback
}
/**
* Registers a new side instruction parser.
* @param id The ID of the instruction to register.
* @param callback The callback function to create the instruction.
*/
@JvmStatic
fun registerSideInstructionParser(id: String, callback: (PokemonBattle, BattleActor, InstructionSet, BattleMessage) -> InterpreterInstruction) {
sideInstructionParser[id] = callback
}
}
@@ -18,7 +18,7 @@ import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Component
class MultiPokemonBattleActor(
open class MultiPokemonBattleActor(
pokemonList: List<BattlePokemon>,
artificialDecider: BattleAI = RandomBattleAI(),
uuid: UUID = UUID.randomUUID()
@@ -24,17 +24,14 @@ import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Component
import net.minecraft.world.phys.Vec3
class PokemonBattleActor(
open class PokemonBattleActor(
uuid: UUID,
val pokemon: BattlePokemon,
override val fleeDistance: Float,
artificialDecider: BattleAI = RandomBattleAI()
artificialDecider: BattleAI = RandomBattleAI(),
) : AIBattleActor(uuid, listOf(pokemon), artificialDecider), EntityBackedBattleActor<PokemonEntity>, FleeableBattleActor {
override val initialPos: Vec3?
init {
initialPos = entity?.position()
}
override val initialPos: Vec3? = pokemon.entity?.position()
override fun getName() = pokemon.effectedPokemon.species.translatedName
override fun nameOwned(name: String): MutableComponent = Component.literal(name)
override fun getWorldAndPosition(): Pair<ServerLevel, Vec3>? {
@@ -17,7 +17,7 @@ import com.cobblemon.mod.common.util.battleLang
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
class TrainerBattleActor(
open class TrainerBattleActor(
val trainerName: String,
uuid: UUID,
pokemonList: List<BattlePokemon>,
@@ -52,7 +52,7 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
preamble.thenAccept {
faintingPokemon.effectedPokemon.currentHealth = 0
faintingPokemon.sendUpdate()
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context))
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context, pnx))
battle.getActorAndActiveSlotFromPNX(pnx).second.battlePokemon = null
faintingPokemon.contextManager.add(context)
faintingPokemon.contextManager.clear(BattleContext.Type.STATUS, BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
@@ -26,6 +26,7 @@ import com.cobblemon.mod.common.util.battleLang
class FormeChangeInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val (pnx, _) = message.pnxAndUuid(0) ?: return
val battlePokemon = message.battlePokemon(0, battle) ?: return
val details = message.argumentAt(1)?.split(',')?.get(0)?.lowercase() ?: return
val speciesName = details.substringBefore('-')
@@ -37,7 +38,7 @@ class FormeChangeInstruction(val message: BattleMessage): InterpreterInstruction
battle.dispatchWaiting {
battle.minorBattleActions[battlePokemon.uuid] = message
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName))
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName, pnx))
val pokemonName = battlePokemon.getName()
val lang = when(formName) {
"busted", "hero", "complete" -> return@dispatchWaiting
@@ -43,17 +43,17 @@ class WinInstruction(val message: BattleMessage): InterpreterInstruction {
val losersText = losers.map { it.getName() }.reduce { acc, next -> acc + " & " + next }
val wasCaught = battle.showdownMessages.any { "capture" in it }
battle.dispatch {
// If the battle was a PvW battle, we need to set the killer of the wild Pokémon to the player
if (battle.isPvW) {
val nonPlayerActor = battle.actors.first { it.type == ActorType.WILD }
val wildPokemon: BattlePokemon = nonPlayerActor.pokemonList.first()
// If the battle was a PvW battle, we need to set the killer of the wild Pokémon to the player
if (battle.isPvW) {
val nonPlayerActor = battle.actors.first { it.type == ActorType.WILD }
val wildPokemon: BattlePokemon = nonPlayerActor.pokemonList.first()
if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
}
if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
}
}
battle.dispatch {
// broadcast victory / defeat
if (!wasCaught) {
val blackedOut = battle.isPvW && losers.any { it is PlayerBattleActor }
@@ -36,10 +36,18 @@ import java.util.function.Function
open class BattlePokemon(
val originalPokemon: Pokemon,
val effectedPokemon: Pokemon = originalPokemon,
val postBattleEntityOperation: (PokemonEntity) -> Unit = {}
var postBattlePokemonOperations: MutableList<(BattlePokemon) -> Unit> = mutableListOf(),
var postBattleEntityOperations: MutableList<(PokemonEntity) -> Unit> = mutableListOf()
) {
lateinit var actor: BattleActor
constructor(originalPokemon: Pokemon, effectedPokemon: Pokemon, postBattleEntityOperation: (PokemonEntity) -> Unit) : this(
originalPokemon = originalPokemon,
effectedPokemon = effectedPokemon,
postBattlePokemonOperations = mutableListOf<(BattlePokemon) -> Unit>(),
postBattleEntityOperations = mutableListOf(postBattleEntityOperation)
)
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?)
@@ -27,6 +27,7 @@ interface ShowdownService {
fun openConnection()
fun closeConnection()
fun startBattle(battle: PokemonBattle, messages: Array<String>)
fun endBattle(battle: PokemonBattle)
fun send(battleId: UUID, messages: Array<String>)
fun getRegistryData(type: String): JsonArray
fun sendRegistryData(data: Map<String, String>, type: String)
@@ -129,6 +129,11 @@ class GraalShowdownService : ShowdownService {
startBattleFunction.execute(this, battle.battleId.toString(), messages)
}
override fun endBattle(battle: PokemonBattle) {
val endBattleFunction = context.getBindings("js").getMember("endBattle")
endBattleFunction.execute(battle.battleId.toString())
}
override fun send(battleId: UUID, messages: Array<String>) {
sendToShowdown(battleId, messages)
}
@@ -62,6 +62,11 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
send(battle.battleId, messages)
}
override fun endBattle(battle: PokemonBattle) {
writer.write(">endbattle ${battle.battleId}\n")
acknowledge { Cobblemon.LOGGER.error("Failed to end battle ${battle.battleId}!") }
}
override fun send(battleId: UUID, messages: Array<String>) {
for (message in messages) {
writer.write("$battleId~$message\n")
@@ -1,42 +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.block
import net.minecraft.world.item.Item
import net.minecraft.core.dispenser.DispenseItemBehavior
import net.minecraft.world.item.Items
import net.minecraft.world.item.alchemy.PotionContents
import net.minecraft.world.item.alchemy.Potions
import net.minecraft.world.level.block.DispenserBlock
object DispenserBehaviorRegistry {
private val behaviors = mutableMapOf<Item, DispenseItemBehavior>()
fun register(items: List<Item>, behaviorProvider: (Item) -> DispenseItemBehavior) {
for (item in items) {
behaviors[item] = behaviorProvider(item)
}
}
fun applyBehaviors() {
behaviors.forEach { (item, behavior) ->
DispenserBlock.registerBehavior(item, behavior)
}
}
// TODO: Seems like there can only 1 behavior per item, so registering the leaves with the honey bottle overrode the honey bottle behavior for the log.
// Registering a new behavior with `DispenserBlock.registerBehavior` will override any existing ones for that item, possibly even ones from Minecraft and other mods
// Will probably need to use an alternate way of implementing this to avoid conflicts.
fun registerDispenserBehaviors() {
// register(listOf(PotionContents.createItemStack(Items.POTION, Potions.WATER).item)) { SaccharineLogSlatheredBlock.createBehavior() }
// register(listOf(Items.HONEY_BOTTLE)) { SaccharineLogBlock.createBehavior() }
// register(listOf(Items.HONEY_BOTTLE, Items.GLASS_BOTTLE)) { item -> SaccharineLeafBlock.createBehavior(item)}
applyBehaviors()
}
}
@@ -19,6 +19,7 @@ import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.BaseEntityBlock
@@ -110,6 +111,8 @@ class DisplayCaseBlock(settings: Properties) : BaseEntityBlock(settings) {
override fun getRenderShape(state: BlockState) = RenderShape.MODEL
override fun getShadeBrightness(state: BlockState, level: BlockGetter, pos: BlockPos): Float = 0.75F
override fun getAnalogOutputSignal(state: BlockState, world: Level, pos: BlockPos): Int {
val stack = (world.getBlockEntity(pos) as DisplayCaseBlockEntity).getStack()
@@ -29,6 +29,7 @@ import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
@@ -43,7 +44,6 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
val CODEC = simpleCodec(::HeartyGrainsBlock)
const val MATURE_AGE = 6
const val AGE_AFTER_HARVEST = 3 // Last single block stage
val WATERLOGGED = BlockStateProperties.WATERLOGGED
val AGE: IntegerProperty = IntegerProperty.create("age", 0, MATURE_AGE)
val HALF: EnumProperty<DoubleBlockHalf> = BlockStateProperties.DOUBLE_BLOCK_HALF
@@ -0,0 +1,128 @@
/*
* 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.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
companion object {
val CODEC: MapCodec<PlaqueBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::PlaqueBlock)
}
val FLOOR_NORTH = Shapes.or(
Shapes.box(0.0625, 0.25, 0.4375, 0.9375, 1.0, 0.5625),
Shapes.box(0.125, 0.0, 0.4375, 0.25, 0.25, 0.5625),
Shapes.box(0.75, 0.0, 0.4375, 0.875, 0.25, 0.5625),
)
val WALL_NORTH = Shapes.box(0.0625, 0.125, 0.9375, 0.9375, 0.875, 1.0)
val CEILING_NORTH = Shapes.box(0.0625, 0.0, 0.4375, 0.9375, 0.75, 0.5625)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(WATERLOGGED, false)
)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, FACE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
for (direction in context.nearestLookingDirections) {
val blockState: BlockState
if (direction.getAxis() === Direction.Axis.Y) {
blockState = (defaultBlockState()
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
} else {
blockState = (defaultBlockState()
.setValue(FACE, AttachFace.WALL) as BlockState)
.setValue(FACING, direction.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
}
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
}
return null
}
override fun codec(): MapCodec<PlaqueBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
} else FLOOR_NORTH
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
}
AttachFace.CEILING ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
} else CEILING_NORTH
}
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return if (state.getValue(FACE) == AttachFace.CEILING) {
val direction = getConnectedDirection(state)
canSupportCenter(level, pos.relative(direction.opposite), direction)
} else {
super.canSurvive(state, level, pos)
}
}
}
@@ -28,6 +28,8 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.alchemy.PotionContents
import net.minecraft.world.item.alchemy.Potions
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
@@ -67,9 +69,16 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
if (blockState.block is SaccharineLeafBlock) {
val currentAge = blockState.getValue(AGE)
val newAge = when {
item == Items.HONEY_BOTTLE && currentAge < MAX_AGE -> (currentAge + 2).coerceAtMost(MAX_AGE)
item == Items.GLASS_BOTTLE && currentAge > MIN_AGE -> (currentAge - 2).coerceAtLeast(MIN_AGE)
val waterBottle = PotionContents.createItemStack(Items.POTION, Potions.WATER).item
val newAge = when (item) {
Items.HONEY_BOTTLE if currentAge < MAX_AGE ->
(currentAge + 2).coerceAtMost(MAX_AGE)
waterBottle if currentAge > MIN_AGE ->
(currentAge - 2).coerceAtLeast(MIN_AGE)
else -> currentAge
}
@@ -79,21 +88,23 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
stack.shrink(1)
val dispenserEntity = source.blockEntity
val outputItem = if (item == Items.HONEY_BOTTLE) Items.GLASS_BOTTLE else Items.HONEY_BOTTLE
val outputItem = Items.GLASS_BOTTLE
val outputStack = ItemStack(outputItem)
var added = false
for (i in 0 until dispenserEntity.containerSize) {
val slotStack = dispenserEntity.getItem(i)
if (dispenserEntity != null) {
for (i in 0 until dispenserEntity.containerSize) {
val slotStack = dispenserEntity.getItem(i)
if (slotStack.isEmpty) {
dispenserEntity.setItem(i, outputStack.copy())
added = true
break
} else if (slotStack.`is`(outputItem) && slotStack.count < slotStack.maxStackSize) {
slotStack.grow(1)
added = true
break
if (slotStack.isEmpty) {
dispenserEntity.setItem(i, outputStack.copy())
added = true
break
} else if (slotStack.`is`(outputItem) && slotStack.count < slotStack.maxStackSize) {
slotStack.grow(1)
added = true
break
}
}
}
@@ -25,6 +25,7 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.DispenserBlock
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.RotatedPillarBlock
import net.minecraft.world.level.block.state.BlockState
@@ -37,7 +38,7 @@ class SaccharineLogBlock(properties: Properties) : RotatedPillarBlock(properties
fun createBehavior(): DispenseItemBehavior {
return DispenseItemBehavior { source, stack ->
val level = source.level
val facing = source.state.getValue(HorizontalDirectionalBlock.FACING)
val facing = source.state.getValue(DispenserBlock.FACING)
val pos = source.pos.relative(facing)
val blockState = level.getBlockState(pos)
@@ -50,7 +51,13 @@ class SaccharineLogBlock(properties: Properties) : RotatedPillarBlock(properties
}
}
fun changeLogTypeDispenser(level: ServerLevel, pos: BlockPos, newState: BlockState, stack: ItemStack, source: BlockSource) {
fun changeLogTypeDispenser(
level: ServerLevel,
pos: BlockPos,
newState: BlockState,
stack: ItemStack,
source: BlockSource
) {
level.setBlock(pos, newState, 3)
level.gameEvent(null, GameEvent.BLOCK_CHANGE, pos)
@@ -75,11 +82,23 @@ class SaccharineLogBlock(properties: Properties) : RotatedPillarBlock(properties
}
if (!added) {
Containers.dropItemStack(level, source.pos.x.toDouble(), source.pos.y.toDouble(), source.pos.z.toDouble(), glassBottle)
Containers.dropItemStack(
level,
source.pos.x.toDouble(),
source.pos.y.toDouble(),
source.pos.z.toDouble(),
glassBottle
)
}
}
fun changeLogType(level: Level, pos: BlockPos, newState: BlockState, player: Player? = null, itemStack: ItemStack? = null) {
fun changeLogType(
level: Level,
pos: BlockPos,
newState: BlockState,
player: Player? = null,
itemStack: ItemStack? = null
) {
level.setBlock(pos, newState, 3)
level.gameEvent(null, GameEvent.BLOCK_CHANGE, pos)
@@ -93,7 +112,15 @@ class SaccharineLogBlock(properties: Properties) : RotatedPillarBlock(properties
}
}
override fun useItemOn(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hand: InteractionHand, hitResult: BlockHitResult): ItemInteractionResult {
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hitResult: BlockHitResult
): ItemInteractionResult {
val axis = state.getValue(AXIS)
val itemStack = player.getItemInHand(hand)
if (!level.isClientSide && itemStack.`is`(Items.HONEY_BOTTLE) && axis == Direction.Axis.Y) {
@@ -0,0 +1,204 @@
/*
* 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.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import kotlin.math.min
class StackableItemBlock(settings: Properties, val type: StackableItemBlockType): HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
enum class StackableItemBlockType: StringRepresentable {
BATTLE_ITEM,
ETHER,
MAX_POTION,
POTION,
STATUS_RESTORE;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val MAX_AMOUNT: Int = 4
val AMOUNT: IntegerProperty = IntegerProperty.create("amount", 1, MAX_AMOUNT)
val CODEC: MapCodec<StackableItemBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
StackableItemBlockType.CODEC.fieldOf("type").forGetter(StackableItemBlock::type)
).apply(it, ::StackableItemBlock) }
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>> = mapOf(
StackableItemBlockType.BATTLE_ITEM to listOf(
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.34375, 0.40625, 0.4375, 0.65625),
Shapes.box(0.59375, 0.0, 0.40625, 0.90625, 0.4375, 0.71875)
),
Shapes.or(
Shapes.box(0.59375, 0.0, 0.59375, 0.90625, 0.4375, 0.90625),
Shapes.box(0.09375, 0.0, 0.53125, 0.40625, 0.4375, 0.84375),
Shapes.box(0.34375, 0.0, 0.09375, 0.65625, 0.4375, 0.40625)
),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.09375, 0.40625, 0.4375, 0.40625),
Shapes.box(0.59375, 0.0, 0.15625, 0.90625, 0.4375, 0.46875),
Shapes.box(0.65625, 0.0, 0.59375, 0.96875, 0.4375, 0.90625),
Shapes.box(0.15625, 0.0, 0.53125, 0.46875, 0.4375, 0.84375)
)
),
StackableItemBlockType.ETHER to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
Shapes.or(
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5, 0.75),
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5, 0.6875)
),
Shapes.or(
Shapes.box(0.375, 0.0, 0.125, 0.625, 0.5, 0.5),
Shapes.box(0.125, 0.0, 0.5625, 0.375, 0.5, 0.9375),
Shapes.box(0.625, 0.0, 0.625, 0.875, 0.5, 1.0)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.0625, 0.375, 0.5, 0.4375),
Shapes.box(0.625, 0.0, 0.125, 0.875, 0.5, 0.5),
Shapes.box(0.6875, 0.0, 0.625, 0.9375, 0.5, 1.0),
Shapes.box(0.1875, 0.0, 0.5625, 0.4375, 0.5, 0.9375)
)
),
StackableItemBlockType.MAX_POTION to listOf(
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.3125, 0.40625, 0.5, 0.6875),
Shapes.box(0.59375, 0.0, 0.375, 0.90625, 0.5, 0.75)
),
Shapes.or(
Shapes.box(0.34375, 0.0, 0.0625, 0.65625, 0.5, 0.4375),
Shapes.box(0.09375, 0.0, 0.5625, 0.40625, 0.5, 0.9375),
Shapes.box(0.59375, 0.0, 0.625, 0.90625, 0.5, 1.0)
),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.0625, 0.40625, 0.5, 0.4375),
Shapes.box(0.15625, 0.0, 0.5625, 0.46875, 0.5, 0.9375),
Shapes.box(0.65625, 0.0, 0.625, 0.96875, 0.5, 1.0),
Shapes.box(0.59375, 0.0, 0.125, 0.90625, 0.5, 0.5)
).singleEncompassing()
),
StackableItemBlockType.POTION to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
Shapes.or(
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5625, 0.75),
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5625, 0.8125)
),
Shapes.or(
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.5625, 0.5),
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.5625, 1.0),
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.5625, 0.9375)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.5625, 0.4375),
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.5625, 0.5),
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.5625, 1.0),
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.5625, 0.9375)
)
),
StackableItemBlockType.STATUS_RESTORE to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
Shapes.or(
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.4375, 0.75),
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.4375, 0.75)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.4375, 0.875),
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.4375, 0.9375),
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.4375, 0.4375)
),
Shapes.or(
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.4375, 0.875),
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.4375, 0.375),
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.4375, 0.4375),
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.4375, 0.9375)
)
)
)
fun getShape(type: StackableItemBlockType, count: Int): VoxelShape = northShapeMap[type]?.let { list -> list[(count - 1).coerceIn(0, list.lastIndex)] } ?: Shapes.block()
fun getMaxAmount(type: StackableItemBlockType): Int = min(northShapeMap[type]?.size ?: 1 , MAX_AMOUNT)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(AMOUNT, 1)
.setValue(WATERLOGGED, false))
}
override fun codec(): MapCodec<StackableItemBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, AMOUNT, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
val count = state.getValue(AMOUNT)
return rotateShape(Direction.NORTH, direction, getShape(type, count))
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return getCollisionShape(state, level, pos, context).singleEncompassing()
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val blockState = context.level.getBlockState(context.clickedPos)
val fluidState = context.level.getFluidState(context.clickedPos)
return if (blockState.`is`(this)) blockState.setValue(AMOUNT, min(getMaxAmount(type), (blockState.getValue(AMOUNT) + 1)))
else super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
}
override fun canBeReplaced(state: BlockState, useContext: BlockPlaceContext): Boolean {
return if (
!useContext.isSecondaryUseActive
&& useContext.itemInHand.`is`(this.asItem())
&& state.getValue(AMOUNT) <= getMaxAmount(type)
) true else super.canBeReplaced(state, useContext)
}
}
@@ -0,0 +1,81 @@
/*
* 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.block
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
open class WallAttachedDirectionalBlock(settings: Properties): HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
companion object {
val CODEC = simpleCodec(::WallAttachedDirectionalBlock)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(WATERLOGGED, false))
}
override fun codec(): MapCodec<out WallAttachedDirectionalBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, WATERLOGGED)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
var direction = context.clickedFace
if (direction === null || direction.axis === Direction.Axis.Y) {
direction = context.horizontalDirection.opposite
}
val fluidState = context.level.getFluidState(context.clickedPos)
val blockState = this.defaultBlockState()
.setValue(HORIZONTAL_FACING, direction)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
return null
}
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return canAttach(level, pos, state.getValue(HORIZONTAL_FACING))
}
fun canAttach(reader: LevelReader, pos: BlockPos, direction: Direction): Boolean {
val blockPos = pos.relative(direction.opposite)
return reader.getBlockState(blockPos).isFaceSturdy(reader, blockPos, direction)
}
}
@@ -0,0 +1,68 @@
/*
* 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.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class WallAttachedDirectionalShapeBlock(settings: Properties, val width: Int = 16, val height: Int = 16, val depth: Int = 16): WallAttachedDirectionalBlock(settings) {
companion object {
/**
* Calculates a [VoxelShape] based on a center point of (8, 8, 16).
*
* The resulting shape is centered X horizontally and Y vertically
* expanding inwards/North along the Z-axis from the South face (16).
*
* @param width The width of the shape from 0-16, along the X-axis.
* @param height The height of the shape from 0-16 along the Y-axis.
* @param depth The depth of the shape from 0-16 along the Z-axis, extending inwards from 16.
* @return A VoxelShape.
*/
fun convertToShape(sizeX: Int, sizeY: Int, sizeZ: Int): VoxelShape {
val x = sizeX.coerceIn(0, 16).toDouble()
val y = sizeY.coerceIn(0, 16).toDouble()
val z = sizeZ.coerceIn(0, 16).toDouble()
val minX = (8.0 - (x / 2.0)) / 16.0
val maxX = (8.0 + (x / 2.0)) / 16.0
val minY = (8.0 - (y / 2.0)) / 16.0
val maxY = (8.0 + (y / 2.0)) / 16.0
val minZ = (16.0 - z) / 16.0
return Shapes.box(minX, minY, minZ, maxX, maxY, 1.0)
}
}
override fun codec(): MapCodec<WallAttachedDirectionalShapeBlock> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
propertiesCodec(),
Codec.INT.fieldOf("width").forGetter { it.width },
Codec.INT.fieldOf("height").forGetter { it.height },
Codec.INT.fieldOf("depth").forGetter { it.depth }
).apply(instance, { properties, width, height, depth ->
WallAttachedDirectionalShapeBlock(properties, width, height, depth)
})
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return rotateShape(Direction.NORTH, direction, convertToShape(width, height, depth))
}
}
@@ -0,0 +1,100 @@
/*
* 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.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import kotlin.math.min
class WallAttachedStackableItemBlock(settings: Properties, val type: WallStackableItemBlockType): WallAttachedDirectionalBlock(settings) {
enum class WallStackableItemBlockType: StringRepresentable {
TAG;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val MAX_AMOUNT: Int = 8
val AMOUNT: IntegerProperty = IntegerProperty.create("amount", 1, MAX_AMOUNT)
val CODEC: MapCodec<WallAttachedStackableItemBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
WallStackableItemBlockType.CODEC.fieldOf("type").forGetter(WallAttachedStackableItemBlock::type)
).apply(it, ::WallAttachedStackableItemBlock) }
val northShapeMap: Map<WallStackableItemBlockType, List<VoxelShape>> = mapOf(
WallStackableItemBlockType.TAG to listOf(
Shapes.box(0.375, 0.25, 0.9375, 0.625, 0.6875, 1.0),
Shapes.box(0.375, 0.0625, 0.9375, 1.0, 0.6875, 1.0),
Shapes.box(0.0, 0.0625, 0.9375, 1.0, 0.9375, 1.0),
Shapes.box(0.0, 0.0625, 0.9375, 1.0, 1.0, 1.0),
Shapes.box(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0),
Shapes.box(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0),
Shapes.box(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0),
Shapes.box(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0)
)
)
fun getShape(type: WallStackableItemBlockType, count: Int): VoxelShape = northShapeMap[type]?.let { list -> list[(count - 1).coerceIn(0, list.lastIndex)] } ?: Shapes.block()
fun getMaxAmount(type: WallStackableItemBlockType): Int = min(northShapeMap[type]?.size ?: 1 , MAX_AMOUNT)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(AMOUNT, 1)
.setValue(WATERLOGGED, false))
}
override fun codec(): MapCodec<WallAttachedStackableItemBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(AMOUNT)
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
val count = state.getValue(AMOUNT)
return rotateShape(Direction.NORTH, direction, getShape(type, count))
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val blockState = context.level.getBlockState(context.clickedPos)
return if (blockState.`is`(this)) blockState.setValue(AMOUNT, min(getMaxAmount(type), (blockState.getValue(AMOUNT) + 1)))
else super.getStateForPlacement(context)
}
override fun canBeReplaced(state: BlockState, useContext: BlockPlaceContext): Boolean {
return if (
!useContext.isSecondaryUseActive
&& useContext.itemInHand.`is`(this.asItem())
&& state.getValue(AMOUNT) <= getMaxAmount(type)
) true else super.canBeReplaced(state, useContext)
}
}
@@ -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.block.dispenser
import net.minecraft.core.dispenser.BlockSource
import net.minecraft.core.dispenser.DispenseItemBehavior
import net.minecraft.world.item.ItemStack
import java.util.concurrent.CopyOnWriteArrayList
class ChainedDispenseItemBehavior(private val base: DispenseItemBehavior) : DispenseItemBehavior {
private val extras = CopyOnWriteArrayList<DispenseItemBehavior>()
fun add(behavior: DispenseItemBehavior) {
extras.add(behavior)
}
override fun dispense(blockSource: BlockSource, itemStack: ItemStack): ItemStack {
var current = itemStack
for (b in extras) {
val prev = current.copy()
val next = try {
b.dispense(blockSource, current)
} catch (t: Throwable) {
continue
}
current = next
if (handled(prev, current)) {
return current
}
}
val beforeBase = current.copy()
val afterBase = try {
base.dispense(blockSource, current)
} catch (_: Throwable) {
return current
}
if (handled(beforeBase, afterBase)) {
return afterBase
}
return afterBase
}
private fun handled(before: ItemStack, after: ItemStack): Boolean {
return !ItemStack.matches(before, after)
}
}
@@ -0,0 +1,90 @@
/*
* 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.block.dispenser
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import com.cobblemon.mod.common.block.SaccharineLogBlock
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
import com.cobblemon.mod.common.mixin.accessor.DispenserBlockAccessor
import net.minecraft.core.dispenser.DispenseItemBehavior
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.item.alchemy.PotionContents
import net.minecraft.world.item.alchemy.Potions
import net.minecraft.world.level.block.DispenserBlock
object DispenserBehaviorRegistry {
private val behaviors = mutableMapOf<Item, DispenseItemBehavior>()
fun register(items: List<Item>, behaviorProvider: (Item) -> DispenseItemBehavior) {
register(behaviorProvider, items = items)
}
fun register(vararg behaviorProviders: (Item) -> DispenseItemBehavior, items: List<Item>) {
for (item in items) {
val providedBehaviors = behaviorProviders.map { provider -> provider(item) }
val existingLocal = behaviors[item]
if (existingLocal is ChainedDispenseItemBehavior) {
providedBehaviors.forEach { existingLocal.add(it) }
continue
}
val currentRegistered = getCurrentRegisteredBehavior(item)
if (currentRegistered is ChainedDispenseItemBehavior) {
behaviors[item] = currentRegistered
providedBehaviors.forEach { currentRegistered.add(it) }
continue
}
val base = currentRegistered ?: DispenseItemBehavior.NOOP
val chained = ChainedDispenseItemBehavior(base)
providedBehaviors.forEach { chained.add(it) }
behaviors[item] = chained
DispenserBlock.registerBehavior(item, chained)
}
}
private fun getCurrentRegisteredBehavior(item: Item): DispenseItemBehavior? {
return try {
val map = DispenserBlockAccessor.getItemBehaviors()
map[item]
} catch (t: Throwable) {
DispenseItemBehavior.LOGGER.error("Failed to read existing dispenser behaviors via mixin accessor", t)
null
}
}
fun registerDispenserBehaviors() {
register(
{ SaccharineLogSlatheredBlock.createBehavior() },
items = listOf(
PotionContents.createItemStack(
Items.POTION,
Potions.WATER
).item
)
)
register(
{ SaccharineLogBlock.createBehavior() },
items = listOf(Items.HONEY_BOTTLE)
)
register(
{ item -> SaccharineLeafBlock.createBehavior(item) },
items = listOf(
Items.HONEY_BOTTLE,
PotionContents.createItemStack(
Items.POTION,
Potions.WATER
).item
)
)
}
}
@@ -74,6 +74,7 @@ import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.isLookingAt
import net.minecraft.client.Minecraft
import net.minecraft.client.color.block.BlockColor
@@ -83,16 +84,19 @@ import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.model.BoatModel
import net.minecraft.client.model.ChestBoatModel
import net.minecraft.client.model.PlayerModel
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.HangingSignRenderer
import net.minecraft.client.renderer.blockentity.SignRenderer
import net.minecraft.client.renderer.entity.EntityRenderer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import org.joml.Vector2d
object CobblemonClient {
@@ -112,6 +116,8 @@ object CobblemonClient {
val battleOverlay: BattleOverlay by lazy { BattleOverlay() }
val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() }
val rideControlsOverlay: RideControlsOverlay by lazy { RideControlsOverlay() }
val ridingTurnInputHandler: MutableMap<ResourceLocation, (LocalPlayer, Double, Double, Double) -> Vector2d> = mutableMapOf()
val ridingSprintHandler: MutableMap<ResourceLocation, () -> Boolean> = mutableMapOf()
fun onLogin() {
clientPlayerData = ClientGeneralPlayerData()
@@ -195,6 +201,8 @@ object CobblemonClient {
ClientPlayerIcon.onTick()
PartyOverlayDataControl.tick(event.client.isPaused)
}
ridingSprintHandler.put(cobblemonResource("normal")) { Minecraft.getInstance().options.keySprint.isDown }
}
private fun registerTooltipManagers() {
@@ -319,7 +327,48 @@ object CobblemonClient {
CobblemonBlocks.PINK_CAMPFIRE_POT,
CobblemonBlocks.RED_CAMPFIRE_POT,
CobblemonBlocks.WHITE_CAMPFIRE_POT,
CobblemonBlocks.YELLOW_CAMPFIRE_POT
CobblemonBlocks.YELLOW_CAMPFIRE_POT,
CobblemonBlocks.WHITE_PLAQUE,
CobblemonBlocks.LIGHT_GRAY_PLAQUE,
CobblemonBlocks.GRAY_PLAQUE,
CobblemonBlocks.BLACK_PLAQUE,
CobblemonBlocks.BROWN_PLAQUE,
CobblemonBlocks.RED_PLAQUE,
CobblemonBlocks.ORANGE_PLAQUE,
CobblemonBlocks.YELLOW_PLAQUE,
CobblemonBlocks.LIME_PLAQUE,
CobblemonBlocks.GREEN_PLAQUE,
CobblemonBlocks.CYAN_PLAQUE,
CobblemonBlocks.LIGHT_BLUE_PLAQUE,
CobblemonBlocks.BLUE_PLAQUE,
CobblemonBlocks.PURPLE_PLAQUE,
CobblemonBlocks.MAGENTA_PLAQUE,
CobblemonBlocks.PINK_PLAQUE,
CobblemonBlocks.BLUNDER_POLICY,
CobblemonBlocks.WEAKNESS_POLICY,
CobblemonBlocks.POTION,
CobblemonBlocks.SUPER_POTION,
CobblemonBlocks.HYPER_POTION,
CobblemonBlocks.MAX_POTION,
CobblemonBlocks.FULL_RESTORE,
CobblemonBlocks.ANTIDOTE,
CobblemonBlocks.AWAKENING,
CobblemonBlocks.BURN_HEAL,
CobblemonBlocks.ICE_HEAL,
CobblemonBlocks.PARALYZE_HEAL,
CobblemonBlocks.FULL_HEAL,
CobblemonBlocks.ETHER,
CobblemonBlocks.MAX_ETHER,
CobblemonBlocks.ELIXIR,
CobblemonBlocks.MAX_ELIXIR,
CobblemonBlocks.DIRE_HIT,
CobblemonBlocks.GUARD_SPEC,
CobblemonBlocks.X_ACCURACY,
CobblemonBlocks.X_ATTACK,
CobblemonBlocks.X_DEFENSE,
CobblemonBlocks.X_SP_ATK,
CobblemonBlocks.X_SP_DEF,
CobblemonBlocks.X_SPEED
)
this.createBoatModelLayers()
@@ -507,9 +507,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
override fun positionRider(passenger: Entity, positionUpdater: MoveFunction) {
val locatorName = getSeatLocator(passenger)
val locator = this.locatorStates[locatorName]
if (locator == null) return
val locator = this.locatorStates[locatorName] ?: return
val offset = locator.matrix.getTranslation(Vector3f())
.sub(
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toAssetPath
import java.util.UUID
import net.minecraft.client.DeltaTracker
import net.minecraft.client.Minecraft
@@ -38,6 +39,7 @@ import net.minecraft.client.gui.components.toasts.AdvancementToast
import net.minecraft.client.gui.components.toasts.Toast
import net.minecraft.client.gui.screens.ChatScreen
import net.minecraft.client.gui.screens.Screen
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import kotlin.math.ceil
import kotlin.math.floor
@@ -126,6 +128,10 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
val panelX = 0
val party = CobblemonClient.storage.party
val matrices = context.pose()
matrices.pushPose()
matrices.translate(0.0, 0.0, -100.0)
if (party.slots.none { it != null }) {
if (CobblemonClient.clientPlayerData.promptStarter &&
!CobblemonClient.clientPlayerData.starterLocked &&
@@ -171,10 +177,11 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
)
matrices.pushPose()
matrices.scale(1F, 1F, 0.2F)
matrices.translate(
panelX + portraitFrameOffsetX + selectedOffsetX + PORTRAIT_DIAMETER / 2.0 - 1.0,
y.toDouble() - 12,
0.0
-50.0
)
val shouldAnimate = when (Cobblemon.config.partyPortraitAnimations) {
@@ -467,7 +474,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
)
}
val ballIcon = cobblemonResource("textures/gui/ball/" + pokemon.caughtBall.name.path + ".png")
val ballIcon = pokemon.caughtBall.name.toAssetPath("textures/gui/ball/", ".png")
val ballHeight = 22
blitk(
matrixStack = matrices,
@@ -508,5 +515,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
}
}
}
matrices.popPose()
}
}
@@ -195,7 +195,7 @@ abstract class ScrollingWidget<T : ObjectSelectionList.Entry<T>>(
return this.width
}
override fun getRowTop(index: Int): Int {
public override fun getRowTop(index: Int): Int {
return this.y - scrollAmount.toInt() + (index * this.itemHeight)
}
@@ -205,6 +205,19 @@ abstract class ScrollingWidget<T : ObjectSelectionList.Entry<T>>(
override fun getScrollbarPosition(): Int = this.left + this.width - this.scrollBarWidth
public override fun ensureVisible(entry: T) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight - 4
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
abstract class Slot<T : Slot<T>>(): Entry<T>() {
// Override render to show each individual element
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.gui.battle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
@@ -17,9 +18,18 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleActionSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleBackButton
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGeneralActionSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleMoveSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleTargetSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.ForfeitConfirmationSelection
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -29,8 +39,11 @@ import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import org.joml.Vector4i
class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val OPTION_VERTICAL_SPACING = 3
const val OPTION_HORIZONTAL_SPACING = 3
@@ -51,11 +64,49 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
var queuedActions = mutableListOf<() -> Unit>()
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
messagePane = BattleMessagePane(CobblemonClient.battle!!.messages)
messagePane.opacity = CobblemonClient.battleOverlay.opacityRatio.toFloat().coerceAtLeast(0.3F)
addRenderableWidget(messagePane)
controllerComponentManager.componentList["spectating_quit"] = ControllerComponent().also {
it.onPress = {
quitSpectating()
}
}
controllerComponentManager.componentList["battle_action"] = SubManagerComponent().also {
it.subManager = { getCurrentActionSelection()?.controllerComponentManager }
}
controllerComponentManager.let {
it.focused = it.componentList["battle_action"]
it.onActiveChange = { state ->
if (!state) {
getCurrentActionSelection()?.controllerComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "battle_action") getCurrentActionSelection()?.controllerComponentManager?.active = true
}
}
it.onBack = {
if (it.currentSelectedComponent == "battle_action") {
getCurrentActionSelection()?.controllerComponentManager?.onBack?.invoke()
} else {
minimizeBattle()
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ (getCurrentActionSelection() as? BattleMoveSelection)?.shiftButtonActive() ?: false },
Component.translatable("cobblemon.controller.battle.swap_position"),
true
))
}
}
fun changeActionSelection(newSelection: BattleActionSelection?) {
@@ -65,6 +116,11 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
if (newSelection != null) {
addRenderableWidget(newSelection)
}
if (controllerComponentManager.currentSelectedComponent == "battle_action" && controllerComponentManager.active) {
focused = null
getCurrentActionSelection()?.controllerComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
}
@@ -85,6 +141,38 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data != null) {
return data
}
val actionSelection = getCurrentActionSelection()
var newData: Vector4i? = null
when (actionSelection) {
is BattleGeneralActionSelection -> {
val tile = actionSelection.controllerComponentManager.focused?.associatedGUIElement as? BattleOptionTile
if (tile != null) {
newData = Vector4i(tile.x - 2, tile.y - 2, BattleOptionTile.OPTION_WIDTH + 4, BattleOptionTile.OPTION_HEIGHT + 4)
}
}
is BattleMoveSelection -> {
val move = actionSelection.moveTiles[actionSelection.selectedMoveIndex]
newData = Vector4i(move.x.toInt() - 10, move.y.toInt() - 2, BattleMoveSelection.MOVE_WIDTH + 12, BattleMoveSelection.MOVE_HEIGHT + 4)
}
is BattleSwitchPokemonSelection -> {
val tile = actionSelection.tiles[actionSelection.selectedTileIndex]
newData = Vector4i(tile.x.toInt() - 2, tile.y.toInt() - 2, BattleSwitchPokemonSelection.SwitchTile.SELECT_WIDTH + 2, BattleSwitchPokemonSelection.SwitchTile.SELECT_HEIGHT - 1)
}
is BattleTargetSelection -> {
val tile = actionSelection.baseTiles[actionSelection.selectedTileIndex]
newData = Vector4i(tile.x.toInt() - 2, tile.y.toInt() - 2, BattleTargetSelection.TARGET_WIDTH + 4, BattleTargetSelection.TARGET_HEIGHT + 2)
}
}
return newData
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
super.render(context, mouseX, mouseY, delta)
@@ -122,9 +210,10 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
val currentSelection = getCurrentActionSelection()
if (currentSelection == null || currentSelection is BattleGeneralActionSelection ) {
val hideLabelButton = if (controllerComponentManager.active) controllerComponentManager.getVisual(ResourceLocation.fromNamespaceAndPath("controlify", "gui_back")) else PartySendBinding.boundKey().displayName
drawScaledText(
context = context,
text = battleLang("ui.hide_label", PartySendBinding.boundKey().displayName),
text = battleLang("ui.hide_label", hideLabelButton),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = (Minecraft.getInstance().window.guiScaledHeight / 5),
opacity = 0.75F * opacity,
@@ -141,6 +230,17 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
)
}
renderSelection(context.pose())
val activeComponent: ControllerComponent?
if (battle.spectating) {
activeComponent = controllerComponentManager.componentList["spectator_quit"]
} else {
activeComponent = controllerComponentManager.componentList["battle_action"]
}
if (controllerComponentManager.focused != activeComponent) {
controllerComponentManager.focused = activeComponent
}
queuedActions.forEach { it() }
queuedActions.clear()
@@ -151,8 +251,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
return if (request.forceSwitch) {
BattleSwitchPokemonSelection(this, request)
} else {
@@ -192,15 +290,21 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
return minimizeBattle()
}
val battle = CobblemonClient.battle
if (battle?.spectating == true && specBackButton.isHovered(mouseX, mouseY)) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
if (specBackButton.isHovered(mouseX, mouseY)) {
quitSpectating()
}
return super.mouseClicked(mouseX, mouseY, button)
}
private fun minimizeBattle(): Boolean {
fun quitSpectating() {
val battle = CobblemonClient.battle
if (battle?.spectating == true) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
}
}
fun minimizeBattle(): Boolean {
val battle = CobblemonClient.battle ?: return false
battle.minimised = !battle.minimised
PartySendBinding.actioned()
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
import com.cobblemon.mod.common.client.battle.ClientBallDisplay
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint
import com.cobblemon.mod.common.client.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -142,9 +143,14 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
if (Minecraft.getInstance().screen !is BattleGUI && battle.mustChoose) {
val textOpacity = PROMPT_TEXT_OPACITY_CURVE(passedSeconds)
val bindingComponent = if (Cobblemon.implementation.isModInstalled("controlify") && CobblemonControlifyEntrypoint.controllerActive())
CobblemonControlifyEntrypoint.SEND_OUT_POKEMON!!.inputGlyph()
else PartySendBinding.boundKey().displayName
drawScaledText(
context = context,
text = battleLang("ui.actions_label", PartySendBinding.boundKey().displayName),
text = battleLang("ui.actions_label", bindingComponent),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = Minecraft.getInstance().window.guiScaledHeight / 5,
opacity = textOpacity,
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.api.gui.ParentWidget
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.network.chat.MutableComponent
@@ -26,6 +27,8 @@ abstract class BattleActionSelection(
val opacity: Float
get() = battleGUI.opacity
val controllerComponentManager = ControllerComponentManager(battleGUI)
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if(pButton == InputConstants.MOUSE_BUTTON_LEFT){
return mousePrimaryClicked(pMouseX, pMouseY)
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.client.CobblemonClient
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.widgets.BattleOptionTile
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
@@ -40,6 +41,7 @@ class BattleGeneralActionSelection(
val lastAnwseredRequest = CobblemonClient.battle?.getLastAnsweredRequest()
val tiles = mutableListOf<BattleOptionTile>()
init {
var rank = 0
@@ -75,22 +77,41 @@ class BattleGeneralActionSelection(
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
battleGUI.minimizeBattle()
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
private fun addOption(rank: Int, text: MutableComponent, texture: ResourceLocation, onClick: () -> Unit) {
val startY = Minecraft.getInstance().window.guiScaledHeight - BattleGUI.OPTION_VERTICAL_OFFSET
val x = if (rank % 2 == 0) BattleGUI.OPTION_ROOT_X else BattleGUI.OPTION_ROOT_X + BattleGUI.OPTION_HORIZONTAL_SPACING + BattleOptionTile.OPTION_WIDTH
val y = if (rank > 1) startY + BattleOptionTile.OPTION_HEIGHT + BattleGUI.OPTION_HORIZONTAL_SPACING else startY
tiles.add(
BattleOptionTile(
battleGUI = battleGUI,
x = x,
y = y,
resource = texture,
text = text,
onClick = onClick
)
val tile = BattleOptionTile(
battleGUI = battleGUI,
x = x,
y = y,
resource = texture,
text = text,
onClick = onClick
)
tiles.add(tile)
val row = rank / 2
val column = rank % 2
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = tile
if (row == 1) it.up = { controllerComponentManager.componentList["tile_0_${column}"] }
else it.down = { controllerComponentManager.componentList["tile_1_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["tile_${row}_0"] }
else it.right = { controllerComponentManager.componentList["tile_${row}_1"] }
it.onPress = { tile.onClick.invoke() }
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -21,10 +21,12 @@ import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.toRGB
import com.cobblemon.mod.common.util.startWith
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
@@ -66,6 +68,10 @@ class BattleMoveSelection(
MoveTile(this, inBattleMove, x, y)
}
var moveTiles = baseTiles
set(value) {
field = value
recalculateMoveGrid()
}
val backButton = BattleBackButton(x - 11F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val gimmickButtons = moveSet.getGimmicks().filter { it !in moveSet.pendingGimmickUsedThisTurn }.mapIndexed { index, gimmick ->
@@ -76,6 +82,53 @@ class BattleMoveSelection(
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F )
private fun recalculateMoveGrid() {
controllerComponentManager.componentList.filter { it.key.startsWith("move_") }.forEach { controllerComponentManager.componentList.remove(it.key) }
moveTiles.forEachIndexed { index, tile ->
val column = index % 2
val row = index / 2
controllerComponentManager.componentList["move_${row}_${column}"] = ControllerComponent().also {
if (row == 1) it.up = { controllerComponentManager.componentList["move_${0}_${column}"] }
else it.down = { controllerComponentManager.componentList["move_${1}_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["move_${row}_0"] }
else it.right = { controllerComponentManager.componentList["move_${row}_1"] }
it.onPress = {
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
tile.onClick()
} else {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, tile.move, tile.response.gimmickID, tile.move.gimmickMove))
playDownSound(Minecraft.getInstance().soundManager)
}
}
it.onSpecial1 = {
if (shiftButtonActive()) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
}
it.onFocus = { tile.controllerSelected = true }
it.onUnfocus = { tile.controllerSelected = false }
it.onSelect = { selectedMoveIndex = index }
}
}
}
var selectedMoveIndex = 0
init {
recalculateMoveGrid()
controllerComponentManager.let {
it.focused = it.componentList["move_0_0"]
it.onBack = {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.changeActionSelection(null)
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
open class MoveTile(
val moveSelection: BattleMoveSelection,
val move: InBattleMove,
@@ -99,6 +152,8 @@ class BattleMoveSelection(
}
}
var controllerSelected = false
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val selectConditionOpacity = moveSelection.opacity * if (!selectable) 0.5F else 1F
@@ -110,7 +165,7 @@ class BattleMoveSelection(
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (selectable && isHovered(mouseX.toDouble(), mouseY.toDouble())) MOVE_HEIGHT else 0,
vOffset = if (selectable && (isHovered(mouseX.toDouble(), mouseY.toDouble()) || controllerSelected)) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -175,7 +230,7 @@ class BattleMoveSelection(
)
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT)
fun onClick() {
if (!selectable) return
@@ -184,6 +239,10 @@ class BattleMoveSelection(
}
}
fun shiftButtonActive(): Boolean {
return this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
moveTiles.forEach {
it.render(context, mouseX, mouseY, delta)
@@ -192,7 +251,7 @@ class BattleMoveSelection(
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')) {
if(shiftButtonActive()) {
shiftButton.render(context, mouseX, mouseY, delta)
}
}
@@ -214,7 +273,7 @@ class BattleMoveSelection(
} else if (gimmick != null) {
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) && shiftButtonActive()) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -31,12 +32,14 @@ import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.cobblemon.mod.common.util.toAssetPath
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.narration.NarratableEntry
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.resources.ResourceLocation
import org.joml.Quaternionf
import org.joml.Vector3f
@@ -66,6 +69,8 @@ class BattleSwitchPokemonSelection(
var isReviving = false
var selectedTileIndex = 0
init {
val pendingActionRequests = CobblemonClient.battle!!.pendingActionRequests
val switchingInPokemon = pendingActionRequests.mapNotNull { it.response }.filterIsInstance<SwitchActionResponse>().map { it.newPokemonId }
@@ -90,6 +95,33 @@ class BattleSwitchPokemonSelection(
tiles.add(SwitchTile(this, slotX, slotY, pokemon, showdownPokemon, isFainted, isCurrentlyInBattle))
}
tiles.forEachIndexed { index, tile ->
val row = index / 2
val column = index % 2
val maxRow = (tiles.size + 1) / 2
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["tile_${row-1}_${column}"] }
if (row + 1 < maxRow) it.down = { controllerComponentManager.componentList["tile_${row+1}_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["tile_${row}_0"] }
else if (row != maxRow || tiles.size % 2 == 0) it.right = { controllerComponentManager.componentList["tile_${row}_1"] }
it.onPress = { clickTile(tile) }
it.onFocus = { tile.controllerSelected = true }
it.onUnfocus = { tile.controllerSelected = false }
it.onSelect = { selectedTileIndex = index }
}
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
battleGUI.changeActionSelection(null)
playDownSound(Minecraft.getInstance().soundManager)
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
fun getSlotPosition(index: Int): Pair<Float, Float> {
@@ -152,11 +184,15 @@ class BattleSwitchPokemonSelection(
playDownSound(Minecraft.getInstance().soundManager)
return true
}
val clicked = tiles.find { it.isHovered(mouseX, mouseY) && if (isReviving) it.isFainted else (!it.isFainted && !it.isCurrentlyInBattle) } ?: return false
val pokemon = clicked.pokemon
val clicked = tiles.find { it.isHovered(mouseX, mouseY) } ?: return false
return clickTile(clicked)
}
fun clickTile(tile: SwitchTile): Boolean {
if (!(if (isReviving) tile.isFainted else (!tile.isFainted && !tile.isCurrentlyInBattle))) return false
val pokemon = tile.pokemon
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, SwitchActionResponse(pokemon.uuid))
return true
}
@@ -188,7 +224,9 @@ class BattleSwitchPokemonSelection(
val state = FloatingState()
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT))
var controllerSelected = false
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT)))
fun render(context: GuiGraphics, mouseX: Double, mouseY: Double, deltaTicks: Float) {
state.currentAspects = pokemon.aspects
@@ -227,11 +265,11 @@ class BattleSwitchPokemonSelection(
y = y,
width = SELECT_WIDTH,
height = SELECT_HEIGHT,
vOffset = if (!isFainted && (isHovered(mouseX, mouseY) || isCurrentlyInBattle)) SELECT_HEIGHT else 0,
vOffset = if (!isFainted && (isHovered(mouseX, mouseY) || isCurrentlyInBattle || controllerSelected)) SELECT_HEIGHT else 0,
textureHeight = SELECT_HEIGHT * 2,
)
val ballIcon = cobblemonResource("textures/gui/ball/" + pokemon.caughtBall.name.path + ".png")
val ballIcon = pokemon.caughtBall.name.toAssetPath("textures/gui/ball/", ".png")
val ballHeight = 22
blitk(
matrixStack = matrixStack,
@@ -26,6 +26,7 @@ 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.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.Gender
@@ -111,10 +112,35 @@ class BattleTargetSelection(
else -> if (isAlly) ArrowDirection.UP else ArrowDirection.DOWN
}
}
TargetTile(this, target, x, y, arrowDirection)
var out = TargetTile(this, target, x, y, arrowDirection)
controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos}"] = ControllerComponent().also {
if (isAlly) it.up = { controllerComponentManager.componentList["tile_1_${fieldPos}"] }
else it.down = { controllerComponentManager.componentList["tile_0_${fieldPos}"] }
if (fieldPos - 1 >= 0) it.left = { controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos-1}"] }
if (fieldPos + 1 < teamSize) it.right = { controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos+1}"] }
it.onPress = { out.onClick() }
it.onFocus = { out.controllerSelected = true }
it.onUnfocus = { out.controllerSelected = false }
it.onSelect = { selectedTileIndex = index }
}
out
}
var targetTiles = baseTiles
var selectedTileIndex = 0
init {
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.changeActionSelection(BattleMoveSelection(battleGUI, request))
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
open inner class TargetTile(
val targetSelection: BattleTargetSelection,
@@ -151,6 +177,8 @@ class BattleTargetSelection(
ArrowDirection.DOWN -> Pair(this.x + TARGET_WIDTH / 2 - 4F, this.y - 7)
}
var controllerSelected = false
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val passedSeconds = CobblemonClient.battleOverlay.passedSeconds // This syncs the role throbbers
val selectConditionOpacity = targetSelection.opacity * if (!selectable) 0.5F else 1F
@@ -304,9 +332,9 @@ class BattleTargetSelection(
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
if(isMultiTarget) {
return targetTiles.firstOrNull { it.selectable && mouseX >= it.x && mouseX <= it.x + TARGET_WIDTH && mouseY >= it.y && mouseY <= it.y + TARGET_HEIGHT } != null
return targetTiles.firstOrNull { it.selectable && ((mouseX >= it.x && mouseX <= it.x + TARGET_WIDTH && mouseY >= it.y && mouseY <= it.y + TARGET_HEIGHT) || it.controllerSelected) } != null
}
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT || controllerSelected
}
fun onClick() {
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.interact.battleRequest.BattleResponseButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
@@ -51,21 +52,52 @@ class ForfeitConfirmationSelection(
acceptButton = BattleResponseButton(xPos + 22, yPos + 18, true) {}
declineButton = BattleResponseButton(xPos + 57, yPos + 18, false) {}
controllerComponentManager.componentList["accept_button"] = ControllerComponent().also {
it.associatedGUIElement = acceptButton
it.onPress = {
onAccept()
playDownSound(Minecraft.getInstance().soundManager)
}
it.right = { controllerComponentManager.componentList["decline_button"] }
}
controllerComponentManager.componentList["decline_button"] = ControllerComponent().also {
it.associatedGUIElement = declineButton
it.onPress = {
onDecline()
playDownSound(Minecraft.getInstance().soundManager)
}
it.left = { controllerComponentManager.componentList["accept_button"] }
}
controllerComponentManager.let {
it.focused = it.componentList["decline_button"]
it.onBack = {
it.componentList["decline_button"]?.onPress?.invoke()
}
}
}
fun onAccept() {
battleGUI.selectAction(request, ForfeitActionResponse())
// Need to fill out any other pending requests
var pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
while (pendingRequest != null) {
battleGUI.selectAction(pendingRequest, PassActionResponse)
pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
}
}
fun onDecline() {
battleGUI.changeActionSelection(null)
}
override fun mousePrimaryClicked(mouseX: Double, mouseY: Double): Boolean {
if (declineButton.isHovered(mouseX, mouseY) || acceptButton.isHovered(mouseX, mouseY)) {
if (declineButton.isHovered(mouseX, mouseY)) {
battleGUI.changeActionSelection(null)
onDecline()
} else {
battleGUI.selectAction(request, ForfeitActionResponse())
// Need to fill out any other pending requests
var pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
while (pendingRequest != null) {
battleGUI.selectAction(pendingRequest, PassActionResponse)
pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
}
onAccept()
}
playDownSound(Minecraft.getInstance().soundManager)
return true
@@ -50,7 +50,7 @@ class BattleOptionTile(
alpha = opacity,
width = OPTION_WIDTH,
height = OPTION_HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) OPTION_HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || focused) OPTION_HEIGHT else 0,
textureHeight = OPTION_HEIGHT * 2
)
@@ -80,7 +80,7 @@ class BattleOptionTile(
override fun isFocused() = focused
fun isHovered(mouseX: Double, mouseY: Double) = mouseX > x && mouseY > y && mouseX < x + OPTION_WIDTH && mouseY < y + OPTION_HEIGHT
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX > x && mouseY > y && mouseX < x + OPTION_WIDTH && mouseY < y + OPTION_HEIGHT)
override fun updateNarration(builder: NarrationElementOutput) {
builder.add(NarratedElementType.TITLE, text)
@@ -10,6 +10,10 @@ package com.cobblemon.mod.common.client.gui.config
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.config.variable.CobblemonConfigVariable
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.ifServer
@@ -22,11 +26,14 @@ import net.minecraft.client.gui.components.StringWidget
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout
import net.minecraft.client.gui.layouts.LinearLayout
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.CommonComponents
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvents
class CobblemonConfigScreen(
private val previousScreen: Screen?,
) : Screen("gui.cobblemon_config.title".asTranslated()), CobblemonRenderable {
) : Screen("gui.cobblemon_config.title".asTranslated()), CobblemonRenderable, ControllerScreen {
companion object {
const val HEIGHT = 240
@@ -54,6 +61,13 @@ class CobblemonConfigScreen(
private var searchString: String = ""
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedEntry = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
@@ -97,6 +111,46 @@ class CobblemonConfigScreen(
addRenderableWidget(widget)
}
arrangeLayoutElements()
controllerComponentManager.componentList["search"] = ControllerComponent().also {
it.associatedGUIElement = searchEdit
it.down = {
val variableEntries = variableList.children().filter { it is CobblemonConfigVariable }
if (variableEntries.isNotEmpty()) {
controllerComponentManager.componentList["entry_${lastSelectedEntry}_input"]
} else {
controllerComponentManager.componentList["done"]
}
}
}
controllerComponentManager.componentList["done"] = ControllerComponent().also {
it.associatedGUIElement = doneButton
it.up = {
val variableEntries = variableList.children().filter { it is CobblemonConfigVariable }
if (variableEntries.isNotEmpty()) {
controllerComponentManager.componentList["entry_${lastSelectedEntry}_input"]
} else {
controllerComponentManager.componentList["search"]
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["search"]
it.onBack = {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 1.0F))
doneButtonClick()
}
it.onStart = {
it.focused = it.componentList["search"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 1.0F))
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
}
}
private fun arrangeLayoutElements() {
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companio
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.SLOT_WIDTH
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigVariableList.CobblemonConfigVariableListEntry
import com.cobblemon.mod.common.client.gui.config.variable.*
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.Category
import com.cobblemon.mod.common.config.CobblemonConfig
@@ -134,6 +135,63 @@ class CobblemonConfigVariableList(
}
scrollAmount = min(maxScroll.toDouble(), scrollAmount)
val variableEntries = children().filter { it is CobblemonConfigVariable }
variableEntries.forEachIndexed { index, entry ->
parent.controllerComponentManager.componentList["entry_${index}_input"] = ControllerComponent().also {
when (entry) {
is CobblemonConfigButtonVariable -> it.associatedGUIElement = entry.button
is CobblemonConfigEditBoxVariable -> it.associatedGUIElement = entry.editBox
}
it.right = {
if ((entry as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index}_reset"]
}
else null
}
if (index + 1 <= variableEntries.size - 1) it.down = { parent.controllerComponentManager.componentList["entry_${index+1}_input"] }
else it.down = { parent.controllerComponentManager.componentList["done"] }
if (index - 1 >= 0) it.up = { parent.controllerComponentManager.componentList["entry_${index-1}_input"] }
else it.up = { parent.controllerComponentManager.componentList["search"] }
it.onSelect = { parent.lastSelectedEntry = index }
it.onFocus = { ensureVisible(entry) }
}
parent.controllerComponentManager.componentList["entry_${index}_reset"] = ControllerComponent().also {
it.associatedGUIElement = (entry as CobblemonConfigVariable).resetButton
it.left = { parent.controllerComponentManager.componentList["entry_${index}_input"] }
if (index + 1 <= variableEntries.size - 1) it.down = {
if ((variableEntries[index+1] as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index+1}_reset"]
}
else null
}
else it.down = { parent.controllerComponentManager.componentList["done"] }
if (index - 1 >= 0) it.up = {
if ((variableEntries[index-1] as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index-1}_reset"]
}
else null
}
else it.up = { parent.controllerComponentManager.componentList["search"] }
it.onPress = {
entry.resetButton.also { button ->
button.onPress()
button.playDownSound(Minecraft.getInstance().soundManager)
parent.controllerComponentManager.also { manager ->
manager.focused = manager.componentList["entry_${index}_input"]
}
}
}
it.onSelect = { parent.lastSelectedEntry = index }
it.onFocus = { ensureVisible(entry) }
}
}
parent.controllerComponentManager.let {
if (it.currentSelectedComponent.startsWith("entry_")) {
it.focused = it.componentList["search"]
}
}
parent.lastSelectedEntry = 0
}
abstract class CobblemonConfigVariableListEntry(
@@ -26,6 +26,9 @@ import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueOptionWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialoguePortraitWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTextInputWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTimerWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueDTO
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueGibberDTO
@@ -45,9 +48,10 @@ import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable {
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable, ControllerScreen {
val speakers = dialogueDTO.speakers?.mapNotNull { (key, value) ->
val name = value.name
when (val face = value.face) {
@@ -141,6 +145,12 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
)
}
var controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBackground(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {}
override fun renderBlurredBackground(delta: Float) {}
@@ -242,6 +252,34 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
}
this.dialogueStartTick = minecraft!!.player!!.tickCount
controllerComponentManager = ControllerComponentManager(this)
controllerComponentManager.componentList["none"] = ControllerComponent().also {
it.onPress = { dialogueBox.onClick() }
}
dialogueOptionWidgets.forEachIndexed { index, option ->
controllerComponentManager.componentList["option_${index}"] = ControllerComponent().also {
it.associatedGUIElement = option
if (index + 1 <= dialogueOptionWidgets.size - 1) it.right = { controllerComponentManager.componentList["option_${index+1}"] }
if (index - 1 >= 0) it.left = { controllerComponentManager.componentList["option_${index-1}"] }
it.onPress = { if (!option.onClick()) dialogueBox.onClick() }
}
}
controllerComponentManager.let {
it.focused = if (dialogueOptionWidgets.isNotEmpty()) {
it.componentList["option_0"]
} else {
it.componentList["none"]
}
it.onSpecial1 = { dialogueBox.onClick() }
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ dialogueBox.dialogue.dialogueInput.allowSkip && dialogueBox.dialogue.dialogueInput.inputType in listOf(DialogueInputDTO.InputType.NONE, DialogueInputDTO.InputType.AUTO_CONTINUE)},
Component.translatable("cobblemon.controller.dialogue.skip"),
true
))
}
}
var gibberIndex = 0
@@ -265,6 +303,8 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
super.render(guiGraphics, mouseX, mouseY, delta)
expressionsToRun.forEach { runtime.resolve(it) }
expressionsToRun = emptyList()
renderSelection(guiGraphics.pose())
}
override fun isPauseScreen() = false
@@ -161,10 +161,17 @@ class DialogueBox(
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
// TODO change this coordinate check to just be "anywhere the scroll bar isn't"
if (!dialogueScreen.waitingForServerUpdate &&
mouseX > this.x && mouseX < this.scrollbarPosition &&
if (mouseX > this.x && mouseX < this.scrollbarPosition &&
mouseY > this.y && mouseY < this.bottom
) {
if (onClick()) return true
}
return super.mouseClicked(mouseX, mouseY, button)
}
fun onClick(): Boolean {
if (!dialogueScreen.waitingForServerUpdate) {
if (dialogue.dialogueInput.allowSkip && dialogue.dialogueInput.inputType in listOf(DialogueInputDTO.InputType.NONE, DialogueInputDTO.InputType.AUTO_CONTINUE)) {
val gibber = dialogueScreen.gibber
if (gibber != null && gibber.graduallyShowText && !dialogueScreen.gibberDone) {
@@ -178,8 +185,7 @@ class DialogueBox(
return true
}
}
return super.mouseClicked(mouseX, mouseY, button)
return false
}
class DialogueLine(val line: FormattedCharSequence, val lineColor: Int) : Entry<DialogueLine>() {
@@ -42,7 +42,7 @@ class DialogueOptionWidget(
y = y,
width = width,
height = height,
vOffset = if (selectable) (if (isHovered) height else 0) else (height * 2),
vOffset = if (selectable) (if (isHovered || isFocused) height else 0) else (height * 2),
textureHeight = height * 3,
)
@@ -58,10 +58,14 @@ class DialogueOptionWidget(
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (!isHovered) return false
return onClick()
}
fun onClick(): Boolean {
if (!dialogueScreen.renderInput()) {
return false
}
if (!isHovered) return false
if (!selectable || dialogueScreen.waitingForServerUpdate) return true
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.gui.interact.battleRequest
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
@@ -16,6 +17,9 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
import com.cobblemon.mod.common.client.battle.ClientTeamRequest
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.server.*
@@ -27,16 +31,18 @@ import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.util.*
class BattleConfigureGUI(
private val packet: PlayerInteractOptionsPacket,
private val activeRequest: ClientBattleChallenge? = null,
private val activeTeamRequest: ClientTeamRequest? = null
) : Screen(lang("ui.challenge.challenge_title")), CobblemonRenderable {
) : Screen(lang("ui.challenge.challenge_title")), CobblemonRenderable, ControllerScreen {
companion object {
const val SIZE = 113
private val backgroundResource = cobblemonResource("textures/gui/interact/request/battle_request.png")
@@ -200,6 +206,12 @@ class BattleConfigureGUI(
private var hasRequest = false
private var ticksPassed = 0F
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) { }
@@ -218,29 +230,29 @@ class BattleConfigureGUI(
}
val (x, y) = getDimensions()
var battleAccept: BattleResponseButton? = null
var battleDecline: BattleResponseButton? = null
if(pendingRequest != null) {
// Draw Accept/Decline buttons
this.addRenderableWidget(
BattleResponseButton(
x + 22,
y + 99,
true
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, true) }
closeGUI()
}
)
battleAccept = BattleResponseButton(
x + 22,
y + 99,
true
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, true) }
closeGUI()
}
this.addRenderableWidget(battleAccept)
this.addRenderableWidget(
BattleResponseButton(
x + 56,
y + 99,
false
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, false) }
closeGUI()
}
)
battleDecline = BattleResponseButton(
x + 56,
y + 99,
false
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, false) }
closeGUI()
}
this.addRenderableWidget(battleDecline)
}
else {
@@ -306,6 +318,36 @@ class BattleConfigureGUI(
this.addRenderableWidget(autoLevelNavButtonRight)
}
}
controllerComponentManager.componentList["battle_accept"] = ControllerComponent().also {
it.associatedGUIElement = battleAccept
it.right = { controllerComponentManager.componentList["battle_decline"] }
}
controllerComponentManager.componentList["battle_decline"] = ControllerComponent().also {
it.associatedGUIElement = battleDecline
it.left = { controllerComponentManager.componentList["battle_accept"] }
}
controllerComponentManager.componentList["request"] = ControllerComponent().also {
it.associatedGUIElement = requestButton
}
controllerComponentManager.let {
if (pendingRequest != null) {
it.focused = it.componentList["battle_decline"]
} else {
it.focused = it.componentList["request"]
it.onTabNav = { nextTab ->
currentPage = (currentPage + (if (nextTab) 1 else -1)) % options.count()
updateRequestButtonText()
playSound(CobblemonSounds.GUI_CLICK)
}
if (battleRequestMap[options[currentPage]]?.option != PlayerInteractOptionsPacket.Options.TEAM_REQUEST) {
it.onSecondaryTabNav = { nextTab ->
levelRulesetOptionIndex += if (nextTab) 1 else -1
playSound(CobblemonSounds.GUI_CLICK)
}
}
}
}
}
fun updateRequestButtonText() {
@@ -438,6 +480,8 @@ class BattleConfigureGUI(
)
super.render(context, mouseX, mouseY, delta)
renderSelection(context.pose())
}
private fun getDimensions(): Pair<Int, Int> {
@@ -454,4 +498,7 @@ class BattleConfigureGUI(
Minecraft.getInstance().setScreen(null)
}
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
@@ -35,7 +35,7 @@ class BattleResponseButton(
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()))
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()) || isFocused)
blitk(
matrixStack = context.pose(),
x = x / SCALE,
@@ -14,6 +14,9 @@ import com.cobblemon.mod.common.api.callback.MoveSelectDTO
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -36,7 +39,7 @@ class MoveSelectConfiguration(
class MoveSelectGUI(
val config: MoveSelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable, ControllerScreen {
companion object {
const val WIDTH = 122
const val HEIGHT = 133
@@ -46,6 +49,8 @@ class MoveSelectGUI(
var closed = false
val controllerComponentManager = ControllerComponentManager(this)
constructor(
title: MutableComponent,
moves: List<MoveSelectDTO>,
@@ -63,6 +68,10 @@ class MoveSelectGUI(
)
)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
fun closeProperly() {
closed = true
onClose()
@@ -73,16 +82,21 @@ class MoveSelectGUI(
val y = (height - HEIGHT) / 2
config.moves.forEachIndexed { index, move ->
addRenderableWidget(
MoveSlotButton(
x = x + 7,
y = y + 7 + ((MoveSlotButton.HEIGHT + 3) * index),
move = move.moveTemplate,
pp = move.pp,
ppMax = move.ppMax,
enabled = move.enabled
) { onPress(move) }
)
val moveSlot = MoveSlotButton(
x = x + 7,
y = y + 7 + ((MoveSlotButton.HEIGHT + 3) * index),
move = move.moveTemplate,
pp = move.pp,
ppMax = move.ppMax,
enabled = move.enabled
) { onPress(move) }
addRenderableWidget(moveSlot)
controllerComponentManager.componentList["move_${index}"] = ControllerComponent().also {
it.associatedGUIElement = moveSlot
if (index + 1 <= config.moves.size - 1) it.down = { controllerComponentManager.componentList["move_${index+1}"] }
if (index - 1 >= 0) it.up = { controllerComponentManager.componentList["move_${index-1}"] }
}
}
// Add Exit Button
@@ -97,6 +111,10 @@ class MoveSelectGUI(
)
super.init()
controllerComponentManager.let {
it.focused = it.componentList["move_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -114,6 +132,8 @@ class MoveSelectGUI(
// Render all added Widgets
super.render(context, mouseX, mouseY, partialTicks)
renderSelection(context.pose())
}
private fun onPress(move: MoveSelectDTO) {
@@ -47,7 +47,7 @@ class MoveSlotButton(
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
isHovered = ((pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height) || isFocused) && enabled
val moveTemplate = Moves.getByNameOrDummy(move.name)
val rgb = moveTemplate.elementalType.hue.toRGB()
@@ -16,6 +16,9 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
@@ -38,7 +41,7 @@ class PartySelectConfiguration(
)
class PartySelectGUI(
val config: PartySelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable, ControllerScreen {
companion object {
const val WIDTH = 163
const val HEIGHT = 132
@@ -50,6 +53,8 @@ class PartySelectGUI(
var closed = false
val controllerComponentManager = ControllerComponentManager(this)
constructor(
title: MutableComponent,
pokemon: List<PartySelectPokemonDTO>,
@@ -67,6 +72,10 @@ class PartySelectGUI(
)
)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
fun closeProperly() {
closed = true
onClose()
@@ -90,20 +99,29 @@ class PartySelectGUI(
slotY += (31 * offsetIndex) + offsetY
}
addRenderableWidget(
PartySlotButton(
parent = this,
x = slotX,
y = slotY,
pokemon = pokemon.pokemonProperties,
aspects = pokemon.aspects,
heldItem = pokemon.heldItem,
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.maxHealth,
enabled = pokemon.enabled,
hoverText = pokemon.hoverText,
) { onPress(pokemon) }
)
val slot = PartySlotButton(
parent = this,
x = slotX,
y = slotY,
pokemon = pokemon.pokemonProperties,
aspects = pokemon.aspects,
heldItem = pokemon.heldItem,
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.maxHealth,
enabled = pokemon.enabled,
hoverText = pokemon.hoverText,
) { onPress(pokemon) }
addRenderableWidget(slot)
val row = index / 2
val column = index % 2
controllerComponentManager.componentList["slot_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (column == 0) it.right = { controllerComponentManager.componentList["slot_${row}_${1}"] }
else it.left = { controllerComponentManager.componentList["slot_${row}_${0}"] }
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${row-1}_${column}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["slot_${row+1}_${column}"] }
}
}
// Add Exit Button
@@ -118,6 +136,10 @@ class PartySelectGUI(
)
super.init()
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -155,6 +177,8 @@ class PartySelectGUI(
// Render all added Widgets
super.render(context, mouseX, mouseY, partialTicks)
renderSelection(context.pose())
}
private fun onPress(pokemon: PartySelectPokemonDTO) {
@@ -24,11 +24,13 @@ import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.cobblemon.mod.common.util.toAssetPath
import com.google.common.collect.Lists
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FormattedCharSequence
import net.minecraft.world.item.ItemStack
import org.joml.Quaternionf
@@ -61,7 +63,7 @@ class PartySlotButton(
private val renderablePokemon = pokemon.asRenderablePokemon().also { it.aspects = aspects }
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
isHovered = ((pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height) || isFocused) && enabled
val alpha = if (enabled) 1.0 else 0.7
val matrices = context.pose()
@@ -111,7 +113,7 @@ class PartySlotButton(
)
context.pose().popPose()
val ballIcon = cobblemonResource("textures/gui/ball/" + pokemon.pokeball!!.asIdentifierDefaultingNamespace().path + ".png")
val ballIcon = pokemon.pokeball!!.asIdentifierDefaultingNamespace().toAssetPath("textures/gui/ball/", ".png")
val ballHeight = 22
blitk(
matrixStack = matrices,
@@ -28,7 +28,7 @@ class InteractWheelButton(
private val tooltipText: String?,
x: Int,
y: Int,
private val isEnabled: Boolean,
val isEnabled: Boolean,
private val colour: () -> Vector3f?,
onPress: OnPress,
private val canHover: (Double, Double) -> Boolean
@@ -74,12 +74,12 @@ class InteractWheelButton(
y = y,
width = width,
height = height,
vOffset = if (isHovered(mouseX.toFloat(), mouseY.toFloat()) && isEnabled) height else 0,
vOffset = if ((isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused) && isEnabled) height else 0,
textureHeight = height * 2,
alpha = if (isEnabled) 1f else 0.4F
)
if(isEnabled && isHovered(mouseX.toFloat(), mouseY.toFloat())){
if(isEnabled && (isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused)){
tooltipText?.let {
context.renderTooltip(Minecraft.getInstance().font, Component.translatable(it), mouseX, mouseY)
}
@@ -162,7 +162,7 @@ class InteractWheelButton(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
return if (isHovered(mouseX.toFloat(), mouseY.toFloat())) super.mouseClicked(mouseX, mouseY, button) else false
return if (isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused) super.mouseClicked(mouseX, mouseY, button) else false
}
override fun getTooltip(): Tooltip? {
@@ -23,9 +23,9 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
private val backgroundResource = cobblemonResource("textures/gui/interact/interact_wheel_base.png")
}
private val buttons = mutableListOf<InteractWheelButton>()
private var maxPage = 1
private var currentPage = 0
val buttons = mutableListOf<InteractWheelButton>()
var maxPage = 1
var currentPage = 0
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -77,7 +77,7 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
currentPage = page
buttons.forEach { removeWidget(it) }
buttons.clear()
val orientations = Orientation.values()
val orientations = Orientation.entries.toTypedArray()
orientations.forEach { orientation ->
val option = options[orientation].toList().getOrNull(page)
addButton(orientation, option)
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pc.StorageSlot
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -76,11 +77,58 @@ class PasturePokemonScrollList(
removedEntries.forEach(this::removeEntry)
newEntries.forEach { addEntry(PastureSlot(it, parent)) }
rebuildControllerSelection()
}
}
fun rebuildControllerSelection() {
if (parent.lastSelectedIndex > 0 && parent.lastSelectedIndex >= children().size) parent.lastSelectedIndex = children().size - 1
children().forEachIndexed { index, slot ->
parent.controllerComponentManager.componentList["slot_${index}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (index - 1 >= 0) it.up = { parent.controllerComponentManager.componentList["slot_${index - 1}"] }
if (index + 1 <= children().size - 1) it.down =
{ parent.controllerComponentManager.componentList["slot_${index + 1}"] }
else it.down = { parent.controllerComponentManager.componentList["recall_all"] }
it.onPress = { slot.moveButton.onPress() }
it.onSpecial1 = { slot.conflictButton.onPress() }
it.onFocus = {
slot.controllerFocused = true
ensureVisible(slot)
}
it.onUnfocus = {
slot.controllerFocused = false
}
}
}
if (parent.controllerComponentManager.currentSelectedComponent.let { it.startsWith("slot_") || it == "empty" }) {
parent.controllerComponentManager.focused =
if (children().isEmpty()) parent.controllerComponentManager.componentList["empty"]
else parent.controllerComponentManager.componentList["slot_${parent.lastSelectedIndex}"]
}
}
override fun getScrollbarPosition() = x + width - 3
public override fun ensureVisible(entry: PastureSlot) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight - 4
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
public override fun getRowTop(index: Int): Int {
return super.getRowTop(index)
}
public override fun addEntry(entry: PastureSlot) = super.addEntry(entry)
public override fun removeEntry(entry: PastureSlot) = super.removeEntry(entry)
override fun renderSelection(context: GuiGraphics, y: Int, entryWidth: Int, entryHeight: Int, borderColor: Int, fillColor: Int) {}
@@ -166,6 +214,8 @@ class PasturePokemonScrollList(
val client: Minecraft = Minecraft.getInstance()
val state = FloatingState()
var controllerFocused = false
fun isOwned() = client.player?.uuid == pokemon.playerId
fun canUnpasture() = isOwned() || parent.pasturePCGUIConfiguration.permissions.canUnpastureOthers
fun canDefend() = PokemonSpecies.getByIdentifier(pokemon.species)?.behaviour?.combat?.willDefendOwner
@@ -182,7 +232,7 @@ class PasturePokemonScrollList(
setEnabled(PokemonBehaviourFlag.PASTURE_CONFLICT in pokemon.behaviourFlags)
}
private val moveButton: PastureSlotIconButton = PastureSlotIconButton(
val moveButton: PastureSlotIconButton = PastureSlotIconButton(
xPos = 0,
yPos = 0,
onPress = {
@@ -191,7 +241,8 @@ class PasturePokemonScrollList(
pastureId = parent.pasturePCGUIConfiguration.pastureId,
pokemonId = pokemon.pokemonId
).sendToServer()
}
},
parent = this@PastureSlot
)
override fun getNarration() = pokemon.displayName
@@ -219,7 +270,7 @@ class PasturePokemonScrollList(
y = y,
height = SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || controllerFocused) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2
)
@@ -18,7 +18,8 @@ import net.minecraft.network.chat.Component
class PastureSlotIconButton(
var xPos: Int, var yPos: Int,
onPress: OnPress
onPress: OnPress,
var parent: PasturePokemonScrollList.PastureSlot
) : Button(xPos, yPos, (SIZE * SCALE).toInt(), (SIZE * SCALE).toInt(), Component.literal("Pasture Move"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
@@ -35,7 +36,7 @@ class PastureSlotIconButton(
y = yPos / SCALE,
width = SIZE,
height = SIZE,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) SIZE else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || parent.controllerFocused) SIZE else 0,
textureHeight = SIZE * 2,
texture = baseResource,
scale = SCALE
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pc.PCGUI
import com.cobblemon.mod.common.client.gui.pc.StorageWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -34,6 +36,9 @@ class PastureWidget(
private val baseResource = cobblemonResource("textures/gui/pasture/pasture_panel.png")
}
val controllerComponentManager = ControllerComponentManager(storageWidget.pcGui)
var lastSelectedIndex = 0
private val recallButton = RecallButton(
x = x + 6,
y = y + 153
@@ -48,6 +53,23 @@ class PastureWidget(
parent = this
)
init {
controllerComponentManager.componentList["recall_all"] = ControllerComponent().also {
it.associatedGUIElement = recallButton
it.up = {
if (pastureScrollList.children().isEmpty()) controllerComponentManager.componentList["empty"]
else controllerComponentManager.componentList["slot_${pastureScrollList.children().size-1}"]
}
}
controllerComponentManager.componentList["empty"] = ControllerComponent().also {
it.down = { controllerComponentManager.componentList["recall_all"] }
}
controllerComponentManager.let {
it.focused = if (pastureScrollList.children().isEmpty()) it.componentList["empty"]
else it.componentList["slot_0"]
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
@@ -40,7 +40,7 @@ class RecallButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
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.pasture.PasturePokemonScrollList
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
@@ -28,6 +29,10 @@ 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
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
@@ -40,15 +45,19 @@ import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.isInventoryKeyPressed
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toAssetPath
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import kotlin.isInitialized
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvents
import org.joml.Vector4i
class PCGUI(
val pc: ClientPC,
@@ -56,7 +65,7 @@ class PCGUI(
val configuration: PCGUIConfiguration,
val openOnBox: Int = CobblemonClient.lastPcBoxViewed,
val unseenWallpapers: MutableSet<ResourceLocation> = mutableSetOf()
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 349
@@ -127,6 +136,14 @@ class PCGUI(
var selectPointerOffsetIncrement = false
var displayOptions = false
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedBeforeOptions = ""
var controllerShiftToggle = false
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -197,30 +214,39 @@ class PCGUI(
this.addRenderableWidget(boxNameWidget)
// Initialise box options if not pasture
var optionsButton: IconButton? = null
var wallpaperButton: IconButton? = null
if (storageWidget.pastureWidget == null) {
optionButtons.clear()
addRenderableWidget(
IconButton(
pX = x + 218,
pY = y + 186,
buttonWidth = 16,
buttonHeight = 16,
resource = buttonOptionsResource,
label = "options"
) {
displayOptions = !displayOptions
if (!displayOptions && wallpaperWidget.visible) {
configuration.showParty = true
wallpaperWidget.visible = false
}
(it as IconButton).highlighted = displayOptions
storageWidget.setupStorageSlots()
for (button in optionButtons) {
button.visible = displayOptions
button.highlighted = false
}
}.also { it.highlighted = displayOptions }
)
optionsButton = IconButton(
pX = x + 218,
pY = y + 186,
buttonWidth = 16,
buttonHeight = 16,
resource = buttonOptionsResource,
label = "options"
) {
if (!displayOptions) {
lastSelectedBeforeOptions = controllerComponentManager.currentSelectedComponent
controllerComponentManager.focused = controllerComponentManager.componentList["setting_button_0"]
controllerShiftToggle = false
} else {
controllerComponentManager.focused = controllerComponentManager.componentList[lastSelectedBeforeOptions]
}
displayOptions = !displayOptions
if (!displayOptions && wallpaperWidget.visible) {
configuration.showParty = true
wallpaperWidget.visible = false
}
(it as IconButton).highlighted = displayOptions
storageWidget.setupStorageSlots()
for (button in optionButtons) {
button.visible = displayOptions
button.highlighted = false
}
}.also { it.highlighted = displayOptions }
addRenderableWidget(optionsButton)
// Add Wallpaper Widget
this.wallpaperWidget = WallpapersScrollingWidget(
@@ -236,7 +262,7 @@ class PCGUI(
this.addRenderableWidget(wallpaperWidget)
// Add Wallpaper Settings Button
this.addRenderableWidget(IconButton(
wallpaperButton = IconButton(
pX = x + 242,
pY = y + 31,
buttonWidth = 20,
@@ -249,14 +275,16 @@ class PCGUI(
wallpaperWidget.visible = !isVisible
(it as IconButton).highlighted = !isVisible
if (!unseenWallpapers.isEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
controllerComponentManager.focused = if (wallpaperWidget.visible) controllerComponentManager.componentList["wallpaper_widget"] else controllerComponentManager.componentList["wallpaper_button"]
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
}
this.addRenderableWidget(wallpaperButton)
PokemonSortMode.entries.forEachIndexed { index, sortType ->
val typeName = sortType.name.lowercase()
this.addRenderableWidget(IconButton(
val button = IconButton(
pX = x + 92 + (12 * index),
pY = y + 31,
buttonWidth = 20,
@@ -267,33 +295,58 @@ class PCGUI(
label = "sort_${typeName}"
) {
storageWidget.resetSelected()
SortPCBoxPacket(pc.uuid, storageWidget.box, sortType, hasShiftDown()).sendToServer()
SortPCBoxPacket(pc.uuid, storageWidget.box, sortType, hasShiftDown() || (controllerComponentManager.active && controllerShiftToggle)).sendToServer()
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
}
this.addRenderableWidget(button)
controllerComponentManager.componentList["setting_button_${index}"] = ControllerComponent().also {
it.associatedGUIElement = button
if (index - 1 >= 0) it.left = { controllerComponentManager.componentList["setting_button_${index-1}"] }
if (index + 1 <= PokemonSortMode.entries.size - 1) it.right = { controllerComponentManager.componentList["setting_button_${index+1}"] }
else it.right = { controllerComponentManager.componentList["wallpaper_button"] }
it.onSpecial1 = {
controllerShiftToggle = !controllerShiftToggle
playSound(CobblemonSounds.PC_CLICK)
}
}
}
controllerComponentManager.componentList["wallpaper_button"] = ControllerComponent().also {
it.associatedGUIElement = wallpaperButton
it.left = { controllerComponentManager.componentList["setting_button_${PokemonSortMode.entries.size - 1}"] }
}
controllerComponentManager.componentList["wallpaper_widget"] = SubManagerComponent().also {
it.subManager = { wallpaperWidget.controllerComponentManager }
it.onSpecial1 = {
controllerShiftToggle = !controllerShiftToggle
playSound(CobblemonSounds.PC_CLICK)
}
}
}
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
}
)
val switchCosmetic = 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
}
)
// Held/Cosmetic Item Button
addRenderableWidget(switchCosmetic)
addRenderableWidget(
IconButton(
@@ -311,6 +364,149 @@ class PCGUI(
this.setPreviewPokemon(null)
super.init()
controllerComponentManager.componentList["box_name"] = ControllerComponent().also {
it.associatedGUIElement = boxNameWidget
it.down = { controllerComponentManager.componentList["storage"] }
}
controllerComponentManager.componentList["storage"] = SubManagerComponent().also {
it.subManager = { storageWidget.controllerComponentManager }
it.up = {
var returnVal: ControllerComponent? = null
val manager = storageWidget.controllerComponentManager
if (manager.up() && manager.currentSelectedComponent.startsWith("slot") && storageWidget.grabbedSlot == null) {
returnVal = controllerComponentManager.componentList["box_name"]
}
returnVal
}
it.down = {
var returnVal: ControllerComponent? = null
val manager = storageWidget.controllerComponentManager
if (manager.down() && manager.currentSelectedComponent.startsWith("slot") && storageWidget.grabbedSlot == null) {
returnVal = controllerComponentManager.componentList["filter"]
}
returnVal
}
it.onSpecial1 = {
if (storageWidget.controllerComponentManager.currentSelectedComponent == "pasture") {
storageWidget.controllerComponentManager.special1()
} else {
switchCosmetic.onPress()
playSound(CobblemonSounds.PC_CLICK)
}
}
it.onSpecial2 = { if (storageWidget.pastureWidget == null) displayOptions = !displayOptions }
}
controllerComponentManager.componentList["filter"] = ControllerComponent().also {
it.associatedGUIElement = filterWidget
it.up = { controllerComponentManager.componentList["storage"] }
}
controllerComponentManager.let {
it.focused = it.componentList["storage"]
it.onTabNav = { nextTab ->
storageWidget.box += if (nextTab) 1 else -1
playSound(CobblemonSounds.PC_CLICK)
}
it.onSecondaryTabNav = { nextTab ->
var nextValue = currentStatIndex + if (nextTab) 1 else -1
if (nextValue > 2) nextValue = 0
if (nextValue < 0) nextValue = 2
currentStatIndex = nextValue
playSound(CobblemonSounds.PC_CLICK)
}
it.onStart = {
if (it.currentSelectedComponent != "storage" || storageWidget.grabbedSlot == null) {
it.focused = controllerComponentManager.componentList["filter"]
storageWidget.controllerComponentManager.focused =
storageWidget.controllerComponentManager.componentList["slot_4_0"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
playSound(SoundEvents.UI_BUTTON_CLICK.value())
}
}
it.onSpecial2 = {
if (storageWidget.pastureWidget == null && storageWidget.grabbedSlot == null) {
optionsButton!!.onPress()
playSound(CobblemonSounds.PC_CLICK)
}
}
it.onBack = {
if (storageWidget.pastureWidget == null && wallpaperWidget.visible) {
wallpaperButton?.onPress()
playSound(CobblemonSounds.PC_CLICK)
} else if (displayOptions) {
optionsButton!!.onPress()
playSound(CobblemonSounds.PC_CLICK)
} else if (storageWidget.grabbedSlot != null) {
storageWidget.resetSelected()
} else {
closeNormally(true)
}
}
it.onActiveChange = { state ->
if (!state) {
storageWidget.controllerComponentManager.active = false
} else {
if (it.currentSelectedComponent == "storage") storageWidget.controllerComponentManager.active = true
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ true },
Component.translatable("cobblemon.controller.pc.switch_box")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ true },
Component.translatable("cobblemon.controller.pc.switch_stats")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent != "pasture" },
Component.translatable("cobblemon.controller.toggle_held_item_type"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent == "pasture" &&
storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent.startsWith("slot_") },
Component.translatable("cobblemon.controller.pc.toggle_defence"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent != "pasture" },
Component.translatable("cobblemon.controller.pc.box_settings"),
true
))
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data == null) {
when (controllerComponentManager.currentSelectedComponent) {
"wallpaper_widget" -> return Vector4i(wallpaperWidget.rowLeft - 2, wallpaperWidget.getRowTop(wallpaperWidget.children().indexOf(focused)) + 2,
WallpapersScrollingWidget.SLOT_WIDTH + 4, WallpapersScrollingWidget.SLOT_HEIGHT + 4)
"storage" -> {
if (storageWidget.pastureWidget != null && storageWidget.controllerComponentManager.currentSelectedComponent == "pasture") {
val scrollWidget = storageWidget.pastureWidget!!.pastureScrollList
if (storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent.startsWith("slot_")) {
return Vector4i(scrollWidget.rowLeft - 6, scrollWidget.getRowTop(scrollWidget.children().indexOf(focused)),
PasturePokemonScrollList.SLOT_WIDTH + 4, PasturePokemonScrollList.SLOT_HEIGHT + 4)
} else if (storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent == "empty") {
return Vector4i(scrollWidget.x - 2, scrollWidget.y - 11, scrollWidget.width + 4, scrollWidget.height + 13)
}
}
}
}
}
return data
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -463,7 +659,7 @@ class PCGUI(
)
// Poké Ball
val ballResource = cobblemonResource("textures/item/poke_balls/" + pokemon.caughtBall.name.path + ".png")
val ballResource = pokemon.caughtBall.name.toAssetPath("textures/item/poke_balls/", ".png")
blitk(
matrixStack = matrices,
texture = ballResource,
@@ -684,7 +880,7 @@ class PCGUI(
)
if (!optionButtons.isEmpty() && displayOptions) {
for (button in optionButtons) button.showAlt = hasShiftDown()
for (button in optionButtons) button.showAlt = hasShiftDown() || (controllerComponentManager.active && controllerShiftToggle)
}
super.render(context, mouseX, mouseY, delta)
@@ -699,6 +895,8 @@ class PCGUI(
if (itemHovered) context.renderTooltip(Minecraft.getInstance().font, displayedItem, mouseX, mouseY)
}
}
renderSelection(matrices)
}
fun closeNormally(unlink: Boolean = true) {
@@ -27,8 +27,8 @@ class ReleaseButton(
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Release"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 58
private const val HEIGHT = 16
const val WIDTH = 58
const val HEIGHT = 16
private val buttonResource = cobblemonResource("textures/gui/pc/pc_release_button.png")
}
@@ -42,7 +42,7 @@ class ReleaseButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -28,8 +28,8 @@ class ReleaseConfirmButton(
) : Button(x, y, WIDTH, HEIGHT, Component.literal("ReleaseConfirm"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 30
private const val HEIGHT = 13
const val WIDTH = 30
const val HEIGHT = 13
private val buttonResource = cobblemonResource("textures/gui/pc/pc_release_button_confirm.png")
}
@@ -43,7 +43,7 @@ class ReleaseConfirmButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -20,6 +20,9 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.gui.pasture.PastureWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
import com.cobblemon.mod.common.client.settings.ServerSettings
@@ -41,6 +44,7 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.joml.Vector2i
class StorageWidget(
pX: Int, pY: Int,
@@ -91,6 +95,9 @@ class StorageWidget(
this.setupStorageSlots()
}
val controllerComponentManager = ControllerComponentManager(pcGui)
var grabbedSlotControllerPos = Vector2i()
init {
this.setupStorageSlots()
@@ -102,6 +109,7 @@ class StorageWidget(
if (!displayConfirmRelease && canDeleteSelected()) {
displayConfirmRelease = true
playSound(CobblemonSounds.PC_CLICK)
controllerComponentManager.focused = controllerComponentManager.componentList["release_no"]
}
}
)
@@ -126,6 +134,7 @@ class StorageWidget(
playSound(CobblemonSounds.PC_RELEASE)
resetSelected()
displayConfirmRelease = false
controllerComponentManager.focused = controllerComponentManager.componentList["slot_0_0"]
}
}
)
@@ -139,6 +148,7 @@ class StorageWidget(
if (displayConfirmRelease) {
displayConfirmRelease = false
playSound(CobblemonSounds.PC_CLICK)
controllerComponentManager.focused = controllerComponentManager.componentList["release"]
}
}
)
@@ -146,6 +156,47 @@ class StorageWidget(
if (pcGui.configuration is PasturePCGUIConfiguration) {
this.pastureWidget = PastureWidget(this, pcGui.configuration, x + 182, y - 19)
}
controllerComponentManager.componentList["release"] = ControllerComponent().also {
it.associatedGUIElement = releaseButton
it.up = { controllerComponentManager.componentList["party_slot_2_0"] }
it.left = { controllerComponentManager.componentList["slot_4_5"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseButton.x + ReleaseButton.WIDTH / 2 + 4, releaseButton.y + ReleaseButton.HEIGHT / 2 + 4)
}
}
controllerComponentManager.componentList["release_no"] = ControllerComponent().also {
it.associatedGUIElement = releaseNoButton
it.up = { controllerComponentManager.componentList["party_slot_2_1"] }
it.left = { controllerComponentManager.componentList["release_yes"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseNoButton.x + ReleaseConfirmButton.WIDTH / 2 + 4, releaseNoButton.y + ReleaseConfirmButton.HEIGHT / 2 + 4)
}
it.onUnselect = {
if (!controllerComponentManager.currentSelectedComponent.startsWith("release")) displayConfirmRelease = false
}
}
controllerComponentManager.componentList["release_yes"] = ControllerComponent().also {
it.associatedGUIElement = releaseYesButton
it.up = { controllerComponentManager.componentList["party_slot_2_0"] }
it.left = { controllerComponentManager.componentList["slot_4_5"] }
it.right = { controllerComponentManager.componentList["release_no"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseYesButton.x + ReleaseConfirmButton.WIDTH / 2 + 4, releaseYesButton.y + ReleaseConfirmButton.HEIGHT / 2 + 4)
}
it.onUnselect = {
if (!controllerComponentManager.currentSelectedComponent.startsWith("release")) displayConfirmRelease = false
}
}
if (pastureWidget != null) {
controllerComponentManager.componentList["pasture"] = SubManagerComponent().also {
it.subManager = { pastureWidget!!.controllerComponentManager }
it.left = { controllerComponentManager.componentList["slot_0_5"] }
}
}
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
fun canDeleteSelected(): Boolean {
@@ -164,7 +215,7 @@ class StorageWidget(
for (row in 1..5) {
for (col in 1..6) {
BoxStorageSlot(
val slot = BoxStorageSlot(
x = boxStartX + ((col - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
y = boxStartY + ((row - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
parent = this,
@@ -176,6 +227,27 @@ class StorageWidget(
this.boxSlots.add(widget)
}
index++
val adjCol = col - 1
val adjRow = row - 1
controllerComponentManager.componentList["slot_${adjRow}_${adjCol}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (adjCol - 1 >= 0) it.left = { controllerComponentManager.componentList["slot_${adjRow}_${adjCol-1}"] }
if (adjCol + 1 <= 5) it.right = { controllerComponentManager.componentList["slot_${adjRow}_${adjCol+1}"] }
else if (pastureWidget == null) {
it.right = when (adjRow) {
4 -> {{ if (canDeleteSelected()) controllerComponentManager.componentList["release"] else null }}
3 -> {{ controllerComponentManager.componentList["party_slot_2_0"] }}
else -> {{ controllerComponentManager.componentList["party_slot_${adjRow}_0"] }}
}
} else {
it.right = { controllerComponentManager.componentList["pasture"] }
}
if (adjRow - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${adjRow-1}_${adjCol}"] }
if (adjRow + 1 <= 4) it.down = { controllerComponentManager.componentList["slot_${adjRow+1}_${adjCol}"] }
it.onSelect = { grabbedSlotControllerPos = Vector2i(slot.x + slot.width / 2 + 4, slot.y + slot.height / 2 + 4) }
it.onPress = { if (slot.clickable()) slot.onPress() }
}
}
}
@@ -194,7 +266,7 @@ class StorageWidget(
partyY += ((StorageSlot.SIZE + PARTY_SLOT_PADDING) * offsetIndex) + offsetY
}
PartyStorageSlot(
val slot = PartyStorageSlot(
x = partyX,
y = partyY,
parent = this,
@@ -205,6 +277,22 @@ class StorageWidget(
this.addWidget(widget)
this.partySlots.add(widget)
}
val row = partyIndex / 2
val col = partyIndex % 2
controllerComponentManager.componentList["party_slot_${row}_${col}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (col == 1) it.left = { controllerComponentManager.componentList["party_slot_${row}_0"] }
else {
it.right = { controllerComponentManager.componentList["party_slot_${row}_1"] }
it.left = { controllerComponentManager.componentList["slot_${row}_5"] }
}
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["party_slot_${row-1}_${col}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["party_slot_${row+1}_${col}"] }
else it.down = { if (canDeleteSelected()) controllerComponentManager.componentList["release"] else null }
it.onSelect = { grabbedSlotControllerPos = Vector2i(slot.x + slot.width / 2 + 4, slot.y + slot.height / 2 + 4) }
it.onPress = { if (slot.clickable()) slot.onPress() }
}
}
}
@@ -307,7 +395,7 @@ class StorageWidget(
this.boxSlots.forEach { slot ->
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
if (grabbedSlot == null && (slot.isHovered(mouseX, mouseY) || slot.isFocused)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
@@ -323,7 +411,7 @@ class StorageWidget(
this.partySlots.forEach { slot ->
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
if (grabbedSlot == null && (slot.isHovered(mouseX, mouseY) || slot.isFocused)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
@@ -335,7 +423,11 @@ class StorageWidget(
pastureWidget?.render(context, mouseX, mouseY, delta)
matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0)
grabbedSlot?.render(context, mouseX, mouseY, delta)
if (controllerComponentManager.active) {
grabbedSlot?.render(context, grabbedSlotControllerPos.x, grabbedSlotControllerPos.y, delta)
} else {
grabbedSlot?.render(context, mouseX, mouseY, delta)
}
matrices.popPose()
}
@@ -372,6 +464,9 @@ class StorageWidget(
}
// Reset release confirmation
if (controllerComponentManager.currentSelectedComponent.startsWith("release")) {
controllerComponentManager.focused = controllerComponentManager.componentList["slot_0_0"]
}
displayConfirmRelease = false
// Clicking on itself, so unselect position
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.pc.WallpapersScrollingWidget.WallpaperEntry
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
import com.cobblemon.mod.common.net.messages.server.storage.pc.RequestChangePCBoxWallpaperPacket
@@ -49,10 +51,14 @@ class WallpapersScrollingWidget(
val backgroundResource = cobblemonResource("textures/gui/pc/wallpaper_scroll_background.png")
}
val controllerComponentManager = ControllerComponentManager(pcGui)
init {
this.visible = false
this.y = pY
createEntries()
}
override fun getScrollbarPosition(): Int = x + width - 3
@@ -90,6 +96,24 @@ class WallpapersScrollingWidget(
}
}
override fun addEntry(entry: WallpaperEntry): Int {
val i = super.addEntry(entry)
controllerComponentManager.componentList["wallpaper_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = { entry.selectWallpaper() }
it.onFocus = { ensureVisible(entry) }
}
if (children().size > 1) {
controllerComponentManager.componentList["wallpaper_${i}"]?.let {
it.pairUp(controllerComponentManager.componentList["wallpaper_${i-1}"])
it.pairDown(controllerComponentManager.componentList["wallpaper_${0}"])
}
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["wallpaper_${i}"]
}
return i
}
override fun getRowLeft(): Int {
return this.left + SLOT_PADDING
}
@@ -116,7 +140,7 @@ class WallpapersScrollingWidget(
partialTick: Float
) {
val matrices = guiGraphics.pose()
val resource = if (Screen.hasShiftDown() && altWallpaper !== null) altWallpaper else wallpaper
val resource = if ((Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) && altWallpaper !== null) altWallpaper else wallpaper
blitk(
matrixStack = matrices,
texture = resource,
@@ -133,24 +157,28 @@ class WallpapersScrollingWidget(
y = top + SLOT_PADDING,
width = width,
height = SLOT_HEIGHT,
vOffset = if (hovering) SLOT_HEIGHT else 0,
vOffset = if (hovering || isFocused) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2
)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (this@WallpapersScrollingWidget.visible && isMouseOver(mouseX, mouseY)) {
val appliedWallpaper = if (Screen.hasShiftDown()) altWallpaper ?: wallpaper else wallpaper
RequestChangePCBoxWallpaperPacket(pcGui.pc.uuid, storageWidget.box, wallpaper, if (Screen.hasShiftDown()) altWallpaper else null).sendToServer()
pcGui.pc.boxes[storageWidget.box].wallpaper = appliedWallpaper
pcGui.unseenWallpapers.remove(wallpaper)
isNew = false
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
selectWallpaper()
return true
}
return false
}
fun selectWallpaper() {
val appliedWallpaper = if (Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) altWallpaper ?: wallpaper else wallpaper
RequestChangePCBoxWallpaperPacket(pcGui.pc.uuid, storageWidget.box, wallpaper, if (Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) altWallpaper else null).sendToServer()
pcGui.pc.boxes[storageWidget.box].wallpaper = appliedWallpaper
pcGui.unseenWallpapers.remove(wallpaper)
isNew = false
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
}
override fun getNarration(): Component? {
return wallpaper.toString().text()
}
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
@@ -25,7 +26,6 @@ import com.cobblemon.mod.common.api.pokedex.filter.SearchFilter
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
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.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.CobblemonClient
@@ -42,6 +42,9 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_STATS
import com.cobblemon.mod.common.client.gui.pokedex.widgets.*
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
@@ -61,6 +64,8 @@ import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import org.joml.Vector4i
/**
* Pokedex GUI
@@ -72,7 +77,7 @@ class PokedexGUI private constructor(
val type: PokedexType,
val initSpecies: ResourceLocation?,
val blockPos: BlockPos?
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable {
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable, ControllerScreen {
companion object {
private val screenBackground = cobblemonResource("textures/gui/pokedex/pokedex_screen.png")
@@ -137,6 +142,14 @@ class PokedexGUI private constructor(
lateinit var tabInfoElement: GuiEventListener
var tabInfoIndex = TAB_DESCRIPTION
val controllerComponentManager = ControllerComponentManager(this)
var lastTileComponent: ControllerComponent? = null
var lastSelectedColumn = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -209,6 +222,232 @@ class PokedexGUI private constructor(
addRenderableWidget(searchByTypeButton)
updateFilters(true)
fun hasKnowledge(): Boolean {
val currentEntry = pokemonInfoWidget.currentEntry
return currentEntry != null && CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.NONE
}
controllerComponentManager.componentList["search"] = ControllerComponent().also {
it.associatedGUIElement = searchWidget
it.down = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
controllerComponentManager.componentList["tile_0_0"]
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.onSpecial1 = {
searchByTypeButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
controllerComponentManager.componentList["empty_entries"] = ControllerComponent().also {
it.up = { controllerComponentManager.componentList["search"] }
it.right = { controllerComponentManager.componentList["pokemon_render"] }
}
controllerComponentManager.componentList["pokemon_render"] = ControllerComponent().also {
it.left = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
lastTileComponent
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.down = { controllerComponentManager.componentList["info_widget"] }
it.onPress = {
if (hasKnowledge()) {
pokemonInfoWidget.playCry()
}
}
it.onSpecial1 = {
if (hasKnowledge() && pokemonInfoWidget.genderButton.visible) {
pokemonInfoWidget.genderButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
it.onSpecial2 = {
if (hasKnowledge() && pokemonInfoWidget.shinyButton.visible) {
pokemonInfoWidget.shinyButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
controllerComponentManager.componentList["info_widget"] = ControllerComponent().also {
it.left = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
lastTileComponent
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.up = { controllerComponentManager.componentList["pokemon_render"] }
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"] ?: it.componentList["empty_entries"]
it.onTabNav = { nextTab ->
if (it.currentSelectedComponent == "empty_entries" || it.currentSelectedComponent.startsWith("tile_")) {
updatePokedexRegion(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
} else if (it.currentSelectedComponent == "pokemon_render") {
val currentEntry = pokemonInfoWidget.currentEntry
if (currentEntry != null) {
val hasKnowledge = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.NONE
val showableForms = CobblemonClient.clientPokedexData.getEncounteredForms(currentEntry)
if (hasKnowledge && showableForms.size > 1 && showableForms.size > pokemonInfoWidget.selectedFormIndex) {
pokemonInfoWidget.switchForm(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
} else if (it.currentSelectedComponent == "info_widget") {
val trimmedTabs = tabButtons.filterIndexed { index, _ -> tabInfoIndex == index || canSelectTab(index) }.map { tabButtons.indexOf(it) }.toMutableList()
var currentTab = trimmedTabs.indexOf(tabInfoIndex) + if (nextTab) 1 else -1
if (currentTab > trimmedTabs.size - 1) currentTab = 0
else if (currentTab < 0) currentTab = trimmedTabs.size - 1
displaytabInfoElement(trimmedTabs[currentTab])
if (trimmedTabs.size > 1) playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
it.onSecondaryTabNav = { nextTab ->
if (it.currentSelectedComponent == "pokemon_render") {
pokemonInfoWidget.switchPose(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
} else if (it.currentSelectedComponent == "info_widget") {
(tabInfoElement as? AbilitiesWidget)?.let {
it.switchAbility(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
(tabInfoElement as? StatsWidget)?.let { widget ->
if (nextTab) {
if (children().contains(widget.rightButton)) {
widget.rightButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
} else {
if (children().contains(widget.leftButton)) {
widget.leftButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
}
}
it.onRHorizontalHeld = { right ->
if (it.currentSelectedComponent == "pokemon_render") {
pokemonInfoWidget.rotationY = ((pokemonInfoWidget.rotationY + if (right) -5F else 5F ) % 360 + 360) % 360
}
}
it.onRVerticalHeld = { up ->
if (it.currentSelectedComponent == "info_widget") {
(tabInfoElement as? InfoTextScrollWidget)?.scroll(if (up) -3 else 3)
}
}
it.onRHorizontal = { right ->
if (it.currentSelectedComponent == "info_widget") {
(tabInfoElement as? StatsWidget)?.let { widget ->
if (widget.selectedStatTypeIndex == StatsWidget.STAT_TYPE_RIDE) {
if (right) {
if (children().contains(widget.rightSubButton)) {
widget.rightSubButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
} else {
if (children().contains(widget.leftSubButton)) {
widget.leftSubButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
}
}
}
it.onStart = {
it.focused = it.componentList["search"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
playSound(SoundEvents.UI_BUTTON_CLICK.value())
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ it.currentSelectedComponent == "empty_entries" || it.currentSelectedComponent.startsWith("tile_") },
Component.translatable("cobblemon.controller.dex.cycle_region")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.visibleForms.size > 1},
Component.translatable("cobblemon.controller.dex.cycle_form"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() },
Component.translatable("cobblemon.controller.dex.cycle_animation")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "info_widget" && tabInfoElement is AbilitiesWidget },
Component.translatable("cobblemon.controller.dex.cycle_ability")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "info_widget" && (tabInfoElement as? StatsWidget)?.let { children().contains(it.rightButton) } ?: false },
Component.translatable("cobblemon.controller.dex.cycle_stats")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.JOYSTICK_HORIZONTAL,
{ it.currentSelectedComponent == "info_widget" && (tabInfoElement as? StatsWidget)?.let { it.selectedStatTypeIndex == StatsWidget.STAT_TYPE_RIDE && children().contains(it.rightSubButton) } ?: false },
Component.translatable("cobblemon.controller.dex.cycle_ride_style")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "search" },
Component.translatable("cobblemon.controller.dex.cycle_search_by"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.genderButton.visible },
Component.translatable("cobblemon.controller.dex.toggle_gender"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.shinyButton.visible },
Component.translatable("cobblemon.controller.dex.toggle_shiny"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.PRESS,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() },
Component.translatable("cobblemon.controller.dex.play_cry"),
true
))
}
}
override fun getSelectionPosAndSize(): Vector4i? {
return when (controllerComponentManager.currentSelectedComponent) {
"search" -> Vector4i(searchWidget.x - 2, searchWidget.y - 2, searchWidget.width + 15, searchWidget.height + 4)
"pokemon_render" -> Vector4i(pokemonInfoWidget.x - 2, pokemonInfoWidget.y + 12, pokemonInfoWidget.width + 6, pokemonInfoWidget.height + 16)
"info_widget" -> Vector4i(pokemonInfoWidget.x - 2, pokemonInfoWidget.y + 94, pokemonInfoWidget.width + 6, pokemonInfoWidget.height + 3)
"empty_entries" -> Vector4i(scrollScreen.x - 2, scrollScreen.y - 2, scrollScreen.width + 4, scrollScreen.height + 4)
else -> {
if (controllerComponentManager.currentSelectedComponent.startsWith("tile_")) {
val scrollWidget = controllerComponentManager.focused?.associatedGUIElement as? EntriesScrollingWidget.PokemonScrollSlotRow
val startX = scrollScreen.x + lastSelectedColumn * (PokedexGUIConstants.SCROLL_SLOT_SIZE + PokedexGUIConstants.SCROLL_SLOT_SPACING)
if (scrollWidget != null) {
Vector4i(startX - 2, scrollScreen.getRowTop(scrollScreen.children().indexOf(scrollWidget)) + 1, PokedexGUIConstants.SCROLL_SLOT_SIZE + 4, PokedexGUIConstants.SCROLL_SLOT_SIZE + 4)
} else null
} else null
}
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -320,6 +559,8 @@ class PokedexGUI private constructor(
super.render(context, mouseX, mouseY, delta)
renderSelection(context.pose())
// Search type tooltip
if (searchByTypeButton.isButtonHovered(mouseX, mouseY)) {
val searchTypeText = lang("ui.pokedex.search.search_by", lang("ui.pokedex.search.type.${selectedSearchByType.name.lowercase()}")).bold()
@@ -435,6 +676,45 @@ class PokedexGUI private constructor(
} else {
setSelectedEntry(entries.first())
}
val totalRows = scrollScreen.children().size
val lastRowCount = scrollScreen.children()[totalRows-1].dexDataList.size
scrollScreen.children().forEachIndexed { row, scrollRow ->
scrollRow.dexDataList.forEachIndexed { column, slot ->
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = scrollRow
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["tile_${row-1}_${column}"] }
else it.up = { controllerComponentManager.componentList["search"] }
if (row + 1 <= scrollScreen.children().size - 1 &&
(row + 1 != totalRows || column <= lastRowCount - 1)) it.down = { controllerComponentManager.componentList["tile_${row+1}_${column}"] }
if (column - 1 >= 0) it.left = { controllerComponentManager.componentList["tile_${row}_${column-1}"] }
if (column + 1 <= scrollRow.dexDataList.size - 1) it.right = { controllerComponentManager.componentList["tile_${row}_${column+1}"] }
else it.right = { controllerComponentManager.componentList["pokemon_render"] }
it.onPress = {
scrollRow.setPokedexEntry.invoke(slot)
playSound(CobblemonSounds.POKEDEX_CLICK)
}
it.onSelect = {
lastTileComponent = it
lastSelectedColumn = column
scrollScreen.ensureVisible(scrollRow)
}
it.onFocus = {
scrollRow.slotControllerSelected = column
}
it.onUnfocus = {
scrollRow.slotControllerSelected = -1
}
}
}
}
}
if (controllerComponentManager.currentSelectedComponent.let { it == "empty_entries" || it.startsWith("tile_") }) {
if (entries.isNotEmpty()) {
controllerComponentManager.focused = controllerComponentManager.componentList["tile_0_0"]
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["empty_entries"]
}
}
}
@@ -47,7 +47,7 @@ class AbilitiesWidget(x: Int, y: Int): InfoTextScrollWidget(pX = x, pY = y) {
init { setAbility() }
private fun switchAbility(nextIndex: Boolean) {
fun switchAbility(nextIndex: Boolean) {
val size = abilitiesList.size
selectedAbilitiesIndex = if (nextIndex) {
(selectedAbilitiesIndex + 1) % size
@@ -48,7 +48,6 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
top = pY - SCROLL_BASE_HEIGHT,
slotHeight = SCROLL_SLOT_SIZE + 2
) {
fun createEntries(filteredPokedex: Collection<PokedexEntry>) {
filteredPokedex.chunked(5).forEachIndexed { index, listChunk ->
val discoveryList = listChunk.map { CobblemonClient.clientPokedexData.getHighestKnowledgeFor(it) }.toMutableList()
@@ -129,6 +128,8 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
var x: Int = 0
var y: Int = 0
var slotControllerSelected = -1
override fun render(
context: GuiGraphics,
index: Int,
@@ -170,7 +171,7 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
height = SCROLL_SLOT_SIZE
)
if (getHoveredSlotIndex(mouseX, mouseY) == index) {
if (getHoveredSlotIndex(mouseX, mouseY) == index || slotControllerSelected == index) {
blitk(
matrixStack = matrices,
texture = slotHighlight,
@@ -59,6 +59,7 @@ import net.minecraft.sounds.SoundEvent
import org.joml.Quaternionf
import org.joml.Vector3f
import java.io.FileNotFoundException
import kotlin.math.absoluteValue
class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm) -> (Unit)) : SoundlessWidget(
pX,
@@ -186,7 +187,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
val variationButtons = mutableListOf<VariationButtonWrapper>()
private val genderButton: ScaledButton = ScaledButton(
val genderButton: ScaledButton = ScaledButton(
pX + 114F,
pY + 27F,
20,
@@ -198,7 +199,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
}
).apply { addWidget(this) }
private val shinyButton: ScaledButton = ScaledButton(
val shinyButton: ScaledButton = ScaledButton(
(pX + 126F),
(pY + 27F),
20,
@@ -364,9 +365,9 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrices.translate(
pX.toDouble() + (POKEMON_PORTRAIT_WIDTH.toDouble() + 2)/2,
pY.toDouble() + portraitStartY - 12,
1000.0 // Prevent model from clipping into background
50.0
)
matrices.scale(scaleAmount, scaleAmount, scaleAmount)
matrices.scale(scaleAmount, scaleAmount, 0.1F) // Scaling Z scale way down to improve order of rendering, too low will cause model rendering to z fight
val rotationVector = Vector3f(13F, rotationY, 0F)
drawProfilePokemon(
@@ -392,10 +393,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
)
}
// Ensure elements are not hidden behind Pokémon render
matrices.pushPose()
matrices.translate(0.0, 0.0, 2000.0)
if (isSelectedPokemonOwned()) {
val primaryType = type[0]
val secondaryType = type[1]
@@ -507,8 +504,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
)
}
}
matrices.popPose()
}
fun setDexEntry(pokedexEntry : PokedexEntry) {
@@ -586,12 +581,12 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
type = arrayOf(form.primaryType, form.secondaryType)
}
private fun playCry() {
fun playCry() {
state.activeAnimations.clear()
state.addFirstAnimation(setOf("cry"))
}
private fun switchForm(nextIndex: Boolean) {
fun switchForm(nextIndex: Boolean) {
if (visibleForms.isEmpty() || currentEntry == null) {
return
}
@@ -612,7 +607,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
updateAspects()
}
private fun switchPose(nextIndex: Boolean) {
fun switchPose(nextIndex: Boolean) {
if (nextIndex) {
if (selectedPoseIndex < poseList.lastIndex) selectedPoseIndex++
else selectedPoseIndex = 0
@@ -237,9 +237,11 @@ class StatsWidget(val pX: Int, val pY: Int) : SoundlessWidget(
blitk(matrixStack = matrices, texture = divider, x = pX + 51, y = pY + 10, height = 27.5, width = 1)
blitk(matrixStack = matrices, texture = divider, x = pX + 9, y = pY + 2, height = 6, width = 121)
val rideStyle = selectedBehaviour.value.key.path.toString().split("/").last().lowercase()
drawScaledText(
context = context,
text = lang("ui.ride_style.${selectedBehaviour.key.name.lowercase()}").bold(),
text = lang("ui.ride_style.${selectedBehaviour.key.toString().lowercase()}.${rideStyle}")
.append(" | ").append(lang("ui.ride_style.${selectedBehaviour.key.toString().lowercase()}")).bold(),
x = pX + 69.5,
y = pY + 3,
colour = 0x3A96B6,
@@ -9,6 +9,9 @@
package com.cobblemon.mod.common.client.gui.snapshots
import com.cobblemon.mod.common.CobblemonBuildDetails
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import net.minecraft.client.Minecraft
import net.minecraft.client.OptionInstance
@@ -20,10 +23,12 @@ import net.minecraft.client.gui.components.Tooltip
import net.minecraft.client.gui.layouts.FrameLayout
import net.minecraft.client.gui.layouts.GridLayout
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.CommonComponents
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvents
class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) : Screen(TITLE) {
class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) : Screen(TITLE), ControllerScreen {
companion object {
@@ -36,6 +41,12 @@ class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) :
private val layout = GridLayout().columnSpacing(10).rowSpacing(20)
private val dontShowAgain = OptionInstance.createBoolean("cobblemon\$snapshots\$dont_show_again", false)
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
@@ -45,17 +56,42 @@ class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) :
helper.addChild(StringWidget(this.title, this.font), 2, settings)
helper.addChild(MultiLineTextWidget(DESCRIPTION, this.font).setCentered(true).setMaxWidth(310), 2, settings)
helper.addChild(Checkbox.builder(Component.translatable("cobblemon.snapshots.warning.dont_show_again"), Minecraft.getInstance().font)
val checkbox = Checkbox.builder(Component.translatable("cobblemon.snapshots.warning.dont_show_again"), Minecraft.getInstance().font)
.selected(dontShowAgain)
.tooltip(Tooltip.create(DONT_SHOW_AGAIN_TIP))
.build(), 2, settings)
.build()
helper.addChild(checkbox, 2, settings)
helper.addChild(Button.builder(CommonComponents.GUI_YES) { this.consumer.invoke(Acknowledgement.YES, dontShowAgain.get()) }.build())
helper.addChild(Button.builder(CommonComponents.GUI_NO) { this.consumer.invoke(Acknowledgement.NO, false) }.build())
val yesButton = Button.builder(CommonComponents.GUI_YES) { this.consumer.invoke(Acknowledgement.YES, dontShowAgain.get()) }.build()
helper.addChild(yesButton)
val noButton = Button.builder(CommonComponents.GUI_NO) { this.consumer.invoke(Acknowledgement.NO, false) }.build()
helper.addChild(noButton)
this.layout.visitWidgets { this.addRenderableWidget(it) }
this.layout.arrangeElements()
this.repositionElements()
controllerComponentManager.componentList["checkbox"] = ControllerComponent().also {
it.associatedGUIElement = checkbox
it.down = { controllerComponentManager.componentList["yes_button"] }
it.onPress = {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F))
checkbox.onPress()
}
}
controllerComponentManager.componentList["yes_button"] = ControllerComponent().also {
it.associatedGUIElement = yesButton
it.up = { controllerComponentManager.componentList["checkbox"] }
it.right = { controllerComponentManager.componentList["no_button"] }
}
controllerComponentManager.componentList["no_button"] = ControllerComponent().also {
it.associatedGUIElement = noButton
it.up = { controllerComponentManager.componentList["checkbox"] }
it.left = { controllerComponentManager.componentList["yes_button"] }
}
controllerComponentManager.let {
it.focused = it.componentList["yes_button"]
}
}
override fun repositionElements() {
@@ -27,6 +27,9 @@ import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.DualTypeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.SingleTypeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.TypeWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
@@ -41,6 +44,8 @@ import net.minecraft.client.gui.components.toasts.Toast
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import org.joml.Vector4i
/**
* Starterselection Screen Thingy
@@ -48,7 +53,7 @@ import net.minecraft.network.chat.Component
* @author Qu
* @since 2022-06-18
*/
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable {
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable, ControllerScreen {
companion object {
// Size of UI at scale 1
@@ -73,6 +78,14 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
private lateinit var starterRoundaboutCenter: StarterRoundabout
private lateinit var starterRoundaboutLeft: StarterRoundabout
private lateinit var starterRoundaboutRight: StarterRoundabout
private lateinit var categoryWidget: CategoryList
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedCategoryIndex = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) { }
@@ -95,17 +108,17 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
return
}
addRenderableWidget(
CategoryList(
paneWidth = 71, paneHeight = BASE_HEIGHT - 11,
topOffset = 6,
entryHeight = 20, entryWidth = 57,
categories = categories,
listX = x - 2, listY = y + 8,
starterSelectionScreen = this
)
categoryWidget = CategoryList(
paneWidth = 71, paneHeight = BASE_HEIGHT - 11,
topOffset = 6,
entryHeight = 20, entryWidth = 57,
categories = categories,
listX = x - 2, listY = y + 8,
starterSelectionScreen = this
)
addRenderableWidget(categoryWidget)
val rightButton = ArrowButton(
pX = x + 183, pY = y + 151,
pWidth = 9, pHeight = 14,
@@ -194,6 +207,50 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
)
controllerComponentManager.componentList["select"] = ControllerComponent().also {
it.associatedGUIElement = selectionButton
it.left = { controllerComponentManager.componentList["category_${lastSelectedCategoryIndex}"] }
}
categoryWidget.children().forEachIndexed { index, category ->
controllerComponentManager.componentList["category_${index}"] = ControllerComponent().also {
it.associatedGUIElement = category
it.right = { controllerComponentManager.componentList["select"] }
if (index - 1 >= 0) it.up = { controllerComponentManager.componentList["category_${index-1}"] }
if (index + 1 <= categoryWidget.children().size - 1) it.down = { controllerComponentManager.componentList["category_${index+1}"] }
it.onPress = {
changeCategory(category.category)
playSound(CobblemonSounds.GUI_CLICK)
}
it.onSelect = {
lastSelectedCategoryIndex = index
categoryWidget.ensureVisible(category)
}
it.onFocus = { category.controllerSelected = true }
it.onUnfocus = { category.controllerSelected = false }
}
}
controllerComponentManager.let {
it.focused = controllerComponentManager.componentList["select"]
it.onTabNav = { nextTab ->
if (nextTab) right() else left()
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ true },
Component.translatable("cobblemon.controller.starter.cycle_starter")
))
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (controllerComponentManager.currentSelectedComponent.startsWith("category_")) {
val category = controllerComponentManager.focused?.associatedGUIElement as? CategoryList.Category
return Vector4i(categoryWidget.rowLeft + 1, categoryWidget.getRowTop(categoryWidget.children().indexOf(category)) - 2,
CategoryList.CATEGORY_BUTTON_WIDTH.toInt() + 4, CategoryList.CATEGORY_BUTTON_HEIGHT.toInt() + 4)
}
return data
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -280,6 +337,8 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
typeWidget.render(context, mouseX, mouseY, delta)
// Render the rest
super.render(context, mouseX, mouseY, delta)
renderSelection(matrices)
}
fun changeCategory(category: RenderableStarterCategory) {
@@ -289,7 +348,7 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
}
private fun right() {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
playSound(CobblemonSounds.GUI_CLICK)
currentSelection = rightOfCurrentSelection()
updateSelection()
}
@@ -297,7 +356,7 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
private fun rightOfCurrentSelection() : Int = if (currentSelection + 1 <= currentCategory.pokemon.size - 1) currentSelection + 1 else 0
private fun left() {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
playSound(CobblemonSounds.GUI_CLICK)
currentSelection = leftOfCurrentSelection()
updateSelection()
}
@@ -340,4 +399,8 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
}
override fun isPauseScreen() = true
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
@@ -41,8 +41,8 @@ class CategoryList(
), CobblemonRenderable {
companion object {
private const val CATEGORY_BUTTON_WIDTH = 51.5f
private const val CATEGORY_BUTTON_HEIGHT = 16f
const val CATEGORY_BUTTON_WIDTH = 51.5f
const val CATEGORY_BUTTON_HEIGHT = 16f
private const val ENTRY_X_OFFSET = 10f
private val categoryResource = cobblemonResource("textures/gui/starterselection/starterselection_slot.png")
}
@@ -76,8 +76,25 @@ class CategoryList(
return this.listX + this.width - 5
}
public override fun ensureVisible(entry: Category) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y - 4
if (topDifference < 0) {
scroll(topDifference)
}
inner class Category(private val category: RenderableStarterCategory) : Entry<Category>() {
val bottomDifference = bottom - i - itemHeight
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
public override fun getRowTop(index: Int): Int {
return super.getRowTop(index)
}
inner class Category(val category: RenderableStarterCategory) : Entry<Category>() {
var controllerSelected = false
override fun render(
context: GuiGraphics,
@@ -93,7 +110,7 @@ class CategoryList(
) {
val matrices = context.pose()
val isHovered = mouseX >= x && mouseY >= y && mouseX < x + entryWidth && mouseY < y + (entryHeight - 1)
if (isHovered) {
if (isHovered || controllerSelected) {
blitk(
matrixStack = matrices,
x = x + 2.5f, y = y,
@@ -41,7 +41,7 @@ class SelectionButton(
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
if (isHovered)
if (isHovered || isFocused)
blitk(
matrixStack = matrices,
texture = buttonTexture,
@@ -28,13 +28,19 @@ import com.cobblemon.mod.common.client.gui.summary.widgets.EvolutionSelectScreen
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.NicknameEntryWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.SummaryTab
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.info.InfoWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksScrollingWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksWidget
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.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
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.SetItemHiddenPacket
@@ -45,6 +51,7 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.isInventoryKeyPressed
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toAssetPath
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
@@ -56,6 +63,8 @@ import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import org.joml.Vector2i
import org.joml.Vector4i
/**
* The screen responsible for displaying various information regarding a Pokémon team.
@@ -66,7 +75,7 @@ import net.minecraft.sounds.SoundEvent
* @param selection The index the [party] will have as the base [selectedPokemon].
*/
class Summary private constructor(party: Collection<Pokemon?>, private val editable: Boolean, private val selection: Int): Screen(
Component.translatable("cobblemon.ui.summary.title")), Schedulable, CobblemonRenderable {
Component.translatable("cobblemon.ui.summary.title")), Schedulable, CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 331
@@ -75,10 +84,10 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private const val SCALE = 0.5F
// Main Screen Index
private const val INFO = 0
private const val MOVES = 1
private const val STATS = 2
private const val MARKS = 3
const val INFO = 0
const val MOVES = 1
const val STATS = 2
const val MARKS = 3
// Side Screen Index
const val PARTY = 0
@@ -122,19 +131,32 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
override val schedulingTracker = SchedulingTracker()
private val party = ArrayList(party)
val party = ArrayList(party)
internal var selectedPokemon: Pokemon = this.party[selection] ?: this.party.first { it != null }!!
private lateinit var mainScreen: AbstractWidget
lateinit var mainScreen: AbstractWidget
lateinit var sideScreen: GuiEventListener
private lateinit var modelWidget: ModelWidget
private lateinit var nicknameEntryWidget: NicknameEntryWidget
lateinit var nicknameEntryWidget: NicknameEntryWidget
private lateinit var markingsWidget: MarkingsWidget
private val summaryTabs = mutableListOf<SummaryTab>()
val summaryTabs = mutableListOf<SummaryTab>()
private var showCosmeticItem = false
private lateinit var heldItemVisibilityButton: SummaryButton
private var mainScreenIndex = INFO
lateinit var heldItemVisibilityButton: SummaryButton
lateinit var heldItemSwitchTypeButton: SummaryButton
lateinit var evolveButton: SummaryButton
var mainScreenIndex = INFO
var sideScreenIndex = PARTY
val controllerComponentManager = ControllerComponentManager(this)
var mainScreenComponentManager: ControllerComponentManager? = null
var sideScreenComponentManager: ControllerComponentManager? = null
var selectedMoveIndex = 0
var moveSwitchingMode = false
var lastSelectedPartySlot = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) { }
/**
@@ -164,7 +186,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
addRenderableWidget(markingsWidget)
// Evolve Button
addRenderableWidget(
evolveButton = addRenderableWidget(
SummaryButton(
buttonX = x + 12F,
buttonY = y + 145F,
@@ -210,22 +232,22 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
heldItemVisibilityButton.buttonActive = !selectedPokemon.heldItemVisible
addRenderableWidget(heldItemVisibilityButton)
// Held/Cosmetic Item Button
addRenderableWidget(
SummaryButton(
buttonX = x + 67F,
buttonY = y + 113F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
heldItemSwitchTypeButton = SummaryButton(
buttonX = x + 67F,
buttonY = y + 113F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
addRenderableWidget(heldItemSwitchTypeButton)
// Init Tabs
summaryTabs.clear()
@@ -259,14 +281,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
// Add Exit Button
addRenderableWidget(
ExitButton(pX = x + 302, pY = y + 145) {
playSound(CobblemonSounds.GUI_CLICK)
if (sideScreenIndex != PARTY) {
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
onBack()
}
)
@@ -299,9 +314,214 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
addRenderableOnly(this.modelWidget)
updatePokemonInfo()
//Controller Support
controllerComponentManager.componentList["nickname"] = ControllerComponent().also {
it.associatedGUIElement = nicknameEntryWidget
it.right = { controllerComponentManager.componentList["center_screen"] }
}
controllerComponentManager.componentList["held_item"] = ControllerComponent().also {
it.associatedGUIElement = heldItemVisibilityButton
it.right = { controllerComponentManager.componentList["center_screen"] }
it.pairUp(controllerComponentManager.componentList["nickname"]!!)
it.down = { if (evolveButton.renderRequirement.invoke(evolveButton)) controllerComponentManager.componentList["evolve"] else null }
it.onSpecial1 = {
playSound(CobblemonSounds.GUI_CLICK)
heldItemSwitchTypeButton.onPress()
}
it.onPress = {
if (heldItemVisibilityButton.clickRequirement.invoke(heldItemVisibilityButton)) heldItemVisibilityButton.onPress()
}
}
controllerComponentManager.componentList["evolve"] = ControllerComponent().also {
it.associatedGUIElement = evolveButton
it.right = { controllerComponentManager.componentList["center_screen"] }
it.up = { controllerComponentManager.componentList["held_item"] }
}
controllerComponentManager.componentList["center_screen"] = SubManagerComponent().also {
it.subManager = { mainScreenComponentManager }
it.left = {
var returnVal: ControllerComponent? = null
if (mainScreenComponentManager?.left() ?: true && !moveSwitchingMode) {
returnVal = controllerComponentManager.componentList["nickname"]
}
returnVal
}
it.right = {
var returnVal: ControllerComponent? = null
if (mainScreenComponentManager?.right() ?: true && !moveSwitchingMode) {
returnVal = controllerComponentManager.componentList["right_screen"]
}
returnVal
}
it.up = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
var index = statWidget.statTabIndex + 1
if (index > statWidget.statOptions.size - 1) {
index = 0
}
statWidget.statTabIndex = index
playSound(CobblemonSounds.GUI_CLICK)
} else {
mainScreenComponentManager?.up()
}
null
}
it.down = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
var index = statWidget.statTabIndex - 1
if (index < 0) {
index = statWidget.statOptions.size - 1
}
statWidget.statTabIndex = index
playSound(CobblemonSounds.GUI_CLICK)
} else {
mainScreenComponentManager?.down()
}
null
}
it.onPress = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
if (statWidget.statOptions.get(statWidget.statTabIndex) == StatWidget.RIDE && (statWidget.pokemon.riding.behaviours?.size ?: 0) > 1) {
statWidget.rideBehaviourIndex = (statWidget.rideBehaviourIndex + 1) % (statWidget.pokemon.riding.behaviours?.size ?: 1)
playSound(CobblemonSounds.GUI_CLICK)
}
} else {
mainScreenComponentManager?.press()
}
}
}
controllerComponentManager.componentList["right_screen"] = SubManagerComponent().also {
it.subManager = { sideScreenComponentManager }
it.left = {
var returnVal: ControllerComponent? = null
if (sideScreenComponentManager?.left() ?: true && sideScreenIndex == PARTY && (sideScreen as? PartyWidget)?.swapSource == null) {
returnVal = controllerComponentManager.componentList["center_screen"]
}
returnVal
}
}
controllerComponentManager.let {
it.focused = it.componentList["center_screen"]
it.onTabNav = { nextTab ->
var index = mainScreenIndex + if (nextTab) 1 else -1
if (index < 0) index = summaryTabs.size - 1
if (index > summaryTabs.size - 1) index = 0
playSound(CobblemonSounds.GUI_CLICK)
displayMainScreen(index)
}
it.onSecondaryTabNav = { nextTab ->
val pokemonIndex = party.indexOf(selectedPokemon)
val indexList = party.filter { it != null }.map { party.indexOf(it) }.toMutableList()
var index = indexList.indexOf(pokemonIndex) + if (nextTab) 1 else -1
if (index < 0) index = indexList.size - 1
if (index > indexList.size - 1) index = 0
playSound(CobblemonSounds.GUI_CLICK)
displaySideScreen(PARTY)
switchSelection(indexList[index])
}
it.onBack = {
if (moveSwitchingMode) {
playSound(CobblemonSounds.GUI_CLICK)
moveSwitchingMode = false
val movesWidget = mainScreen as? MovesWidget
if (movesWidget != null) {
movesWidget.controllerComponentManager.focused?.onUnselect()
}
} else {
val partyWidget = sideScreen as? PartyWidget
if (partyWidget != null && partyWidget.swapSource != null) {
partyWidget.draggedSlot = null
partyWidget.controllerComponentManager.let {
it.focused = it.componentList["slot_${partyWidget.swapSource}"]
}
partyWidget.swapSource = null
playSound(CobblemonSounds.PC_DROP)
} else {
onBack()
}
}
}
it.onActiveChange = { state ->
if (!state) {
moveSwitchingMode = false
}
if (mainScreenComponentManager != null) {
if (!state) {
mainScreenComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "center_screen") mainScreenComponentManager?.active = true
}
}
if (sideScreenComponentManager != null) {
if (!state) {
sideScreenComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "right_screen") sideScreenComponentManager?.active = true
}
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ true },
Component.translatable("cobblemon.controller.summary.quick_switch"),
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MOVES },
Component.translatable("cobblemon.controller.summary.change_move"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "held_item" },
Component.translatable("cobblemon.controller.toggle_held_item_type"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MOVES },
Component.translatable("cobblemon.controller.summary.adjust_move"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MARKS },
Component.translatable("cobblemon.controller.summary.clear_mark"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "right_screen" && sideScreenIndex == PARTY },
Component.translatable("cobblemon.controller.summary.swap_position"),
true
))
}
}
fun onBack() {
playSound(CobblemonSounds.GUI_CLICK)
if (sideScreenIndex != PARTY) {
if (sideScreenIndex == EVOLVE) {
controllerComponentManager.focused = controllerComponentManager.componentList["evolve"]
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["center_screen"]
}
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
}
fun swapPartySlot(sourceIndex: Int, targetIndex: Int) {
moveSwitchingMode = false
if (sourceIndex >= this.party.size || targetIndex >= this.party.size) {
return
}
@@ -362,11 +582,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
}
private fun saveMarkings() {
fun saveMarkings() {
if (::markingsWidget.isInitialized) markingsWidget.saveMarkingsToPokemon()
}
private fun saveActiveMark() {
fun saveActiveMark() {
if (mainScreenIndex == MARKS && mainScreen is MarksWidget) (mainScreen as MarksWidget).saveActiveMarkToPokemon()
}
@@ -378,12 +598,16 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
/**
* Switch center screen
*/
private fun displayMainScreen(screen: Int) {
fun displayMainScreen(screen: Int) {
// Save mark to Pokémon if any changes when switching out of marks screen
saveActiveMark()
// Get stat tab index if currently displaying stat screen
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).statTabIndex else 0
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).rawStatTabIndex else 0
if (screen != MOVES) {
moveSwitchingMode = false
}
mainScreenIndex = screen
if (::mainScreen.isInitialized) removeWidget(mainScreen)
@@ -403,6 +627,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pY = y + 12,
pokemon = this.selectedPokemon
)
mainScreenComponentManager = null
}
MOVES -> {
@@ -410,7 +635,13 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pX = x + 77,
pY = y + 12,
summary = this
)
).also { movesWidget ->
mainScreenComponentManager = movesWidget.controllerComponentManager
movesWidget.controllerComponentManager.let {
it.focused = it.componentList["move_${selectedMoveIndex}"]
}
}
}
STATS -> {
@@ -418,25 +649,38 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon,
tabIndex = subIndex
tabIndex = subIndex,
summary = this
)
mainScreenComponentManager = null
}
MARKS -> {
mainScreen = MarksWidget(
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon
)
pokemon = this.selectedPokemon,
summary = this
).also {
mainScreenComponentManager = it.controllerComponentManager
}
}
}
addRenderableWidget(mainScreen)
if (controllerComponentManager.currentSelectedComponent == "center_screen" && controllerComponentManager.active) {
focused = null
mainScreenComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
/**
* Switch right screen
*/
fun displaySideScreen(screen: Int, move: Move? = null) {
moveSwitchingMode = false
sideScreenIndex = screen
if (::sideScreen.isInitialized) removeWidget(sideScreen)
@@ -451,7 +695,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
isParty = selectedPokemon in CobblemonClient.storage.party,
summary = this,
partyList = this.party
)
).also {
sideScreenComponentManager = it.controllerComponentManager
}
}
MOVE_SWAP -> {
@@ -473,7 +719,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
moveSlotList += MoveSwapScreen.MoveSlot(switchPane, null, 0, pokemon)
}
moveSlotList.forEach { switchPane.addEntry(it) }
sideScreenComponentManager = switchPane.controllerComponentManager
}
controllerComponentManager.focused = controllerComponentManager.componentList["right_screen"]
}
}
@@ -483,14 +731,67 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
sideScreen = EvolutionSelectScreen(
x + 216,
y + 23,
pokemon = selectedPokemon
)
pokemon = selectedPokemon,
summary = this
).also {
sideScreenComponentManager = it.controllerComponentManager
}
controllerComponentManager.focused = controllerComponentManager.componentList["right_screen"]
}
}
val element = sideScreen
if (element is Renderable && element is NarratableEntry) {
addRenderableWidget(element)
}
if (controllerComponentManager.currentSelectedComponent == "right_screen" && controllerComponentManager.active) {
focused = null
sideScreenComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data != null) {
when (focused) {
nicknameEntryWidget -> return Vector4i(nicknameEntryWidget.x - 12, nicknameEntryWidget.y - 4, 78, 17)
heldItemVisibilityButton -> return Vector4i(data.x + 1, data.y + 1, data.z / 2, data.w / 2)
is PartySlotWidget -> return data.add(-2, -2, 0, 2)
else -> return data
}
} else {
when (controllerComponentManager.currentSelectedComponent) {
"center_screen" -> {
when (mainScreenIndex) {
MARKS -> {
val marksScreen = mainScreen as MarksWidget
val horizontalSpacing = if (marksScreen.marksScrollList.children().size > 5) MarksScrollingWidget.SLOT_SPACING else (MarksScrollingWidget.SLOT_SPACING + 1)
val row = marksScreen.lastSelectedMark / 6
val column = marksScreen.lastSelectedMark % 6
val startX = marksScreen.marksScrollList.rowLeft + (horizontalSpacing + MarksScrollingWidget.SLOT_SIZE) * column
val startY = marksScreen.marksScrollList.getRowTop(row) + MarksScrollingWidget.SLOT_SPACING
return Vector4i(startX - 2, startY - 2, MarksScrollingWidget.SLOT_SIZE + 4, MarksScrollingWidget.SLOT_SIZE + 4)
}
else -> return Vector4i(mainScreen.x + 2, mainScreen.y + 2, 130, 144)
}
}
"right_screen" -> {
when (sideScreenIndex) {
EVOLVE -> {
val evolveScreen = sideScreen as EvolutionSelectScreen
return Vector4i(evolveScreen.rowLeft - 5, evolveScreen.getRowTop(evolveScreen.children().indexOf(focused)) - 2, evolveScreen.rowWidth + 4, EvolutionSelectScreen.SLOT_HEIGHT + 4)
}
MOVE_SWAP -> {
val moveSwapScreen = sideScreen as MoveSwapScreen
return Vector4i(moveSwapScreen.rowLeft - 11, moveSwapScreen.getRowTop(moveSwapScreen.children().indexOf(focused)) - 2, moveSwapScreen.rowWidth + 13, MoveSwapScreen.SLOT_HEIGHT + 4)
}
}
}
}
}
return null
}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -558,7 +859,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
// Poké Ball
val ballResource = cobblemonResource("textures/item/poke_balls/" + selectedPokemon.caughtBall.name.path + ".png")
val ballResource = selectedPokemon.caughtBall.name.toAssetPath("textures/item/poke_balls/", ".png")
blitk(
matrixStack = matrices,
texture = ballResource,
@@ -630,8 +931,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
val itemX = x + 3
val itemY = y + 104
if (!displayedItem.isEmpty) {
matrices.pushPose()
matrices.translate(0.0,0.0,-200.0)
context.renderItem(displayedItem, itemX, itemY)
context.renderItemDecorations(Minecraft.getInstance().font, displayedItem, itemX, itemY)
matrices.popPose()
}
drawScaledText(
@@ -660,19 +964,25 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
scale = SCALE
)
matrices.pushPose()
// Prevent widgets from being overlapped by other components
matrices.translate(0.0, 0.0, 1000.0)
// Set Look View Pos
val selectionData = getSelectionPosAndSize()
if (controllerComponentManager.active && selectionData != null) {
modelWidget.overrideLookPos = Vector2i(selectionData.x + selectionData.z / 2, selectionData.y + selectionData.w / 2)
} else {
modelWidget.overrideLookPos = null
}
// Render all added Widgets
super.render(context, mouseX, mouseY, delta)
// Selection Rendering
renderSelection(matrices)
// Render Item Tooltip
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)
}
matrices.popPose()
}
/**
@@ -32,8 +32,8 @@ class SummaryButton(
private val text: MutableComponent = Component.empty(),
private val resource: ResourceLocation,
private val activeResource: ResourceLocation? = null,
private val renderRequirement: ((button: SummaryButton) -> Boolean) = { true },
private val clickRequirement: ((button: SummaryButton) -> Boolean) = { true },
val renderRequirement: ((button: SummaryButton) -> Boolean) = { true },
val clickRequirement: ((button: SummaryButton) -> Boolean) = { true },
private val hoverTexture: Boolean = true,
private val silent: Boolean = false,
private val boldText: Boolean = true,
@@ -67,7 +67,7 @@ class SummaryButton(
y = buttonY / scale,
width = buttonWidth,
height = buttonHeight,
vOffset = if (hoverTexture && isButtonHovered(pMouseX, pMouseY)) buttonHeight else 0,
vOffset = if (hoverTexture && (isButtonHovered(pMouseX, pMouseY) || isFocused)) buttonHeight else 0,
textureHeight = if (hoverTexture) (buttonHeight.toFloat() * 2) else buttonHeight,
scale = scale
)
@@ -15,8 +15,11 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.common.SummaryScrollList
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.FormData
@@ -34,7 +37,8 @@ import org.joml.Vector3f
class EvolutionSelectScreen(
x: Int,
y: Int,
val pokemon: Pokemon
val pokemon: Pokemon,
val summary: Summary
): SummaryScrollList<EvolutionSelectScreen.EvolveSlot>(
x,
y,
@@ -52,8 +56,36 @@ class EvolutionSelectScreen(
private var entriesCreated = false
var controllerComponentManager = ControllerComponentManager(summary).also {
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
public override fun addEntry(entry: EvolveSlot): Int {
return super.addEntry(entry)
val i = super.addEntry(entry)
controllerComponentManager.componentList["moves_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = {
entry.selectButton.onPress()
}
it.onSelect = {
ensureVisible(entry)
entry.selectButton.isFocused = true
}
it.onUnselect = {
entry.selectButton.isFocused = false
}
}
if (children().size > 1) {
controllerComponentManager.componentList["moves_${i}"]?.let {
it.pairUp(controllerComponentManager.componentList["moves_${i-1}"])
it.pairDown(controllerComponentManager.componentList["moves_${0}"])
}
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["moves_${i}"]
}
return i
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import org.joml.Quaternionf
import org.joml.Vector2i
import org.joml.Vector3f
class ModelWidget(
@@ -57,6 +58,8 @@ class ModelWidget(
var currentYawAndPitch: Pair<Float, Float> = Pair(0f, 0f)
val rotationVector = Vector3f(13F, rotationY, 0F)
var overrideLookPos: Vector2i? = null
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, partialTicks: Float) {
if (!render) {
return
@@ -86,7 +89,7 @@ class ModelWidget(
val timeLooking = lookStartTime?.let { (System.currentTimeMillis() - it) / 1000 } ?: MAX_SECONDS_LOOKING
val lookedTooMuch = timeLooking >= MAX_SECONDS_LOOKING
val resetToCenter = (!isHovered && lookedTooMuch) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
val resetToCenter = (!isHovered && lookedTooMuch && overrideLookPos == null) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
if (resetToCenter) {
lookStartTime = null
@@ -95,12 +98,19 @@ class ModelWidget(
val rotation = Quaternionf().fromEulerXYZDegrees(rotationVector)
if (shouldFollowCursor) {
var xPos = mouseX
var yPos = mouseY
if (overrideLookPos != null) {
xPos = overrideLookPos!!.x
yPos = overrideLookPos!!.y
}
currentYawAndPitch = calculateHeadYawAndPitch(
x + width / 2f,
y + height / 2f,
rotation,
mouseX,
mouseY,
xPos,
yPos,
currentYawAndPitch.first,
currentYawAndPitch.second,
resetToCenter
@@ -116,6 +126,7 @@ class ModelWidget(
headYaw = currentYawAndPitch.first,
headPitch = currentYawAndPitch.second,
)
context.flush()
heldItemRenderer.renderOnModel(
pokemon.heldItem,
@@ -24,6 +24,7 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.cobblemon.mod.common.util.toAssetPath
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
@@ -87,7 +88,7 @@ class PartySlotWidget(
y = y,
width = width,
height = height,
vOffset = getSlotVOffset(slotPokemon, isHovered, isSelected),
vOffset = getSlotVOffset(slotPokemon, isHovered || isFocused, isSelected),
textureHeight = height * 2,
)
@@ -107,7 +108,7 @@ class PartySlotWidget(
)
}
val ballIcon = cobblemonResource("textures/gui/ball/" + slotPokemon.caughtBall.name.path + ".png")
val ballIcon = slotPokemon.caughtBall.name.toAssetPath("textures/gui/ball/", ".png")
val ballHeight = 22
blitk(
matrixStack = matrices,
@@ -222,16 +223,21 @@ class PartySlotWidget(
if (partyWidget.swapEnabled) {
toggleDrag(true)
} else {
if ((index > -1) && (summary.selectedPokemon.uuid != pokemon?.uuid)) {
summary.switchSelection(index)
if (pokemon != null) partyWidget.playSound(CobblemonSounds.GUI_CLICK)
return true
}
if (onPress()) return true
}
}
return super.mouseClicked(pMouseX, pMouseY, pButton)
}
fun onPress(): Boolean {
if ((index > -1) && (summary.selectedPokemon.uuid != pokemon?.uuid)) {
summary.switchSelection(index)
if (pokemon != null) partyWidget.playSound(CobblemonSounds.GUI_CLICK)
return true
}
return false
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (partyWidget.swapEnabled) toggleDrag(false)
return partyWidget.mouseReleased(pMouseX, pMouseY, pButton)
@@ -14,6 +14,8 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
@@ -67,6 +69,8 @@ class PartyWidget(
}
)
var controllerComponentManager = ControllerComponentManager(summary)
init {
if (partySize > 6 || partySize < 1)
throw InvalidParameterException("Invalid party size")
@@ -85,7 +89,7 @@ class PartyWidget(
y += (32 * offsetIndex) + offsetY
}
PartySlotWidget(
val slot = PartySlotWidget(
pX = x,
pY = y,
partyWidget = this,
@@ -97,6 +101,86 @@ class PartyWidget(
this.addWidget(widget)
partySlots.add(widget)
}
controllerComponentManager.componentList["slot_${index}"] = ControllerComponent().also {
it.associatedGUIElement = slot
it.onPress = {
if (swapEnabled) {
if (swapSource == null) {
if (partyList[index] != null) {
swapSource = index
draggedSlot = PartySlotWidget(
pX = slot.x + 4,
pY = slot.y + 4,
partyWidget = this,
summary = summary,
pokemon = partyList[index],
index = -1,
isClientPartyMember = isParty
)
draggedSlot?.isFocused = true
playSound(CobblemonSounds.PC_GRAB)
}
} else {
if (swapSource != index) {
summary.swapPartySlot(swapSource!!, index)
}
swapSource = null
draggedSlot = null
playSound(CobblemonSounds.PC_DROP)
}
} else slot.onPress()
}
it.onSpecial1 = {
swapEnabled = !swapEnabled
swapButton.buttonActive = swapEnabled
if (!swapEnabled) {
swapSource = null
draggedSlot = null
}
playSound(CobblemonSounds.GUI_CLICK)
}
fun updateDraggedSlotPos() {
draggedSlot?.let { theSlot ->
theSlot.x = slot.x + 4
theSlot.y = slot.y + 4
}
}
if (index-2 >= 0) {
it.up = {
controllerComponentManager.componentList["slot_${index-2}"]
}
}
if (index+2 <= 5) {
it.down = {
controllerComponentManager.componentList["slot_${index+2}"]
}
}
if (index.mod(2) == 0) {
it.right = {
controllerComponentManager.componentList["slot_${index+1}"]
}
} else {
it.left = {
controllerComponentManager.componentList["slot_${index-1}"]
}
}
it.onSelect = {
updateDraggedSlotPos()
summary.lastSelectedPartySlot = index
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["slot_${summary.lastSelectedPartySlot}"]
it.onActiveChange = { state ->
if (!state) {
swapSource = null
draggedSlot = null
}
}
}
}
Loaded 100 of 890 files, more files were not shown because too many files have changed in this diff. Show more