Compare commits

...
Author SHA1 Message Date
Gertimoshka 72999ff600 Fixes to Rendering Order 2026-08-15 14:37:59 +03:00
Gertimoshka ec18bb9154 Starter Screen Implementation and Pokedex Partial Fixes 2026-08-12 23:56:44 +03:00
Gertimoshka 1e1fba5f7f Innitial Merge Fixes 2026-08-12 10:20:33 +03:00
Gertimoshka 69497bc3cc The Painful Merge Complete: The Revival of Controlify Support 2026-08-12 09:49:01 +03:00
Polymeta 058c06f56b Merge branch 'implement_pokemon' into 'main'
some ride stats and other stuff

See merge request cable-mc/cobblemon!2341
2026-08-08 19:14:42 +00:00
Carmendar 28bee4189a Merge branch 'main' into implement_pokemon 2026-08-07 23:30:54 -05:00
Carmendar 3f0e21b6b3 some minor ride stat adjustments 2026-08-07 23:10:53 -05:00
Jerzykv 9ca2d47562 Fixed Metagross idle loop yet again 2026-08-06 18:13:30 +02:00
Jakotens 94f0914aa8 Poser fixes to Pidgey, Pidgeotto, Pidgeot, Aerodactyl, and Snivy. Animation and walk speed fixes to Pidgey, Pidgeotto, and Pidgeot. Added a new neck_ai bone to Pidgeotto. 2026-08-05 13:09:05 -06:00
Jakotens 60101876b5 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-08-05 11:29:44 -06:00
RedGenesectNinja b8a4dfbcb4 Adjusted Bidoof's scale again 2026-08-05 03:45:24 -05:00
RedGenesectNinja fbc5959c67 Adjusted Bidoof's scale 2026-08-05 03:14:10 -05:00
Kenji_64 49f20fe1a4 Several cry/poser related fixes 2026-08-04 15:17:35 +02:00
Jerzykv 3c4288d7fc More alpha eyes 2026-08-04 14:55:54 +02:00
Jerzykv e03bb51a74 Substitute animation fix 2026-08-04 14:55:43 +02:00
Jerzykv 8545de321f Lanturn poser fixes 2026-08-04 14:55:31 +02:00
Jerzykv 5e353e9a6d Chinchou json poser conversion 2026-08-04 14:55:21 +02:00
Jerzykv c1fd257f63 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-08-04 13:56:38 +02:00
Jerzykv ea0ff3bfb9 Komala can no longer walk 2026-08-04 13:56:30 +02:00
Hiroku d4b6e36aab Merge branch 'gamemasters' into 'main'
Interaction Fix

See merge request cable-mc/cobblemon!2339
2026-08-03 08:49:25 +00:00
dhi_holo 0fb721702d Interaction Fix
Solves interaction problem reported https://discord.com/channels/725110639554592879/1481053968716009493/1533385403170820148

Habitat Block acts like a block without interactions if right clicked by a player in survival, or without operator permissions of level 2 or higher (same as command blocks, known as Gamemaster)
With those permissions, it works as usual

Also removed Habitat Block from the Cobblemon Blocks tab, leaving it solely in the Operator Items Tab, like Command Blocks.
2026-08-02 21:08:13 +10:00
Polymeta 0e5053b787 Merge branch 'config-screen-scrolling-labels' into 'main'
Labels on the config screen now scroll instead of overflowing

See merge request cable-mc/cobblemon!2338
2026-08-01 22:09:46 +00:00
lion f3867e0b1d Labels on the config screen now scroll instead of overflowing 2026-08-01 22:09:46 +00:00
Hiroku a4fcd1ae7b Merge branch 'transparent-occlusion' into 'main'
Fix translucent draw order

See merge request cable-mc/cobblemon!2337
2026-08-01 01:56:58 +00:00
Jordan ed9aff2448 Fix translucent draw order 2026-08-01 01:56:57 +00:00
Nick 8497a2a083 Merge branch 'trade-dupe-fix' into 'main'
Trade dupe fix

See merge request cable-mc/cobblemon!2336
2026-07-31 15:22:15 -07:00
Deltric b389a95b5e Trade dupe fix 2026-07-31 17:03:33 -05:00
Polymeta c310027ce1 Merge branch 'Habitat-Bug-Fixes' into 'main'
Fixin some things

See merge request cable-mc/cobblemon!2335
2026-07-31 20:34:24 +00:00
Slippy 5ddb01df36 More Bug Fixes 2026-07-31 14:25:33 -05:00
Slippy 5a6f49aad5 found one more thing 2026-07-31 02:54:16 -05:00
Slippy 793efcfa3d Fixin some things 2026-07-31 02:49:24 -05:00
Polymeta d671c5a8b8 [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-07-29 11:59:10 +01:00
Polymeta 69b31dd9b9 fix up null checks to prevent crashes during ticks in BerryBlock
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-07-29 11:45:50 +01:00
ewwww f785c5405f a variety of tiny tweaks 2026-07-29 02:20:57 -04:00
Polymeta fc94bbac4c Merge branch 'dandelion-sinistertea-fix' into 'main'
Dandelion Sinister Tea Fix

See merge request cable-mc/cobblemon!2324
2026-07-28 15:53:25 +00:00
Ceejay f7d315fa33 Dandelion Sinister Tea Fix 2026-07-28 15:53:25 +00:00
Paul 7c30fd81f3 Scraped behaviours for updated AI behaviour 2026-07-28 20:10:29 +10:00
Paul 7f1704d573 Fixed habitat spelling beheeyem wrong 2026-07-28 19:49:22 +10:00
Paul 0c2ec3ca81 Scraped habitat spawns, deduplicated fixes in changelog, update wildlife -> wildfire in habitat pool 2026-07-28 19:43:09 +10:00
Paul 33d3e156cc Merge remote-tracking branch 'origin/lang-updates' 2026-07-28 19:06:49 +10:00
arvid6 415642a8ba Translated using Weblate (Spanish)
Currently translated at 99.6% (9432 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-07-27 18:45:01 +00:00
arvid6 dd12081cd1 Translated using Weblate (Spanish)
Currently translated at 99.5% (9425 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-07-27 11:20:52 +00:00
arvid6 b7de84acfd Translated using Weblate (Spanish)
Currently translated at 99.1% (9384 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-07-27 11:06:54 +00:00
ewwww 2dfca3ff89 a variety of tiny tweaks 2026-07-27 05:17:44 -04:00
Hiroku 8efda8c47b Merge branch 'main' into 'main'
Fix load time deadlock between ElementalType and ElementalTypes

Closes #2034

See merge request cable-mc/cobblemon!2332
2026-07-26 02:24:21 +00:00
Naitronbomb 6baf96f83b Add changelog entry for load time deadlock fix 2026-07-25 11:13:18 -07:00
Naitronbomb 009a9c3f15 Fix load time deadlock between ElementalType and ElementalTypes 2026-07-25 09:29:47 -07:00
Polymeta fc0a6c0c8b Merge branch 'gem-unbalance' into 'main'
Type Gem Loot Table Tweaks

See merge request cable-mc/cobblemon!2322
2026-07-25 16:14:44 +00:00
Hiroku 041e080cd4 Merge branch 'fix-starter-toast-dismissal' into 'main'
Fix starter toast not being dismissed when capturing a pokemon

See merge request cable-mc/cobblemon!2331
2026-07-25 03:13:11 +00:00
lion 65bbadcfa8 Fix starter toast not being dismissed when capturing a pokemon 2026-07-25 03:13:11 +00:00
阿鱼 a70d5e200a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (9420 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-07-25 00:24:55 +00:00
阿鱼 4d0ee751a3 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 97.2% (9206 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-07-24 22:42:33 +00:00
Hiroku d87438e53d I'll try changing the repo URL, that's a good trick 2026-07-24 16:57:53 +10:00
Hiroku 3efef07afa Merge remote-tracking branch 'origin/main' 2026-07-24 16:27:11 +10:00
Hiroku 9ff227fbef Fix pipelines maybe with findutils installs? 2026-07-24 16:27:01 +10:00
Hiroku 55ef5fd537 Merge branch 'fix-forms-riding-camera-offset' into 'main'
Fix riding camera offset defined on Pokemon forms not being applied

See merge request cable-mc/cobblemon!2326
2026-07-24 06:23:29 +00:00
lion b3cdfb579c Fix riding camera offset defined on Pokemon forms not being applied 2026-07-24 06:23:28 +00:00
Hiroku 29588f1fde Merge branch 'implement_pokemon' into 'main'
the last of 1.8 (merge once ride stats are in)

See merge request cable-mc/cobblemon!2329
2026-07-24 06:19:25 +00:00
Polymeta 24c10d1a4f Merge branch 'riding-scrape-1.8' into 'implement_pokemon'
Riding scrape

See merge request cable-mc/cobblemon!2330
2026-07-24 06:10:15 +00:00
Carmendar ac0d9d4d74 Seat fixes 2026-07-24 00:53:00 -05:00
Nico Merenq d33974b19e Translated using Weblate (Russian)
Currently translated at 78.8% (7467 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-24 05:48:29 +00:00
Carmendar 1dcdfe309e Correcting Goodra ride stats 2026-07-24 00:03:33 -05:00
Landon Wills 6ce1990145 Fix issues with scraper 2026-07-24 00:54:33 -04:00
Landon Wills 1203721889 Remove pidgeotto scrape 2026-07-24 00:54:01 -04:00
Landon Wills 333672f993 Push pidgeot riding scrape 2026-07-24 00:53:33 -04:00
Carmendar f0f9a9596f Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-07-23 23:52:58 -05:00
Carmendar 80b8aa2b8c Hisuian Goodra anim updates 2026-07-23 23:52:41 -05:00
Landon Wills b919e447d8 Add scraped files 2026-07-24 00:36:22 -04:00
Hiroku 3075b7fd73 Improved spawn scraper script: recognize more time range names, support 'storm' as a weather condition 2026-07-24 14:31:13 +10:00
ewwww 412e3fcb8a minor poser tweaks 2026-07-24 00:23:14 -04:00
ewwww 32253fd462 starmie shiny texture fix 2026-07-23 23:17:42 -04:00
arvid6 4de018a209 Translated using Weblate (Spanish)
Currently translated at 98.2% (9297 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-07-23 15:30:25 +00:00
ewwww 2204a4b700 substitute remodel + animations 2026-07-23 02:43:37 -04:00
arvid6 22a27cbbd7 Translated using Weblate (Spanish)
Currently translated at 97.5% (9238 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-07-21 18:03:56 +00:00
arvid6 e202e7bf2f Translated using Weblate (Spanish)
Currently translated at 97.3% (9217 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-07-21 17:03:35 +00:00
ewwww 03104b3a3d re-deleting these 2026-07-20 04:09:26 -04:00
Polymeta 280e1ae820 Merge branch 'implement_these_dumbfuck_birds' into 'implement_pokemon'
Implement these dumbfuck birds

See merge request cable-mc/cobblemon!2328
2026-07-20 08:02:19 +00:00
Carmendar ee5653bfba Merge branch 'implement_pokemon' into implement_these_dumbfuck_birds
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/repository/VaryingModelRepository.kt
2026-07-20 02:49:35 -05:00
Carmendar a9c3c14367 Finishing touches to Pidgeot 2026-07-20 02:44:46 -05:00
Paul 9cdc979067 Fixed /giveallpokemon not respecting the min and max options 2026-07-20 13:13:27 +10:00
Carmendar 4b45fdde3f Pidgeotto fixed, and work done to Pidgeot. 2026-07-19 14:55:31 -05:00
Nico Merenq 5bf7cc6aef Translated using Weblate (Russian)
Currently translated at 78.7% (7454 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-19 12:01:56 +00:00
Polymeta 7cf1f5a4e5 Merge branch 'habitats-crash-fix' into 'main'
Fixed some crashes that happened due to invalid processor referencing and structure

See merge request cable-mc/cobblemon!2327
2026-07-18 22:12:10 +00:00
Faintful 0fe2b0f60c Fixed some crashes that happened due to invalid processor referencing and structure 2026-07-18 22:22:56 +02:00
阿鱼 667376226b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 96.4% (9134 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-07-18 14:24:13 +00:00
Nico Merenq 5506e076f6 Translated using Weblate (Russian)
Currently translated at 78.5% (7437 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-18 14:24:11 +00:00
Hiroku f8799efd9d Merge branch 'Habitat-Blocks-pt2' into 'main'
Habitat blocks pt2

See merge request cable-mc/cobblemon!2325
2026-07-18 01:47:16 +00:00
Slippy bb642fbf29 ALL HABITATS HAVE HABITAT BLOCKS!!! 2026-07-17 16:23:59 -05:00
Nico Merenq e19aa02283 Translated using Weblate (Russian)
Currently translated at 78.5% (7433 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-17 17:47:39 +00:00
Slippy e65010e289 Six more habitats with habitat blocks 2026-07-16 01:05:07 -05:00
Nico Merenq 4b4a715868 Translated using Weblate (Russian)
Currently translated at 78.4% (7429 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-15 20:06:53 +00:00
Slippy 26ec5717f1 11 more habitats with habitat blocks 2026-07-15 11:55:41 -05:00
Nico Merenq 446d4771d8 Translated using Weblate (Russian)
Currently translated at 78.4% (7429 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-15 13:44:03 +00:00
Nico Merenq 9d817b3f77 Translated using Weblate (Russian)
Currently translated at 78.4% (7427 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-15 03:10:05 +00:00
Zack the Ghast 21213c4fce Translated using Weblate (Portuguese (Portugal))
Currently translated at 66.3% (6281 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2026-07-14 07:36:57 +00:00
Watuneni iteite cebf105dd5 Translated using Weblate (Japanese)
Currently translated at 100.0% (9467 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-07-13 07:43:46 +00:00
Jakotens 123d3291c6 Fixed Gogoat's battle_idle animation being absent from its poser which caused certain battle animations to break. 2026-07-12 16:21:57 -06:00
dhi_holo 935a39c578 Type Gems now place Stage 1 (Medium) rather than Stage 0 (Small) 2026-07-12 17:56:24 +10:00
dhi_holo d53e67b55c New Loot Tables 2026-07-12 17:31:50 +10:00
Zack the Ghast ec0766ddcd Translated using Weblate (Portuguese (Portugal))
Currently translated at 65.9% (6244 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2026-07-11 15:56:17 +00:00
Nico Merenq 917577f7a8 Translated using Weblate (Russian)
Currently translated at 78.3% (7418 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-11 03:06:15 +00:00
Nico Merenq 01de2f71af Translated using Weblate (Russian)
Currently translated at 78.2% (7412 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-10 14:42:24 +00:00
Nico Merenq 0779251581 Translated using Weblate (Russian)
Currently translated at 76.5% (7244 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-09 23:17:58 +00:00
Nico Merenq 14d4c6c2e4 Translated using Weblate (Russian)
Currently translated at 76.2% (7222 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-09 21:50:35 +00:00
Nico Merenq de82d36da3 Translated using Weblate (Russian)
Currently translated at 75.2% (7124 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-09 17:04:40 +00:00
Nico Merenq ae2704e218 Translated using Weblate (Russian)
Currently translated at 75.0% (7104 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-09 13:30:48 +00:00
Nico Merenq a235c1286c Translated using Weblate (Russian)
Currently translated at 74.6% (7063 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-07-09 13:20:24 +00:00
Polymeta ce584bd403 Merge branch 'tm-unlock-utilities' into 'main'
Move Data Monitor Learn to Helper, Add Utility and MoLang Functions for Unlocking TMs + Impossible Obtain Method

See merge request cable-mc/cobblemon!2321
2026-07-09 08:09:45 +00:00
Konoha D 1c25861e04 Move Data Monitor Learn to Helper, Add Utility and MoLang Functions for Unlocking TMs + Impossible Obtain Method 2026-07-09 08:09:45 +00:00
Watuneni iteite 006cc21ebb Translated using Weblate (Japanese)
Currently translated at 100.0% (9467 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-07-08 03:33:00 +00:00
Watuneni iteite 5ab9072f94 Translated using Weblate (Japanese)
Currently translated at 99.6% (9431 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-07-07 09:36:33 +00:00
Watuneni iteite f7023b9737 Translated using Weblate (Japanese)
Currently translated at 99.4% (9417 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-07-06 07:04:09 +00:00
Paul 987a7d0ccc Fixed entity attributes not updating when a pokemon levels up 2026-07-04 11:56:47 +10:00
ewwww d49c0a0ec6 changelog 2026-07-03 17:26:41 -04:00
ewwww d4a910bf26 added corsola's physical attack + updated textures 2026-07-03 17:23:13 -04:00
Kenji_64 25b75231ad Implemented Cottonee and Whimsicott animations! 2026-07-03 17:22:13 +02:00
RedGenesectNinja 0db1f372e3 Adjusted swimming heights for both forms of Grimer and Muk 2026-07-03 03:58:57 -05:00
RedGenesectNinja 1ddffc1c0d Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-07-03 01:19:34 -05:00
RedGenesectNinja 5fe04aad83 Implemented Grimer animations 2026-07-02 20:26:49 -05:00
Polymeta 03b74d4b72 Merge branch 'implement_pokemon' into 'main'
various asset updates

See merge request cable-mc/cobblemon!2319
2026-07-02 06:16:52 +00:00
ewwww a5f150af9a poser tweaks 2026-07-02 00:56:11 -04:00
ewwww b3b5e47cc9 tinyprogress 2026-07-02 00:06:01 -04:00
Watuneni iteite 1d0aa06dc4 Translated using Weblate (Japanese)
Currently translated at 99.1% (9389 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-07-01 11:24:54 +00:00
ewwww 1e5c0f5137 added chansey + blissey animations 2026-07-01 00:26:07 -04:00
ewwww 7bafd53f58 minor dugtrio tweaks 2026-06-30 07:36:16 -04:00
ewwww bdbdf238f5 dugtrio physical attack + diglett tweaks 2026-06-30 07:06:24 -04:00
ewwww 6cd71050df added golett remodel 2026-06-30 04:11:09 -04:00
Polymeta 2047f4f461 Merge branch 'my-stupid-tmm-bug' into 'main'
Fix for TMM putting burn screen tab when no tm is selected

See merge request cable-mc/cobblemon!2320
2026-06-30 08:08:37 +00:00
Gertimoshka 0aaed4bb84 Fixed issue where it would still put you on burn screen even with no tm selected 2026-06-30 06:03:56 +03:00
ewwww 3779b4c0b3 updated assets 2026-06-29 17:42:21 -04:00
ewwww 74aaa16a0d physical attack animation for graveler + eye locators for alolan graveler 2026-06-29 07:01:18 -04:00
ewwww 332ef2fddd diglett physical attack 2026-06-29 06:30:21 -04:00
ewwww f29c5c5f6d new animations for sandshrew 2026-06-29 05:24:17 -04:00
ewwww c46375aafb new animations for nidoran male, female, and nidorina 2026-06-29 05:23:54 -04:00
Watuneni iteite a5e16ccf65 Translated using Weblate (Japanese)
Currently translated at 98.7% (9349 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-06-29 08:24:38 +00:00
ewwww 4cc83560e8 kakuna animations 2026-06-29 01:53:20 -04:00
ewwww a7f4ef8449 mountable duraludon and archaludon (stats not final) 2026-06-29 01:25:42 -04:00
ewwww edaafa2263 work in progress i'm sleep deprived 2026-06-28 08:28:18 -04:00
ewwww 0162bdae5f added new animations for poliwhirl 2026-06-27 18:54:44 -04:00
阿鱼 da82eb8d61 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.9% (8993 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-27 11:50:29 +00:00
ewwww 34fc434883 implemented new battle animations for happiny 2026-06-27 05:51:30 -04:00
ewwww 74949a02de yungoos poser tweak 2026-06-27 05:51:04 -04:00
ewwww 1617c1b087 added zubat physical animation 2026-06-27 04:57:03 -04:00
ewwww 4b1c180538 added poliwrath walk animation 2026-06-27 04:56:50 -04:00
ewwww cba6f51706 implemented new yungoos & gumshoos animations 2026-06-27 03:46:00 -04:00
ewwww 9b9fbedaf7 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-06-26 19:27:45 -04:00
ewwww 8c3acd9a0e 'angery' typo 2026-06-26 18:23:48 -04:00
CaptainLennie 4ab52cac0e Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-06-26 19:28:24 +02:00
CaptainLennie 68805a5dd1 Fixed tentacool using ground animations while swimming on the surface (and updated to JSON poser) 2026-06-26 19:28:10 +02:00
Jerzykv 5a0b4e8f99 Alolan Vulpix portrait tweak 2026-06-26 14:03:11 +02:00
Jerzykv 3e9d3a307f Implemented animations for Rellor and Rabsca 2026-06-26 13:59:14 +02:00
ewwww 7c4d3ecb71 tweaked aerodactyl's walkspeed 2026-06-26 07:30:01 -04:00
ewwww e91fd5596a implemented new vulpix animations 2026-06-26 07:29:29 -04:00
ewwww f9d218adf8 forgot something 2026-06-25 05:46:04 -04:00
ewwww 05d70b28e7 monitor screen updates 2026-06-25 05:41:30 -04:00
ewwww 6755462eb0 added new animations for noctowl 2026-06-25 05:39:58 -04:00
ewwww 7ef3facac8 added new animations for drowzee and hypno 2026-06-25 04:07:39 -04:00
Watuneni iteite 2dc93f3336 Translated using Weblate (Japanese)
Currently translated at 96.7% (9158 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-06-24 10:16:38 +00:00
ewwww b7c16324bd misc texture fixes 2026-06-24 04:05:06 -04:00
阿鱼 e8864418d4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.9% (8992 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-23 22:49:29 +00:00
Polymeta 7ccd0bbe11 Merge branch 'multiplayer_mount_sync' into 'main'
Multiplayer mount sync

Closes #2023

See merge request cable-mc/cobblemon!2315
2026-06-22 12:23:42 +00:00
ewwww e1cc9c7aba added new animations for Togetic 2026-06-22 02:45:55 -04:00
ewwww 79e821a438 added new animations for Staryu and Starmie 2026-06-22 01:08:45 -04:00
Polymeta d289880cc8 Merge branch 'tmm-fixes-n-changes' into 'main'
TMM Fixes and Changes

See merge request cable-mc/cobblemon!2316
2026-06-20 19:43:21 +00:00
ewwww 19dd57feed form name corrections 2026-06-20 13:16:12 -04:00
Paul 6609c7d038 Added held_item_visible pokemonproperty 2026-06-20 22:29:14 +10:00
Paul 88afe05c16 Fixed NPE's when block entities aren't attached to berry blocks at time of ticking, closes #2020 2026-06-20 18:01:52 +10:00
ewwww e739b57a08 grammar. 2026-06-20 01:22:04 -04:00
Gertimoshka 99aa25e1bf I forgor 2026-06-19 17:50:52 +03:00
Gertimoshka 76faa50035 Fixes, tmm multiplayer syncing and cleanup 2026-06-19 17:42:50 +03:00
aeroeng15 8ce989cb6b Changelog 2026-06-18 19:03:45 -07:00
aeroeng15 02030950aa Force passenger updates 2026-06-18 18:53:36 -07:00
阿鱼 82b418d54d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.9% (8993 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-18 21:12:39 +00:00
Polymeta 6148b58fdc Merge branch 'qa-fixes-84' into 'main'
Qa fixes 84

See merge request cable-mc/cobblemon!2314
2026-06-17 11:17:33 +00:00
ewwww 2044762b72 various poser alterations that accidentally got reverted 2026-06-17 05:33:34 -04:00
ewwww 77117e83c7 implemented (non-riding) animations & corrected scales for Duraludon & Archaludon 2026-06-17 05:33:21 -04:00
Carmendar 55f4e6e738 Goodra ride anim adjustments 2026-06-17 00:57:18 -05:00
Faintful 1e4936aeac Add changelog 2026-06-16 20:40:08 +02:00
Faintful 15ea72e777 Fixed Smoked Tail Curry returning a wooden bowl when consumed 2026-06-16 20:38:56 +02:00
Polymeta 3c3d4e4552 [skip-ci] changelogging 2026-06-16 17:43:50 +00:00
Polymeta 796e416578 Merge branch 'fix-auto-third-person-mod-compat' into 'main'
Add check for remember ride camera config when dismounting

Closes #2012

See merge request cable-mc/cobblemon!2308
2026-06-16 17:37:08 +00:00
lion 7608dfde94 Add check for remember ride camera config when dismounting 2026-06-16 17:37:07 +00:00
Polymeta b2b871617a Merge branch 'qa-fixes-85' into 'main'
Qa fixes 85

See merge request cable-mc/cobblemon!2313
2026-06-16 17:19:16 +00:00
Faintful bc4c8d13b7 Fix Panpour consuming a held item when evolving with a Water Stone 2026-06-16 17:07:36 +02:00
Faintful 0f9c51f7dd Fixed full heal stackable block not dropping full heals 2026-06-16 16:05:06 +02:00
Polymeta 2c2623e0f9 Merge branch 'summary-and-pokedex-profile-translations' into 'main'
Add profile translation and scale properties specific to the Pokédex and summary

See merge request cable-mc/cobblemon!2309
2026-06-16 13:44:41 +00:00
lion 8ec85e7d2f Add profile translation and scale properties specific to the Pokédex and summary 2026-06-16 13:44:41 +00:00
Paul d5a08314eb Merge remote-tracking branch 'origin/lang-updates' 2026-06-16 20:36:49 +10:00
Paul 062c32be2a Correct the parsing of phases in habitat GUI (they aren't moon phases) 2026-06-16 20:33:54 +10:00
阿鱼 09590b49cd Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.9% (8988 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 10:14:47 +00:00
Polymeta 420cbd2c99 Merge branch 'fix-pvn-fainted-party-battle' into 'main'
Fix PvN battle start with fainted parties

See merge request cable-mc/cobblemon!2311
2026-06-16 09:45:36 +00:00
Jongho Hong 0380013f37 Add changelog entry 2026-06-16 18:41:43 +09:00
阿鱼 6c1fcc103a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.7% (8973 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 09:14:38 +00:00
阿鱼 59b01c3988 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.7% (8971 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 08:58:39 +00:00
阿鱼 678fa2d0ce Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.6% (8961 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 08:28:14 +00:00
Jongho Hong 94abd79923 Fix PvN battle start with fainted parties 2026-06-16 17:22:50 +09:00
阿鱼 0a1931aaae Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.6% (8958 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 08:21:26 +00:00
阿鱼 85d76dcc29 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.5% (8952 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 08:07:26 +00:00
阿鱼 bd0a9ef654 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.4% (8944 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 07:27:07 +00:00
阿鱼 7a702e276c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.2% (8926 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 05:41:55 +00:00
阿鱼 3f9a2825cb Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 94.2% (8921 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 05:30:18 +00:00
阿鱼 d1462bc27b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 93.9% (8892 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 05:05:07 +00:00
阿鱼 211b14dbd0 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 93.5% (8852 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 04:59:55 +00:00
阿鱼 47e83003c3 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 93.4% (8850 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 04:59:34 +00:00
阿鱼 59134c4212 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 93.4% (8849 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-16 04:32:46 +00:00
阿鱼 44b434562c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 93.4% (8847 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-15 20:32:05 +00:00
Faintful 7af9b0854a Fixed Status Cure items not returning a bottle after use 2026-06-15 17:55:22 +02:00
Hiroku 344b0750f9 Merge branch 'implement_pokemon' into 'main'
more QA fixes

See merge request cable-mc/cobblemon!2310
2026-06-15 04:26:30 +00:00
ewwww 10d9d97c11 restored broken or missing seat locators for Ariados, Bastiodon, Dhelmise, Dondozo, Drifblim, Girafarig, and Klinklang 2026-06-15 00:06:56 -04:00
Paul 94bcfba076 Phase count should be derived, not set 2026-06-14 22:10:46 +10:00
RedGenesectNinja 69a75ffcd4 Fixed broken Alola bias Exeggcute textures 2026-06-13 13:25:57 -05:00
阿鱼 cc1317dd06 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 93.4% (8845 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-13 07:46:03 +00:00
RedGenesectNinja a3c5a6c377 Implemented Yamask fixes 2026-06-09 21:58:33 -05:00
Watuneni iteite abb5346305 Translated using Weblate (Japanese)
Currently translated at 96.5% (9139 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-06-09 15:19:33 +00:00
Watuneni iteite 9466f66721 Translated using Weblate (Japanese)
Currently translated at 96.4% (9129 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-06-09 14:13:54 +00:00
Watuneni iteite 716ea55bc7 Translated using Weblate (Japanese)
Currently translated at 96.2% (9115 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-06-09 14:01:57 +00:00
Manrique 15f0ad1821 Translated using Weblate (Spanish)
Currently translated at 97.2% (9211 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/es/
2026-06-08 08:42:34 +00:00
Watuneni iteite a16975c750 Translated using Weblate (Japanese)
Currently translated at 96.1% (9105 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-06-07 15:19:10 +00:00
Watuneni iteite c0e6d87c40 Translated using Weblate (Japanese)
Currently translated at 96.1% (9105 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-06-07 12:39:38 +00:00
Watuneni iteite 91078b917e Translated using Weblate (Japanese)
Currently translated at 96.0% (9090 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-06-07 12:02:11 +00:00
ewwww b7e27fa52a Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-06-05 21:10:08 -04:00
ewwww dc87ffd2a8 luvdisc faint fix 2026-06-05 21:09:49 -04:00
阿鱼 cbeda361d4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 93.3% (8835 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-04 15:12:59 +00:00
阿鱼 079a2fb50d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 93.3% (8835 of 9467 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-06-03 06:11:14 +00:00
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
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
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
JPAKx4 a0979b28d3 Merge tag '1.7.2' into controllify-compat-ui-navigation 2026-01-25 09:58:19 -06:00
Gertimoshka f459c64073 Button Guides for our Screens 2026-01-24 15:02:56 +02:00
Gertimoshka 8a7abb9a11 Starter Selection Controlified 2026-01-22 23:56:47 +02:00
Gertimoshka 7be8beb71e Pokedex GUI Controlify Support + Rendering Layering fixes 2026-01-18 20:47:56 +02:00
Gertimoshka 637274c7ee Battle GUI now has working Controller Support 2026-01-12 19:44:51 +02:00
Gertimoshka 63786357b9 Finally working Summary GUI for contrilify with Visuals 2025-12-31 15:40:55 +02: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
JPAKx4 ba3207d176 Work on a framework to make UI stuff not suck 2025-12-20 12:23:48 -06: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
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
JPAKx4 26b25c8c39 More work on keybinds for Controlify compat 2025-12-07 17:01:04 -06: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
JPAK 32bb340260 Move compat to common for neoforge and fabric support. 2025-12-05 21:36:59 -06:00
JPAKx4 b7bfc6d0a1 Continue work on controlify compat 2025-12-03 07:01:01 -06:00
JPAKx4 d86879095f Begin ControlifyCompat 2025-11-29 17:02:35 -06:00
1122 changed files with 166485 additions and 25189 deletions

No files matched your search

+4
View File
@@ -16,6 +16,7 @@ check_assets_cache:
stage: preprocessing
script:
- echo "Checking if cached hash matches current hash"
- yum install -y findutils
# Calculate the current hash
- find common/src/main/resources/assets common/src/main/resources/data -type f -exec sha256sum {} + | sort -k 2 | sha256sum > current_hash.txt
@@ -148,6 +149,7 @@ build:
dependencies:
- replace_resources
script:
- yum install -y findutils
- export GRADLE_OPTS="-Xmx4G -Xms4G"
- chmod +x ./gradlew
- ./gradlew build
@@ -163,6 +165,7 @@ publish:
rules:
- if: '$CI_PROJECT_PATH == "cable-mc/cobblemon" && $CI_COMMIT_BRANCH == "main"'
script:
- yum install -y findutils
- export GRADLE_OPTS="-Xmx4G -Xms4G"
- chmod +x ./gradlew
- ./gradlew publish
@@ -176,6 +179,7 @@ deploy_artifacts:
dependencies:
- build
script:
- yum install -y findutils
- echo "Collecting artifacts..."
- find neoforge/build/libs/ -type f -name "*.jar" | grep -vE "dev-shadow|sources|javadoc" | xargs -I {} mv {} ./
- find fabric/build/libs/ -type f -name "*.jar" | grep -vE "dev-shadow|sources|javadoc" | xargs -I {} mv {} ./
+148 -26
View File
@@ -1,4 +1,76 @@
# 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 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
- Effect Cleanse: Removes all effects
- Added more seasonings for sinister tea
- Mental Herb - Mental Restoration I for 10 seconds
- White Herb - Negative Effect Cleanse
- Milk - Effect Cleanse
- Moomoo Milk - Effect Cleanse
- 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.
## [1.7.3 (January 31st, 2026)](#1-7-3)
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
### Additions
@@ -21,7 +93,7 @@
- Added Move Dex to Pokédex
- Added the Habitat Block, a spawner block for servers and adventure maps that controls spawning in an area.
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks
- Added intrinsic scaling to all mons (+-5%). Some rendering cases, such as shoulder mounting, revert to 1.0 scaling to preserve animation accuracy.
- Added intrinsic scaling to all Pokémon (+-5%). Some rendering cases, such as shoulder mounting, revert to 1.0 scaling to preserve animation accuracy.
- Added scaling to "baby" Pokémon (smaller (-20%) at level 1 → normally sized at level 10)
- Added conditional seats for riding. Seats can be gated by molang such as "is_alpha".
- Added Alpha Pokémon
@@ -42,9 +114,8 @@
- All ruin structures gilded chest loot tables have been updated to include a guaranteed TM not learned naturally by any Pokémon through their level-up moveset. Each ruin gets its own specific TM, so you can now go hunting ruins to complete your move dex!
- All fossil structures have been transferred over to biome features, allowing for better placement in generation.
- The prehistoric_underwater_fissure & prehistoric_submerged_impact fossil structures now use the biome tag `#cobblemon:is_temperate_ocean`.
- A new Biome tag called `#cobblemon:is_stony_beach` has been added.
- Gimmighoul towers now generate at ground level rather than sinking one block into the ground.
- 1 New shipwreck cove structure called the "magma_shipwreck_cove" has been added.
- A new shipwreck cove structure called the "magma_shipwreck_cove" has been added.
- Shipwreck coves now generate slightly more frequently.
- Added equipment loot tables for shipwreck cove enemies.
- Maps from abandoned fishing boats leading to shipwreck coves now will skip explored chunks, allowing the player to find more shipwreck coves easier.
@@ -57,15 +128,18 @@
- Pokémon cries now play when clicking Pokémon in the summary screen.
- Pressing the Summary key while either the starter selection or summary screen are open now closes those screens
- Ice type Pokémon are now immune to Packed Snow
- Held item visibility can now be controlled by the held_item_visible command argument.
### New rideable Pokémon
- Drampa
- Archeops
- Skarmory
- Crobat
- Goodra
- Hisuian Goodra
- Skarmory
- Milotic
- Archeops
- Goodra
- Goodra (Hisuian Form)
- Drampa
- Duraludon
- Archaludon
### Pokémon Added
#### Generation 3
@@ -162,7 +236,10 @@
- Cofagrigus
- Tyranitar
- Turtonator
- Golett
- Corsola
- Arbok (New Patterns: Angry, Cartoon, Clown, Comedic, Crow, Grin, Ink, Modern, Rain, Scribble, Silence, Sloth, Suspicious, Will)
- ... and Substitute
#### Model changes to the regional bias resource pack:
@@ -171,6 +248,26 @@
- Changed models for every existing regional bias
### Animation updates for the following Pokémon
- Kakuna
- Grimer
- Nidoran♀ (+ New shoulder mount)
- Nidorina
- Nidoran♂ (+ New shoulder mount)
- Sandshrew
- Diglett
- Dugtrio
- Graveler
- Happiny
- Chansey
- Blissey
- Poliwhirl
- Poliwrath
- Zubat
- Vulpix
- Drowzee
- Hypno
- Noctowl
- Skarmory
- Skitty
- Flygon
@@ -202,8 +299,19 @@
- Yamask
- Cofagrigus
- Luvdisc
- Staryu
- Starmie
- Togetic
- Rellor
- Rabsca
- Yungoos
- Gumshoos
- Cottonee
- Whimsicott
- Corsola
#### Added sounds to the following moves
- Absorb
- Aurora Beam
- Double Team
@@ -229,13 +337,8 @@
### Changes
- Stat increase and decrease particles have been changed, with the addition of alpha boost particles.
- Dragonite's mailbag cosmetic can now also be obtained with a chest in the cosmetic slot
### Fixes
- Adjusted Croagunk's walkspeed
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly
- Fixed Litleo not blinking
- Fixed Garchomp T-posing when sleeping while out of battle.
- Adjusted Aerodactyl's walkspeed
- Sharp Beak can now be crafted
- Metal Alloy can now be crafted
- Ability Capsules can now be brewed
@@ -288,25 +391,19 @@
- Hearty Grains can be milled like Farmer's Delight Rice Panicles if both Farmer's Delight and Create are installed
- Big Roots and Revival Herbs can be milled to their respective powders
- Pep-Up Flowers, Medicinal Leeks, and all 70 berries can be milled in Create's Millstone for additional dye drops
- Auspicious Armor and Upgrades can be Haunted using Create's crafting systems
- Auspicious Armour and Upgrades can be Haunted using Create's crafting systems
- Applin Apples can be used to make Honeyed Apples in Create
- Golden Apples can now be crafted using the Applin Apples
- Prismarine Crystals and Amethyst Shards can now be used to craft Pokedexes
- Updated Pokedex Screens item tag to support tagged variants of Redstone and Glowstone Dust
- Sounds for interacting with various Cobblemon blocks now play from the center of the block, rather than the corner
- Updated Pokédex Screens item tag to support tagged variants of Redstone and Glowstone Dust
- Sounds for interacting with various Cobblemon blocks now play from the centre of the block, rather than the corner
- The `/pc` command no longer plays PC activation sounds
- Lowered the volume of Blitzle and Zebstrika's cry
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
- Regional Foods will now prioritize being eaten instead of used for healing Pokémon, shift clicking will use it as healing
- Resolved Z-Fighting on Pokédex Models
- Restored Ninjask's ability to do barrel rolls
- Fixed a crash caused by adding enchanted items to the Resurrection Machine
### Fixes
- Fixed Croagunk's walkspeed
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly
- Fixed Litleo not blinking
- Fixed Omanyte and Omastar incorrectly using water animations on land.
- Fixed Pokédex layout for genderless variations
- Fixed odd stacking behaviour with the poke_bait item.
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
@@ -348,9 +445,30 @@
- Fixed Exploit where you could take out the bait or drop your Pokérod with bait to avoid consuming bait, now the bobber will just disappear if you do so
- Fixed Apricorn and Saccharine boats incorrectly using chest boat models
- Fixed wild and trainer battle AI refusing to select moves with the `adjacentAlly` target type, even when it's their only option
- Fixed Guilded chests being more accessible than intended
- Fixed gilded chests being more accessible than intended
- Fixed fished up Pokémon not being added as "seen" to the Pokedex
- Fixed surprised Gimmighoul encounters not being added as "seen" to the Pokedex
- Fixed a crash involving berry trees that have been world edited.
- Fixed visual desync in multiplayer for players mounting Pokémon outside other players' loaded chunks
- Fixed Pokémon entities leveled by rare candy not getting stronger as Minecraft entities unless recalled.
- Fixed a freeze during game load that occurred when multiple mods tried to load Cobblemon API classes at the same time.
- Fixed random tick crashes from berry trees
- Lowered the volume of Blitzle and Zebstrika's cry
- Regional Foods will now prioritise being eaten instead of used for healing Pokémon, shift clicking will use it as healing
- Resolved Z-Fighting on Pokédex Models
- Restored Ninjask's ability to do barrel rolls
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
- Fixed a crash caused by adding enchanted items to the Resurrection Machine
- Fixed a crash that could occur when starting a PvN battle with only fainted Pokémon.
- Fixed the Remember Riding Camera config not being respected when dismounting and resolved the incompatibility with the Auto Third Person mod
- Fixed Full Heal block not dropping Full Heals
- Fixed status cure items not returning an empty bottle when used
- Fixed Panpour consuming its held item when evolving with a Water Stone
- Fixed Smoked Tail Curry returning a Wooden Bowl when consumed despite it not being included in the recipe
- Fixed `/giveallpokemon` not respecting the min and max parameters
- Fixed the choose starter toast not disappearing when capturing a Pokémon instead of opening the starter selection screen
- Fixed an issue where certain Sinister Tea seasonings were only applying as instant effects and would not apply over time
- Long config option labels now scroll instead of overflowing
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
@@ -361,7 +479,9 @@
- Separated all the functions of MoLangFunctions.kt into their own files.
- Spawn buckets and their base weights are now provided by the spawner, with different base weights for world, fishing, snack, and habitat spawn buckets.
- Removed "SpawnBucket" class in favour of just storing maps of bucket names to weights.
- Fixed nicknames not being applied to Pokémon spawned from `spawn_pool_world` files
- Fixed nicknames not being applied to Pokémon spawned from `spawn_pool_world` files.
- Added support for `profilePokedexScale`, `profilePokedexTranslation`, `profileSummaryScale`, and `profileSummaryTranslation` in poser files for finer control over profile displays.
- Fixed riding camera offsets defined on Pokémon forms not being applied
### Molang & Datapacks
- Added `moveset_builders` datapack folder for different styles of movesets.
@@ -372,6 +492,7 @@
- Added `q.item.is_enchanted()`
- Added `q.item.has_enchantment(minecraft:sharpness, 3)`
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
- Added `has_tm_move_unlocked`, `unlock_tm_move`, & `lock_tm_move` as available Molang functions for Players
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for Pokémon
- Added Data Component support for species drops, eg `"components": {"minecraft:custom_model_data": 123}`
- Added the new `sortOrder` field to mark entries, (defaults to 0 if not specified) which controls the order in which marks are rendered on the HUD, with higher numbers being rendered on top of lower numbers
@@ -462,6 +583,7 @@
- German
- Spanish
- Ecuadorian Spanish
- Portuguese
- Brazilian Portuguese
- Simplified Chinese
- Traditional Chinese
+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> {
@@ -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;
@@ -149,6 +151,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);
}
}
@@ -108,6 +108,7 @@ import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.FreezeFrameProperty
import com.cobblemon.mod.common.pokemon.properties.HeldItemVisibleProperty
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.LabelProperty
import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
@@ -350,6 +351,7 @@ object Cobblemon {
CustomPokemonProperty.register(UnaspectPropertyType)
CustomPokemonProperty.register(FreezeFrameProperty)
CustomPokemonProperty.register(NoAIProperty)
CustomPokemonProperty.register(HeldItemVisibleProperty)
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
@@ -405,7 +405,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 64, 7, 0.3f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false)
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 64, 7, 0.3f, false)
@JvmField
@@ -35,6 +35,7 @@ import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SetActiveTMPacketHandler
import com.cobblemon.mod.common.client.net.gui.SetTMMachineContainerDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SynchronizeActiveTMHandler
import com.cobblemon.mod.common.client.net.habitat.OpenHabitatBlockEditorHandler
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
@@ -147,6 +148,7 @@ import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SetActiveTMPacket
import com.cobblemon.mod.common.net.messages.client.ui.SetTMMachineContainerDataPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SynchronizeActiveTMPacket
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
@@ -375,6 +377,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PlayerInteractOptionsPacket.ID, PlayerInteractOptionsPacket::decode, PlayerInteractOptionsHandler))
list.add(PacketRegisterInfo(PokedexUIPacket.ID, PokedexUIPacket::decode, PokedexUIPacketHandler))
list.add(PacketRegisterInfo(ExpGainedDataPacket.ID, ExpGainedDataPacket::decode, ExpGainedDataPacketHandler))
list.add(PacketRegisterInfo(SynchronizeActiveTMPacket.ID, SynchronizeActiveTMPacket::decode, SynchronizeActiveTMHandler))
// Starter packets
list.add(PacketRegisterInfo(OpenStarterUIPacket.ID, OpenStarterUIPacket::decode, StarterUIPacketHandler))
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.cooking.effects
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.Entity
@@ -18,5 +19,13 @@ class CleanseAllEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF) {
override fun applyInstantenousEffect(source: Entity?, indirect: Entity?, target: LivingEntity, amplifier: Int, proximity: Double) {
target.removeAllEffects()
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
override fun applyEffectTick(livingEntity: LivingEntity, amplifier: Int): Boolean {
if (livingEntity is ServerPlayer){
livingEntity.removeAllEffects()
}
return true
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = true
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.cooking.effects
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.Entity
@@ -23,5 +24,18 @@ class CleanseNegativeEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF)
}
}
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
override fun applyEffectTick(livingEntity: LivingEntity, amplifier: Int): Boolean {
if (livingEntity is ServerPlayer){
val snapshot = livingEntity.activeEffects.toList()
for (ei in snapshot) {
if (ei.effect.value().category == MobEffectCategory.HARMFUL) {
livingEntity.removeEffect(ei.effect)
}
}
}
return true
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = true
}
@@ -69,7 +69,7 @@ class HabitatPool {
pool.spawns = spawns
return pool
} else {
return HabitatPools.habitatPoolsById[poolId] ?: HabitatPool.default()
return HabitatPools.habitatPoolsById[poolId] ?: default()
}
}
}
@@ -94,6 +94,14 @@ class HabitatPool {
return spawns.mapNotNull { it.createSpawnDetail(habitatBlockEntity) }
}
fun getPhaseCount(): Int {
return spawns
.flatMap { it.phases?.ranges?.flatten() ?: emptySet() }
.toSet()
.count()
.coerceAtLeast(1)
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(id)
buffer.writeUtf(name)
@@ -17,7 +17,6 @@ import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
class HabitatSettingsDTO() {
var numberOfPhases = 1
var phaseOrder = HabitatPhaseOrder.SIMPLE
lateinit var levelRange: IntRange
var modifiers: String = ""
@@ -28,7 +27,6 @@ class HabitatSettingsDTO() {
var naturalSettings: NaturalHabitatSettingsDTO? = null
constructor(habitatBlockEntity: HabitatBlockEntity) : this() {
this.numberOfPhases = habitatBlockEntity.numberOfPhases
this.phaseOrder = habitatBlockEntity.phaseOrder
this.levelRange = habitatBlockEntity.levelRange
this.modifiers = habitatBlockEntity.modifiers.asString()
@@ -44,7 +42,6 @@ class HabitatSettingsDTO() {
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeInt(numberOfPhases)
buffer.writeEnum(phaseOrder)
buffer.writeInt(levelRange.first)
buffer.writeInt(levelRange.last)
@@ -60,7 +57,6 @@ class HabitatSettingsDTO() {
}
fun decode(buffer: RegistryFriendlyByteBuf) {
this.numberOfPhases = buffer.readInt()
this.phaseOrder = buffer.readEnum(HabitatPhaseOrder::class.java)
val levelRangeStart = buffer.readInt()
val levelRangeEnd = buffer.readInt()
@@ -17,6 +17,8 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.ObjectValue
@@ -44,6 +46,7 @@ import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.pc
import com.cobblemon.mod.common.util.pokedex
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.tmList
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
@@ -312,6 +315,23 @@ object PlayerMoLangFunctions : AbstractMoLangFunctionHolder<Player>() {
}
return@put DoubleValue.ZERO
}
map["has_tm_move_unlocked"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
map["unlock_tm_move"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
player.tmList().syncTMFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
map["lock_tm_move"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
player.tmList().syncTMLockFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 0.0 else 1.0)
}
map["start_battle"] = put@{ params ->
val opponentValue = params.get<MoValue>(0)
val opponent = if (opponentValue is ObjectValue<*>) {
@@ -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(
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.tms
import com.cobblemon.mod.common.tms.obtain.DefaultObtainMethod
import com.cobblemon.mod.common.tms.obtain.ImpossibleObtainMethod
import com.cobblemon.mod.common.tms.obtain.PlayerHasAdvancementObtainMethod
import com.cobblemon.mod.common.tms.obtain.PlayerYObtainMethod
import com.cobblemon.mod.common.tms.obtain.UnlockableObtainMethod
@@ -36,6 +37,7 @@ interface ObtainMethod {
val variant = buffer.readUtf()
return when (variant) {
"cobblemon:unlockable" -> UnlockableObtainMethod()
"cobblemon:impossible" -> ImpossibleObtainMethod()
"cobblemon:default" -> DefaultObtainMethod()
"cobblemon:advancement" -> {
val advancement = buffer.readResourceLocation()
@@ -14,6 +14,8 @@ import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.CountableContext
import com.cobblemon.mod.common.advancement.criterion.LearnAllTMContext
import com.cobblemon.mod.common.advancement.criterion.LearnTMContext
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
@@ -50,6 +52,11 @@ class TMMoveManager(
learn(listOf(tmId))
}
fun syncTMLockFromMove(moveTemplate: MoveTemplate) {
val tmId = TechnicalMachines.moveToTM[moveTemplate]?.id ?: return
unlearn(listOf(tmId))
}
fun getLearnableTMsFromPokemon(pokemon: Pokemon): Collection<ResourceLocation> {
val learnableMoves = pokemon.allAccessibleMoves.toMutableSet()
learnableMoves.addAll(pokemon.moveSet.map { it.template })
@@ -42,10 +42,12 @@ class ElementalType(
companion object {
@JvmStatic
val BY_STRING_CODEC: Codec<ElementalType> = CodecUtils.createByStringCodec(
ElementalTypes::get,
{it.showdownId}
) { id -> "No ElementalType for ID $id" }
val BY_STRING_CODEC: Codec<ElementalType> by lazy {
CodecUtils.createByStringCodec(
ElementalTypes::get,
{ it.showdownId }
) { id -> "No ElementalType for ID $id" }
}
}
}
}
@@ -349,11 +349,12 @@ object BattleBuilder {
npcPartyStores.add(npcParty)
}
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
val playerAvailablePokemon = playerActor.pokemonList.count { it.health > 0 }
if (playerAvailablePokemon < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.pokemonList.size
hadCount = playerAvailablePokemon
)
}
@@ -163,7 +163,8 @@ class TrackerActor(val uuid: UUID) {
//we assume that AI knows it all about our allies
trackerActor.party.addAll(actor.pokemonList.map { createAllied(it) })
actor.activePokemon.forEach {
val active = trackerActor.party.first { trackMon -> trackMon.id == it.battlePokemon!!.uuid }
val battlePokemon = it.battlePokemon ?: return@forEach
val active = trackerActor.party.first { trackMon -> trackMon.id == battlePokemon.uuid }
trackerActor.party.remove(active)
trackerActor.activePokemon.add(active)
}
@@ -176,7 +177,8 @@ class TrackerActor(val uuid: UUID) {
//only know basics of what we see right now
trackerActor.party.addAll(actor.pokemonList.map { createOpposing(it) })
actor.activePokemon.forEach {
val active = trackerActor.party.first { trackMon -> trackMon.id == it.battlePokemon!!.uuid }
val battlePokemon = it.battlePokemon ?: return@forEach
val active = trackerActor.party.first { trackMon -> trackMon.id == battlePokemon.uuid }
trackerActor.party.remove(active)
trackerActor.activePokemon.add(active)
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.block
//import dev.lambdaurora.lambdynlights.util.SodiumDynamicLightHandler.pos
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.berry.Berry
@@ -40,7 +39,11 @@ import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.BonemealableBlock
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
@@ -70,7 +73,7 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
override fun playerWillDestroy(world: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
if (!player.isCreative && state.getValue(AGE) == FRUIT_AGE) {
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
treeEntity.harvest(world, state, pos, player).forEach { drop -> Block.popResource(world, pos, drop) }
treeEntity.harvest(world, state, pos, player).forEach { drop -> popResource(world, pos, drop) }
}
return super.playerWillDestroy(world, pos, state, player)
}
@@ -92,7 +95,7 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
// Random Tick handler: uses time compensation to ensure correct growth speed
@Deprecated("Deprecated in Java")
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity? ?: return
// processTick calculates elapsed time and updates timer, returns whether growth is needed
if (blockEntity.processTick(world, pos, state) && !world.getBlockTicks().hasScheduledTick(pos, this)) {
@@ -128,7 +131,10 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
val newAge = curAge + 1
if (newAge > FRUIT_AGE) return
val newState = state.setValue(AGE, newAge)
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
val treeEntity = world.getBlockEntity(pos) as? BerryBlockEntity? ?: run {
world.setBlock(pos, Blocks.AIR.defaultBlockState(), UPDATE_CLIENTS)
return
}
if (curAge == MATURE_AGE) {
treeEntity.generateGrowthPoints(world, newState, pos, null)
determineMutation(world, random, pos, newState)
@@ -146,7 +152,7 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
for (direction in this.lookupDirections) {
val redirectedPos = pos.offset(direction.normal)
val redirectedState = world.getBlockState(redirectedPos)
val berryBlock = redirectedState.block as? BerryBlock ?: continue
val berryBlock = redirectedState.block as? BerryBlock? ?: continue
val berry = berryBlock.berry() ?: continue
val mutation = this.berry()?.mutationWith(berry) ?: continue
mutations += mutation
@@ -160,7 +166,7 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
treeEntity.decrementMulchDuration(world, pos, state)
}
val mutation = if (random.nextInt(1000) < mutateChance) mutations.random() else null
(world.getBlockEntity(pos) as? BerryBlockEntity)?.let { blockEntity ->
(world.getBlockEntity(pos) as? BerryBlockEntity?)?.let { blockEntity ->
CobblemonEvents.BERRY_MUTATION_RESULT.post(BerryMutationResultEvent(berry, world, state, pos, berryMutationOffer.mutations, mutation)) { berryMutationResult ->
berryMutationResult.pickedMutation?.let { mutation -> blockEntity.mutate(mutation) }
}
@@ -178,7 +184,7 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
): Boolean {
val underBlockState = world.getBlockState(pos.below())
val validSoil = state.getValue(IS_ROOTED) || underBlockState.`is`(CobblemonBlockTags.BERRY_SOIL)
val treeEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return false
val treeEntity = world.getBlockEntity(pos) as? BerryBlockEntity? ?: return false
return getMulch(treeEntity) == MulchVariant.NONE && state.getValue(AGE) < FLOWER_AGE && validSoil
}
@@ -353,7 +359,7 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
fun getMulch(entity: BerryBlockEntity) = entity.mulchVariant
fun convertMulchToEntity(world: ServerLevel, state: BlockState, pos: BlockPos) {
val entity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
val entity = world.getBlockEntity(pos) as? BerryBlockEntity? ?: return
if (state.getValue(MULCH) != MulchVariant.NONE && state.getValue(MULCH) != entity.mulchVariant) {
entity.mulchVariant = state.getValue(MULCH)
world.setBlockAndUpdate(pos, state.setValue(MULCH, MulchVariant.NONE))
@@ -370,9 +376,9 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
}
private fun harvestBerry(world: Level, state: BlockState, pos: BlockPos, player: Player? = null): Boolean {
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return false
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity? ?: return false
blockEntity.harvest(world, state, pos, player).forEach { drop ->
Block.popResource(world, pos, drop)
popResource(world, pos, drop)
}
val sound = if (player != null) CobblemonSounds.BERRY_HARVEST else SoundEvents.SHEEP_SHEAR
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
import com.cobblemon.mod.common.api.multiblock.MultiblockStructure
import com.cobblemon.mod.common.api.multiblock.builder.MultiblockStructureBuilder
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.block.MonitorBlock
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
@@ -307,8 +306,7 @@ open class FossilMultiblockEntity(
private fun unlockTmForPlayer(player: ServerPlayer, stack: ItemStack) {
if (stack.item !is TechnicalMachineItem) return
val move = TMMoveComponent.getTMMove(stack) ?: return
val tmId = TechnicalMachines.moveToTM[move]?.id ?: return
player.tmList()?.learn(listOf(tmId))
player.tmList().syncTMFromMove(move)
}
private fun updateMonitorScreen() {
@@ -25,6 +25,7 @@ import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.entity.CommandBlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
@@ -80,11 +81,13 @@ class HabitatBlock(properties: Properties) : Block(properties), EntityBlock {
}
override fun useWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hitResult: BlockHitResult): InteractionResult {
if (!level.isClientSide) {
val be = level.getBlockEntity(pos) as? HabitatBlockEntity ?: return InteractionResult.PASS
return be.onUse(player, hitResult)
val blockEntity = level.getBlockEntity(pos)
val be = level.getBlockEntity(pos) as? HabitatBlockEntity ?: return InteractionResult.PASS
return if (blockEntity is HabitatBlockEntity && player.canUseGameMasterBlocks()) {
be.onUse(player, hitResult)
} else {
InteractionResult.PASS
}
return InteractionResult.SUCCESS
}
override fun getDrops(state: BlockState, params: LootParams.Builder): MutableList<ItemStack> {
@@ -60,10 +60,12 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
var spawningStyle: HabitatSpawningStyle = ActivatedHabitatSpawning(this)
var numberOfPhases: Int = 1
var phaseOrder = HabitatPhaseOrder.SIMPLE
var currentPhase = 1
val numberOfPhases: Int
get() = pool.getPhaseCount()
var levelRange: IntRange = 1..Cobblemon.config.maxPokemonLevel
var modifiers: PokemonProperties = PokemonProperties()
var pool: HabitatPool = HabitatPool.default()
@@ -139,7 +141,6 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
val activatedSettings = habitatSettingsDTO.activatedSettings
val naturalSettings = habitatSettingsDTO.naturalSettings
val isActivated = habitatSettingsDTO.isActivatedSpawning
this.numberOfPhases = habitatSettingsDTO.numberOfPhases
this.phaseOrder = habitatSettingsDTO.phaseOrder
this.levelRange = habitatSettingsDTO.levelRange
this.modifiers = PokemonProperties.parse(habitatSettingsDTO.modifiers)
@@ -169,7 +170,6 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
override fun saveAdditional(tag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.saveAdditional(tag, registryLookup)
tag.putString(DataKeys.HABITAT_MIMIC, mimicId.toString())
tag.putInt(DataKeys.HABITAT_PHASE_COUNT, numberOfPhases)
tag.putString(DataKeys.HABITAT_PHASE_ORDER, phaseOrder.toString())
// Only save the level range when it's a non-trivial one
if (levelRange.first != 1 || levelRange.last != Cobblemon.config.maxPokemonLevel) {
@@ -201,7 +201,6 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
return // Client doesn't need to try loading the rest
}
numberOfPhases = tag.getInt(DataKeys.HABITAT_PHASE_COUNT)
phaseOrder = HabitatPhaseOrder.valueOf(tag.getString(DataKeys.HABITAT_PHASE_ORDER))
levelRange = tag.getString(DataKeys.HABITAT_LEVEL_RANGE).takeIf { it.isNotBlank() }?.let(IntRangeAdapter::deserialize) ?: 1..Cobblemon.config.maxPokemonLevel
modifiers = tag.getString(DataKeys.HABITAT_MODIFIERS).takeIf { it.isNotBlank() }?.let(PokemonProperties::parse) ?: PokemonProperties()
@@ -10,11 +10,13 @@ package com.cobblemon.mod.common.block.tmmachine
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMenuType
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.block.entity.TMMachineBlockEntity
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.server.level.ServerLevel
import com.cobblemon.mod.common.net.messages.client.ui.SynchronizeActiveTMPacket
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.Mth
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
@@ -35,38 +37,20 @@ class TMMachineMenu(menuType: MenuType<*>, syncId: Int) : AbstractContainerMenu(
var inventory: Container? = null
var containerData: ContainerData? = null
var currentlySelectedTM: TechnicalMachine? = null
private var lastSelectedTM: TechnicalMachine? = null
var onSelectedTMUpdate: (TechnicalMachine?) -> Unit = {}
constructor(syncId: Int, playerInventory: Inventory) : this(CobblemonMenuType.TM_MACHINE, syncId) {
this.playerInventory = playerInventory
this.inventory = SimpleContainer(5)
this.containerData = SimpleContainerData(6)
this.tmMachineEntity = (inventory as? TMMachineBlockEntity.TMMachineBlockInventory)?.blockEntity
this.addDataSlots(containerData!!)
inventory?.startOpen(playerInventory.player)
val startX = 15
val startY = 144
val slotWidth = 18
val slotHeight = 18
for (row in 0..2) {
for (col in 0..8) {
this.addSlot(Slot(playerInventory, 9 + (row * 9) + col, startX + (slotWidth * col), startY + (slotHeight * row)))
}
}
for (col in 0..8) {
this.addSlot(Slot(playerInventory, col, startX + (slotWidth * col), startY + 58))
}
val container = inventory
container?.let {
this.addSlot(TMResultSlot(container, RESULT_SLOT, startX + 37, startY - 28)) // Output slot
this.addSlot(BlankTMSlot(container, BLANK_TM_SLOT, startX + 90, startY - 28)) // Blank TM
for (index in INGREDIENT_SLOTS) {
this.addSlot(Slot(container, index, startX + 114 + (18 * (index - INGREDIENT_SLOTS.first)), startY - 28))
}
}
this.addSlotListener(this)
initiateSlots()
}
constructor(syncId: Int, playerInventory: Inventory, inventory: Container, containerData: ContainerData) : this(CobblemonMenuType.TM_MACHINE, syncId) {
@@ -78,6 +62,20 @@ class TMMachineMenu(menuType: MenuType<*>, syncId: Int) : AbstractContainerMenu(
this.addDataSlots(containerData)
inventory.startOpen(playerInventory.player)
initiateSlots()
tmMachineEntity?.let {
val move = Moves.getByName(it.activeMove)
move.let {
currentlySelectedTM = TechnicalMachines.moveToTM[it]
synchronizeSelectedTM()
}
}
this.addSlotListener(this)
}
private fun initiateSlots() {
val startX = 15
val startY = 144
val slotWidth = 18
@@ -92,7 +90,7 @@ class TMMachineMenu(menuType: MenuType<*>, syncId: Int) : AbstractContainerMenu(
this.addSlot(Slot(playerInventory, col, startX + (slotWidth * col), startY + 58))
}
val container = inventory
val container = inventory!!
container.let {
this.addSlot(TMResultSlot(container, RESULT_SLOT, startX + 37, startY - 28)) // Output slot
this.addSlot(BlankTMSlot(container, BLANK_TM_SLOT, startX + 90, startY - 28)) // Blank TM
@@ -100,7 +98,6 @@ class TMMachineMenu(menuType: MenuType<*>, syncId: Int) : AbstractContainerMenu(
this.addSlot(Slot(container, index, startX + 114 + (18 * (index - INGREDIENT_SLOTS.first)), startY - 28))
}
}
this.addSlotListener(this)
}
override fun quickMoveStack(player: Player, index: Int): ItemStack {
@@ -207,4 +204,25 @@ class TMMachineMenu(menuType: MenuType<*>, syncId: Int) : AbstractContainerMenu(
override fun dataChanged(containerMenu: AbstractContainerMenu, dataSlotIndex: Int, value: Int) {
broadcastChanges()
}
fun synchronizeSelectedTM() {
val serverPlayer = playerInventory?.player as? ServerPlayer ?: return
serverPlayer.sendPacket(SynchronizeActiveTMPacket(currentlySelectedTM))
lastSelectedTM = currentlySelectedTM
}
override fun sendAllDataToRemote() {
super.sendAllDataToRemote()
synchronizeSelectedTM()
}
override fun broadcastChanges() {
super.broadcastChanges()
if (lastSelectedTM != currentlySelectedTM) synchronizeSelectedTM()
}
fun clientSetSelectedTM(tm: TechnicalMachine?) {
currentlySelectedTM = tm
onSelectedTMUpdate(tm)
}
}
@@ -70,6 +70,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
@@ -79,16 +80,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 {
@@ -109,6 +113,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()
@@ -194,6 +200,8 @@ object CobblemonClient {
ClientPlayerIcon.onTick()
PartyOverlayDataControl.tick(event.client.isPaused)
}
ridingSprintHandler.put(cobblemonResource("normal")) { Minecraft.getInstance().options.keySprint.isDown }
}
private fun registerTooltipManagers() {
@@ -49,7 +49,7 @@ object MountedCameraTypeHandler {
return
}
val currentCameraType = mc.options.cameraType
if (currentCameraType != unmountedCameraType) {
if (Cobblemon.config.rememberRidingCamera && currentCameraType != unmountedCameraType) {
mc.options.cameraType = unmountedCameraType
}
val data = ClientPersistedData.ridingPerspectives
@@ -129,21 +129,30 @@ 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 &&
if (!this.attachedToast &&
CobblemonClient.clientPlayerData.promptStarter &&
!CobblemonClient.clientPlayerData.starterLocked &&
!CobblemonClient.clientPlayerData.starterSelected &&
!CobblemonClient.checkedStarterScreen
) {
if (!this.attachedToast) {
// Adding starter toast on start and connect of client to server.
minecraft.toasts.addToast(this.starterToast)
this.attachedToast = true
}
// Adding starter toast on start and connect of client to server.
minecraft.toasts.addToast(this.starterToast)
this.attachedToast = true
}
return // Don't render the stuff below if no Pokémon in party
}
// Without this simply capturing a Pokémon instead of opening the starter screen won't dismiss the toast
if (!CobblemonClient.checkedStarterScreen) {
CobblemonClient.checkedStarterScreen = true
starterToast.nextVisibility = Toast.Visibility.HIDE
}
val totalHeight = party.slots.size * SLOT_HEIGHT
val midY = minecraft.window.guiScaledHeight / 2
val startY = (midY - totalHeight / 2) - ((SLOT_SPACING * 5) / 2)
@@ -177,10 +186,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) {
@@ -552,5 +562,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
}
}
}
matrices.popPose()
}
}
@@ -27,10 +27,18 @@ import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.atan
enum class ProfileTransformType {
PROFILE,
NONE,
POKEDEX,
SUMMARY,
}
fun drawProfilePokemon(
renderablePokemon: RenderablePokemon,
matrixStack: PoseStack,
@@ -39,7 +47,7 @@ fun drawProfilePokemon(
state: PosableState,
partialTicks: Float,
scale: Float = 20F,
applyProfileTransform: Boolean = true,
profileTransformType: ProfileTransformType = ProfileTransformType.PROFILE,
applyBaseScale: Boolean = false,
r: Float = 1F,
g: Float = 1F,
@@ -56,7 +64,7 @@ fun drawProfilePokemon(
state = state.also { it.currentAspects = renderablePokemon.aspects },
partialTicks = partialTicks,
scale = scale,
applyProfileTransform = applyProfileTransform,
profileTransformType = profileTransformType,
applyBaseScale = applyBaseScale,
r = r,
g = g,
@@ -75,7 +83,7 @@ fun drawProfilePokemon(
state: PosableState,
partialTicks: Float,
scale: Float = 20F,
applyProfileTransform: Boolean = true,
profileTransformType: ProfileTransformType = ProfileTransformType.PROFILE,
applyBaseScale: Boolean = false,
doQuirks: Boolean = false,
r: Float = 1F,
@@ -114,18 +122,38 @@ fun drawProfilePokemon(
state.setPoseToFirstSuitable(poseType)
state.updatePartialTicks(partialTicks)
if (applyProfileTransform) {
matrixStack.translate(
model.profileTranslation.x,
model.profileTranslation.y + 1.5 * model.profileScale,
model.profileTranslation.z - 4.0
)
matrixStack.scale(model.profileScale, model.profileScale, 1 / model.profileScale)} else {
if (profileTransformType == ProfileTransformType.NONE) {
matrixStack.translate(0F, 0F, -4.0F)
if (applyBaseScale) {
matrixStack.scale(baseScale, baseScale, 1 / baseScale)
}
} else {
var profileScale: Float
var profileTranslation: Vec3
when (profileTransformType) {
ProfileTransformType.POKEDEX -> {
profileScale = model.profilePokedexScale ?: model.profileScale
profileTranslation = model.profilePokedexTranslation ?: model.profileTranslation
}
ProfileTransformType.SUMMARY -> {
profileScale = model.profileSummaryScale ?: model.profileScale
profileTranslation = model.profileSummaryTranslation ?: model.profileTranslation
}
else -> {
profileScale = model.profileScale
profileTranslation = model.profileTranslation
}
}
matrixStack.translate(
profileTranslation.x,
profileTranslation.y + 1.5 * profileScale,
profileTranslation.z - 4.0
)
matrixStack.scale(profileScale, profileScale, 1 / profileScale)
}
matrixStack.mulPose(rotation)
model.applyAnimations(null, state, 0F, 0F, 0F, headYaw, headPitch)
@@ -0,0 +1,97 @@
/*
* 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.client.gui
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
fun handleProfileDebugKeys(keyCode: Int, poserResourceLocation: ResourceLocation, state: FloatingState) {
if (!Cobblemon.config.enableDebugKeys) {
return
}
val model = VaryingModelRepository.getPoser(poserResourceLocation, state)
val delta = when (keyCode) {
InputConstants.KEY_UP -> Vec3(0.0, -0.01, 0.0)
InputConstants.KEY_DOWN -> Vec3(0.0, 0.01, 0.0)
InputConstants.KEY_LEFT -> Vec3(-0.01, 0.0, 0.0)
InputConstants.KEY_RIGHT -> Vec3(0.01, 0.0, 0.0)
else -> null
}
if (delta != null) {
model.profileTranslation = model.profileTranslation.add(delta)
model.profileSummaryTranslation = model.profileSummaryTranslation?.add(delta)
model.profilePokedexTranslation = model.profilePokedexTranslation?.add(delta)
}
val scaleDelta = when (keyCode) {
InputConstants.KEY_EQUALS -> 0.01F
InputConstants.KEY_MINUS -> -0.01F
else -> null
}
if (scaleDelta != null) {
model.profileScale += scaleDelta
model.profileSummaryScale = model.profileSummaryScale?.plus(scaleDelta)
model.profilePokedexScale = model.profilePokedexScale?.plus(scaleDelta)
}
}
fun logProfileTranslations(poserResourceLocation: ResourceLocation, state: FloatingState) {
if (!Cobblemon.config.enableDebugKeys) {
return
}
val model = VaryingModelRepository.getPoser(poserResourceLocation, state)
val player = Minecraft.getInstance().player
fun Double.f2() = "%.2f".format(this)
fun Float.f2() = "%.2f".format(this)
fun sendTranslation(name: String, translation: Vec3) {
player?.sendSystemMessage(Component.literal("$name Translation: (${translation.x.f2()}, ${translation.y.f2()}, ${translation.z.f2()})"))
}
fun sendScale(name: String, scale: Float) {
player?.sendSystemMessage(Component.literal("$name Scale: ${scale.f2()}"))
}
fun logTranslation(name: String, translation: Vec3) {
Cobblemon.LOGGER.info("override var ${name.replaceFirstChar(Char::lowercase)}Translation = Vec3(${translation.x.f2()}, ${translation.y.f2()}, ${translation.z.f2()})")
}
fun logScale(name: String, scale: Float) {
Cobblemon.LOGGER.info("override var ${name.replaceFirstChar(Char::lowercase)}Scale = ${scale.f2()}F")
}
sendTranslation("Profile", model.profileTranslation)
sendScale("Profile", model.profileScale)
model.profileSummaryTranslation?.let { sendTranslation("Summary Profile", it) }
model.profileSummaryScale?.let { sendScale("Summary Profile", it) }
model.profilePokedexTranslation?.let { sendTranslation("Pokedex Profile", it) }
model.profilePokedexScale?.let { sendScale("Pokedex Profile", it) }
logTranslation("Profile", model.profileTranslation)
logScale("Profile", model.profileScale)
model.profileSummaryTranslation?.let { logTranslation("ProfileSummary", it) }
model.profileSummaryScale?.let { logScale("ProfileSummary", it) }
model.profilePokedexTranslation?.let { logTranslation("ProfilePokedex", it) }
model.profilePokedexScale?.let { logScale("ProfilePokedex", it) }
}
@@ -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
}
@@ -68,25 +68,23 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
}
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
var stackHeight = -scrollAmount.toInt()
guiGraphics.enableScissor(x, y, x + width, y + height)
children.forEachIndexed { index, entry ->
if (stackHeight in 1..<height || stackHeight + entry.height in 1..<height) {
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
val yTop = getRowTop(entry)
if (yTop + entry.height > y || yTop < y + height) {
entry.render(guiGraphics, index, x, yTop, mouseX, mouseY, partialTick)
}
stackHeight += entry.height
}
guiGraphics.disableScissor()
}
fun getEntryAtPosition(mouseX: Int, mouseY: Int): Entry? {
if (mouseX in x..(x + width) && mouseY in y..(y + height)) {
var stackHeight = -scrollAmount
children.forEach {
if (mouseY.toDouble() in (y + stackHeight)..(y + stackHeight + it.height)) {
val yTop = getRowTop(it)
if (mouseY in (yTop)..(yTop + it.height)) {
return it
}
stackHeight += it.height
}
}
return null
@@ -94,10 +92,16 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
fun ensureVisible(index: Int) {
val entry = children[index]
if (entry.scrolledY - scrollAmount < 0) {
ensureVisible(entry)
}
open fun ensureVisible(entry: Entry) {
if (entry !in children) return
val yTop = entry.scrolledY - scrollAmount.toInt()
if (yTop < 0) {
setScroll(entry.scrolledY.toDouble())
} else if (entry.scrolledY + entry.height > height) {
setScroll(entry.scrolledY.toDouble() + entry.height)
} else if (yTop + entry.height > height) {
setScroll((entry.scrolledY + entry.height - height).toDouble())
}
}
@@ -121,14 +125,12 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
}
fun getRowTop(entry: Entry): Int {
var stackHeight = -scrollAmount.toInt()
children.forEach {
if (it == entry) {
return y + stackHeight
}
stackHeight += it.height
}
return y
if (entry !in children) return -1
return y + entry.scrolledY - scrollAmount.toInt()
}
fun getLeft(): Int {
return x
}
override fun defaultButtonNarrationText(pNarrationElementOutput: NarrationElementOutput) {}
@@ -136,6 +138,7 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
abstract inner class Entry(var height: Int): GuiEventListener {
var scrolledY: Int = 0
private var widgetFocused = false
override fun setFocused(focused: Boolean) {
@@ -145,6 +148,14 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
override fun isFocused(): Boolean = widgetFocused
abstract fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float)
fun getRowTop(): Int {
return getRowTop(this)
}
fun getRowLeft(): Int {
return getLeft()
}
}
inner class EmptySeparator(height: Int): Entry(height) {
@@ -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
@@ -114,6 +115,10 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
override val schedulingTracker = SchedulingTracker()
override fun render(context: GuiGraphics, tickCounter: DeltaTracker) {
val matrixStack = context.pose()
matrixStack.pushPose()
matrixStack.translate(0.0, 0.0, -50.0) // Push back so controlify gui renders infront
val tickDelta = tickCounter.realtimeDeltaTicks.takeIf { !Minecraft.getInstance()!!.isPaused } ?: 0F
schedulingTracker.update(tickDelta / 20F)
passedSeconds += tickDelta / 20
@@ -142,9 +147,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,
@@ -160,6 +170,8 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
messagePane.opacity = 0.3F
messagePane.render(context, 0, 0, 0F)
}
matrixStack.popPose()
}
fun drawTile(context: GuiGraphics, tickDelta: Float, activeBattlePokemon: ActiveClientBattlePokemon, left: Boolean, rank: Int, dexState: PokedexEntryProgress, hasCommand: Boolean = false, isHovered: Boolean = false, isCompact: Boolean = false) {
@@ -297,10 +309,11 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
(y + portraitDiameter + portraitOffsetY).toInt(),
)
matrixStack.pushPose()
matrixStack.scale(1F, 1F, 0.2F)
matrixStack.translate(
portraitStartX + portraitDiameter / 2.0,
y.toDouble() + portraitOffsetY - if (isCompact) 15.0 else 5.0,
0.0
-50.0
)
if (ballState != null && ballState.currentPose != "shut") {
@@ -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)
@@ -15,6 +15,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
@@ -41,6 +42,7 @@ class BattleGeneralActionSelection(
val lastAnwseredRequest = CobblemonClient.battle?.getLastAnsweredRequest()
val tiles = mutableListOf<BattleOptionTile>()
init {
var rank = 0
@@ -76,22 +78,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
@@ -68,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 }
@@ -92,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> {
@@ -154,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
}
@@ -190,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
@@ -229,7 +265,7 @@ 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,
)
@@ -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
@@ -41,6 +48,7 @@ class CobblemonConfigScreen(
const val WIDGET_HEIGHT = SLOT_HEIGHT - PADDING
const val LABEL_Y_OFFSET = 4
const val WIDGET_Y_OFFSET = 0
const val LABEL_MAX_WIDTH = SLOT_WIDTH - RESET_SLOT_WIDTH - WIDGET_WIDTH - PADDING * 2
}
val clonedConfig = Cobblemon.config.clone()
@@ -54,6 +62,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 +112,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(
@@ -8,17 +8,18 @@
package com.cobblemon.mod.common.client.gui.config.variable
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.LABEL_Y_OFFSET
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.LABEL_MAX_WIDTH
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.RESET_SLOT_WIDTH
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.SLOT_WIDTH
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.WIDGET_HEIGHT
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.WIDGET_Y_OFFSET
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigVariableList
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigVariableList.CobblemonConfigVariableListEntry
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScrollingText
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.config.CobblemonConfigField
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.Tooltip
@@ -91,12 +92,15 @@ abstract class CobblemonConfigVariable(
val x = getX(left)
val y = getY(top)
drawScaledText(
context = context,
text = labelLang,
x = getX(left),
y = getY(top) + LABEL_Y_OFFSET,
shadow = true
drawScrollingText(
context,
Minecraft.getInstance().font,
labelLang,
x,
y + WIDGET_Y_OFFSET,
x + LABEL_MAX_WIDTH,
y + WIDGET_Y_OFFSET + WIDGET_HEIGHT,
-1
)
resetButton.x = x + SLOT_WIDTH - RESET_SLOT_WIDTH
@@ -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
@@ -160,10 +160,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) {
@@ -177,8 +184,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
@@ -115,35 +115,19 @@ class HabitatEditGUI(
addOptionWidget(mimicInput)
y += spacing
labels.add(lang("ui.edit.habitat.phase_number") to (10 to y))
y += 12
val phasesInput = EditBox(font, 10, y, buttonWidth, 20, "Phases".text())
phasesInput.value = habitatSettingsDTO.numberOfPhases.toString()
phasesInput.setResponder { value ->
val phases = value.toIntOrNull()
val valid = phases != null && phases > 0
if (valid) {
habitatSettingsDTO.numberOfPhases = phases
}
phasesInput.setTextColor(if (valid) validTextColor else invalidTextColor)
setFieldValidity("phases", valid, "ui.edit.habitat.validation.phases")
// Phase setting only visible if there's more than one phase
val phaseCount = habitatSettingsDTO.pool.toHabitatPool()?.getPhaseCount() ?: 1
if (phaseCount > 1) {
phaseOrderButton = addOptionWidget(
Button.builder(getPhaseOrderText()) {
val orders = HabitatPhaseOrder.entries
val currentIndex = orders.indexOf(habitatSettingsDTO.phaseOrder)
habitatSettingsDTO.phaseOrder = orders[(currentIndex + 1) % orders.size]
refresh()
}.bounds(10, y, buttonWidth, 20).build()
)
y += spacing
}
val initialPhases = phasesInput.value.toIntOrNull()
val initialPhasesValid = initialPhases != null && initialPhases > 0
phasesInput.setTextColor(if (initialPhasesValid) validTextColor else invalidTextColor)
setFieldValidity("phases", initialPhasesValid, "ui.edit.habitat.validation.phases")
addOptionWidget(phasesInput)
y += spacing
phaseOrderButton = addOptionWidget(
Button.builder(getPhaseOrderText()) {
val orders = HabitatPhaseOrder.entries
val currentIndex = orders.indexOf(habitatSettingsDTO.phaseOrder)
habitatSettingsDTO.phaseOrder = orders[(currentIndex + 1) % orders.size]
refresh()
}.bounds(10, y, buttonWidth, 20).build()
)
y += spacing
labels.add(lang("ui.edit.habitat.level_range") to (10 to y))
y += 12
@@ -390,7 +374,10 @@ class HabitatEditGUI(
}
fun refresh() {
phaseOrderButton.message = getPhaseOrderText()
val phaseCount = habitatSettingsDTO.pool.toHabitatPool()?.getPhaseCount() ?: 1
if (phaseCount > 1 && ::phaseOrderButton.isInitialized) {
phaseOrderButton.message = getPhaseOrderText()
}
spawnModeButton.message = getSpawnModeText()
poolSelectorButton.message = getPoolText()
viewPoolButton.message = getViewPoolText()
@@ -439,6 +426,7 @@ class HabitatEditGUI(
}
habitatSettingsDTO.pool = newCustomPool
}
rebuildWidgets()
return
}
@@ -451,6 +439,7 @@ class HabitatEditGUI(
}
habitatSettingsDTO.pool = selectedPool
rebuildWidgets()
}
private fun validateLevelRangeInputs() {
@@ -45,7 +45,6 @@ class HabitatSpawnEditorGUI(
private val actionButtonBottomMargin = 10
private val validTextColor = 0xFFFFFF
private val invalidTextColor = 0xFF5555
private val moonPhaseRange = 0..7
private lateinit var positionTypeButton: Button
private lateinit var bucketButton: Button
private lateinit var timeOfDayButton: Button
@@ -185,7 +184,7 @@ class HabitatSpawnEditorGUI(
phasesInput.setTextColor(validTextColor)
setFieldValidity("phases_list", true, "ui.edit.habitat.validation.phases_list")
} else {
val parsed = parseMoonPhases(value)
val parsed = parsePhases(value)
val valid = parsed != null
if (valid) {
spawn.phases = parsed
@@ -194,7 +193,7 @@ class HabitatSpawnEditorGUI(
setFieldValidity("phases_list", valid, "ui.edit.habitat.validation.phases_list")
}
}
val initialPhasesValid = phasesInput.value.isBlank() || parseMoonPhases(phasesInput.value) != null
val initialPhasesValid = phasesInput.value.isBlank() || parsePhases(phasesInput.value) != null
phasesInput.setTextColor(if (initialPhasesValid) validTextColor else invalidTextColor)
setFieldValidity("phases_list", initialPhasesValid, "ui.edit.habitat.validation.phases_list")
addOptionWidget(phasesInput)
@@ -332,7 +331,7 @@ class HabitatSpawnEditorGUI(
return runCatching { PokemonSpecies.getByName(lowered) }.getOrNull()
}
private fun parseMoonPhases(raw: String): IntRanges? {
private fun parsePhases(raw: String): IntRanges? {
val ranges = mutableListOf<IntRange>()
val tokens = raw.split(",").map(String::trim)
if (tokens.isEmpty() || tokens.any { it.isEmpty() }) {
@@ -344,13 +343,12 @@ class HabitatSpawnEditorGUI(
val range = when (parts.size) {
1 -> {
val value = parts[0].toIntOrNull() ?: return null
if (value !in moonPhaseRange) return null
value..value
}
2 -> {
val start = parts[0].toIntOrNull() ?: return null
val end = parts[1].toIntOrNull() ?: return null
if (start !in moonPhaseRange || end !in moonPhaseRange || start > end) {
if (start > end) {
return null
}
start..end
@@ -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) {
@@ -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) {
@@ -63,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()
@@ -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? {
@@ -32,9 +32,10 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
return super.keyPressed(keyCode, scanCode, modifiers)
}
private val buttons = mutableListOf<InteractWheelButton>()
val buttons = mutableListOf<InteractWheelButton>()
private var maxPage = 1
private var currentPage = 0
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -86,7 +87,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
@@ -44,12 +49,15 @@ 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,
@@ -57,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
@@ -128,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) {}
@@ -198,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(
@@ -237,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,
@@ -250,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,
@@ -268,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(
@@ -312,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) {
@@ -685,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)
@@ -700,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
@@ -32,6 +33,8 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.handleProfileDebugKeys
import com.cobblemon.mod.common.client.gui.logProfileTranslations
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
@@ -44,6 +47,10 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_MOVES
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.gui.pokedex.widgets.AbilitiesWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DescriptionWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DropsScrollingWidget
@@ -72,6 +79,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
@@ -83,7 +92,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")
@@ -161,6 +170,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) {}
@@ -266,6 +283,222 @@ class PokedexGUI private constructor(
)
addRenderableWidget(categoryFilterButtonDown)
updateFilters(true)
fun hasKnowledge(): Boolean {
val currentEntry = pokemonInfoWidget.currentEntry
return currentEntry != null && CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
}
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.UNREGISTERED
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.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
}
}
}
fun getSufficientlyKnownForms(selectedEntry: PokedexEntry, tabIndex: Int) = if (tabIndex in looseUnlockTabs) {
@@ -410,6 +643,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()
@@ -427,6 +662,11 @@ class PokedexGUI private constructor(
override fun onClose() {
if (blockPos != null) AdjustBlockEntityViewerCountPacket(blockPos, false).sendToServer()
playSound(CobblemonSounds.POKEDEX_CLOSE)
selectedEntry?.speciesId?.let { speciesId ->
logProfileTranslations(speciesId, pokemonInfoWidget.state)
}
super.onClose()
}
@@ -460,6 +700,7 @@ class PokedexGUI private constructor(
if (isDragging && canDragRender) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
pokemonInfoWidget.rotationY = (((pokemonInfoWidget.rotationY + dragOffsetY) % 360 + 360) % 360)
pokemonInfoWidget.lastRotationY = pokemonInfoWidget.rotationY
}
oldDragPosX = mouseX
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
@@ -475,6 +716,18 @@ class PokedexGUI private constructor(
override fun tick() {
if (::pokemonInfoWidget.isInitialized && !pokemonInfoWidget.suppressViewport) pokemonInfoWidget.tick()
//Controller
if (controllerComponentManager.rHorizontalState != 0) {
if (controllerComponentManager.currentSelectedComponent == "pokemon_render") {
pokemonInfoWidget.rotationY = ((pokemonInfoWidget.rotationY + 5F * controllerComponentManager.rHorizontalState) % 360 + 360) % 360
}
}
if (controllerComponentManager.rVerticalState != 0) {
if (controllerComponentManager.currentSelectedComponent == "info_widget") {
(tabInfoElement as? InfoTextScrollWidget)?.scroll(-3 * controllerComponentManager.rVerticalState)
}
}
}
fun updatePokedexRegion(nextIndex: Boolean) {
@@ -534,6 +787,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"]
}
}
}
@@ -757,6 +1049,12 @@ class PokedexGUI private constructor(
return true
}
if (!searchWidget.isFocused) {
selectedEntry?.speciesId?.let { speciesId ->
handleProfileDebugKeys(keyCode, speciesId, pokemonInfoWidget.state)
}
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
@@ -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
@@ -149,6 +149,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,
@@ -190,7 +192,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,
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ProfileTransformType
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
@@ -55,6 +56,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.util.Mth
import org.joml.Quaternionf
import org.joml.Vector3f
import java.io.FileNotFoundException
@@ -122,6 +124,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
var state = FloatingState()
var rotationY = 30F
var lastRotationY = rotationY // Used to smooth out rotation for controllers
var ticksElapsed = 0
var pokeBallBackgroundFrame = 0
@@ -187,7 +190,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,
@@ -199,7 +202,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,
@@ -260,6 +263,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
).apply { addWidget(this) }
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val realDetla = Minecraft.getInstance().timer.getGameTimeDeltaPartialTick(true)
val currentEntry = this.currentEntry ?: return
val hasKnowledge =
@@ -374,10 +379,10 @@ 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
10.0 // Dont use too big numbers otherwise side mod overlays can break (controllify keyboard)
)
matrices.scale(scaleAmount, scaleAmount, scaleAmount)
val rotationVector = Vector3f(13F, rotationY, 0F)
matrices.scale(scaleAmount, scaleAmount, 0.2F)
val rotationVector = Vector3f(13F, Mth.rotLerp(realDetla, lastRotationY, rotationY), 0F)
drawProfilePokemon(
renderablePokemon = renderablePokemon!!,
@@ -386,7 +391,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
partialTicks = delta,
rotation = Quaternionf().fromEulerXYZDegrees(rotationVector),
state = state,
blockLight = 15
blockLight = 15,
profileTransformType = ProfileTransformType.POKEDEX,
)
matrices.popPose()
@@ -405,7 +411,7 @@ 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)
matrices.translate(0.0, 0.0, 20.0)
if (isSelectedPokemonOwned()) {
val primaryType = type[0]
@@ -636,12 +642,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
}
@@ -662,7 +668,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
@@ -754,6 +760,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
val delay = 3
if (ticksElapsed % delay == 0) pokeBallBackgroundFrame++
if (pokeBallBackgroundFrame == 16) pokeBallBackgroundFrame = 0
lastRotationY = rotationY
}
fun isWithinPortraitSpace(mouseX: Double, mouseY: Double): Boolean =
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.pokedex.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ProfileTransformType
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_DESCRIPTION_HEIGHT
@@ -90,7 +91,7 @@ class SizeWidget(val pX: Int, val pY: Int) : SoundlessWidget(
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(0F, 0F, 0F)),
state = FloatingState(),
scale = modelScale,
applyProfileTransform = false,
profileTransformType = ProfileTransformType.NONE,
applyBaseScale = true,
r = 0F,
g = 0F,
@@ -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() {
@@ -25,9 +25,10 @@ import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionScrollbar
import com.cobblemon.mod.common.client.gui.startselection.widgets.CategoryList
import com.cobblemon.mod.common.client.gui.startselection.widgets.SelectionButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.StarterRoundabout
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
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.keybind.keybinds.SummaryBinding
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
@@ -41,8 +42,9 @@ import net.minecraft.client.gui.GuiGraphics
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.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.joml.Vector4i
import net.minecraft.resources.ResourceLocation
import java.io.FileNotFoundException
/**
@@ -51,7 +53,7 @@ import java.io.FileNotFoundException
* @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
private const val BASE_WIDTH = 239
@@ -73,6 +75,14 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
private lateinit var modelWidget: ModelWidget
private lateinit var currentPokemon: RenderablePokemon
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedCategory = 0
var lastSelectedButton = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
var ticksElapsed = 0
var currentBallBackgroundFrame = 0
@@ -151,6 +161,91 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
width = 3,
height = 145
))
controllerComponentManager.componentList["select"] = ControllerComponent().also {
it.associatedGUIElement = selectionButton
it.right = { controllerComponentManager.componentList["pokemon_${lastSelectedCategory}_${lastSelectedButton}"] }
}
categoryList.generatedCategoriesWidgets.forEachIndexed { categoryIndex, category ->
for (buttonIndex in 0..<category.category.pokemon.size) {
val column = buttonIndex % 3
controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex}"] = ControllerComponent().also {
it.associatedGUIElement = category
it.right = {
if (column < 2) controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex+1}"]
else null
}
it.left = {
if (column > 0) controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex-1}"]
else controllerComponentManager.componentList["select"]
}
it.up = {
if (buttonIndex-3 < 0) {
var returnVal: ControllerComponent? = null
var index = categoryIndex
while (index-1 >= 0) {
val lastCategory = categoryList.generatedCategoriesWidgets[index-1]
val row = (lastCategory.category.pokemon.size - 1) / 3 + if ((lastCategory.category.pokemon.size - 1) % 3 < column) -1 else 0
if (row >= 0) {
returnVal = controllerComponentManager.componentList["pokemon_${index-1}_${row*3+column}"]
break
}
index -= 1
}
returnVal
}
else controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex-3}"]
}
it.down = {
if (buttonIndex+3 >= category.category.pokemon.size) {
var returnVal: ControllerComponent? = null
var index = categoryIndex
while (index+1 < categoryList.generatedCategoriesWidgets.size) {
val nextCategory = categoryList.generatedCategoriesWidgets[index+1]
if (nextCategory.category.pokemon.size > column) {
returnVal = controllerComponentManager.componentList["pokemon_${index+1}_${column}"]
break
}
index += 1
}
returnVal
}
else controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex+3}"]
}
it.onPress = {
selectPokemon(category.category, buttonIndex)
playSound(CobblemonSounds.GUI_CLICK)
}
it.onSelect = {
lastSelectedCategory = categoryIndex
lastSelectedButton = buttonIndex
}
it.onFocus = {
category.controllerButtonSelected = buttonIndex
categoryList.ensureVisible(category)
}
it.onUnfocus = {
category.controllerButtonSelected = -1
}
}
}
}
controllerComponentManager.let {
it.focused = controllerComponentManager.componentList["select"]
}
}
override fun getSelectionPosAndSize(): Vector4i? {
if (controllerComponentManager.currentSelectedComponent.startsWith("pokemon_")) {
val category = controllerComponentManager.focused?.associatedGUIElement as? CategoryList.Category ?: return null
val row = category.controllerButtonSelected / 3
val column = category.controllerButtonSelected % 3
val x = category.getRowLeft() + (CategoryList.CONTAINER_BUTTON_SIZE + 2) * column + CategoryList.MARGIN + 3
val y = category.getRowTop() + (CategoryList.CONTAINER_BUTTON_SIZE + CategoryList.BUTTON_SEPERATION) * row + CategoryList.HEADER_HEIGHT + CategoryList.BUTTON_SEPERATION - 1
return Vector4i(x, y, CategoryList.CONTAINER_BUTTON_SIZE + 4, CategoryList.CONTAINER_BUTTON_SIZE + 4)
}
return super.getSelectionPosAndSize()
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -307,6 +402,8 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
matrices.popPose()
super.render(context, mouseX, mouseY, delta)
renderSelection(matrices)
}
fun getPlatformResource(type: ElementalType): ResourceLocation? {
@@ -59,9 +59,12 @@ class CategoryList(
const val CONTAINER_WIDTH = 89
}
val generatedCategoriesWidgets = mutableListOf<Category>()
init {
addEntry(EmptySeparator(MARGIN))
createEntries().forEach {
generatedCategoriesWidgets.add(it)
addEntry(it)
addEntry(EmptySeparator(MARGIN))
}
@@ -73,7 +76,18 @@ class CategoryList(
Category(category, height)
}
inner class Category(private val category: RenderableStarterCategory, height: Int) : Entry(height) {
override fun ensureVisible(entry: Entry) {
if (entry !in children) return
val yTop = entry.scrolledY - scrollAmount.toInt()
if (yTop - MARGIN < 0) {
setScroll((entry.scrolledY - MARGIN).toDouble())
} else if (yTop + entry.height + MARGIN > height) {
setScroll((entry.scrolledY + entry.height - height + MARGIN).toDouble())
}
}
inner class Category(val category: RenderableStarterCategory, height: Int) : Entry(height) {
var controllerButtonSelected = -1
override fun render(
guiGraphics: GuiGraphics,
@@ -170,7 +184,7 @@ class CategoryList(
width = CONTAINER_BUTTON_SIZE,
height = CONTAINER_BUTTON_SIZE,
textureHeight = CONTAINER_BUTTON_SIZE * 2,
vOffset = if (hovered) CONTAINER_BUTTON_SIZE else 0
vOffset = if (hovered || controllerButtonSelected == index) CONTAINER_BUTTON_SIZE else 0
)
matrices.pushPose()
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.client.gui.startselection.widgets
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
import com.cobblemon.mod.common.client.CobblemonResources
@@ -17,10 +15,8 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
@@ -50,7 +46,7 @@ class SelectionButton(
width = BUTTON_WIDTH,
height = BUTTON_HEIGHT,
textureHeight = BUTTON_HEIGHT * 2,
vOffset = if (isHovered) BUTTON_HEIGHT else 0
vOffset = if (isHovered || isFocused) BUTTON_HEIGHT else 0
)
drawScaledText(
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.client.gui.summary
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.CobblemonSounds
@@ -28,16 +27,22 @@ 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.*
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.keybind.keybinds.SummaryBinding
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
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
@@ -58,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.
@@ -68,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
@@ -77,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
@@ -124,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) { }
/**
@@ -166,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,
@@ -212,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()
@@ -261,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()
}
)
@@ -302,9 +315,214 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
addRenderableWidget(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
}
@@ -365,11 +583,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()
}
@@ -381,12 +599,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)
@@ -406,6 +628,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pY = y + 12,
pokemon = this.selectedPokemon
)
mainScreenComponentManager = null
}
MOVES -> {
@@ -413,7 +636,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 -> {
@@ -421,25 +650,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)
@@ -454,7 +696,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 -> {
@@ -476,7 +720,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"]
}
}
@@ -486,14 +732,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) {}
@@ -633,8 +932,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(
@@ -663,19 +965,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()
}
/**
@@ -715,27 +1023,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
) {
this.focused = null
}
if (Cobblemon.config.enableDebugKeys) {
val model = VaryingModelRepository.getPoser(selectedPokemon.species.resourceIdentifier, modelWidget.state)
if (keyCode == InputConstants.KEY_UP) {
model.profileTranslation = model.profileTranslation.add(0.0, -0.01, 0.0)
}
if (keyCode == InputConstants.KEY_DOWN) {
model.profileTranslation = model.profileTranslation.add(0.0, 0.01, 0.0)
}
if (keyCode == InputConstants.KEY_LEFT) {
model.profileTranslation = model.profileTranslation.add(-0.01, 0.0, 0.0)
}
if (keyCode == InputConstants.KEY_RIGHT) {
model.profileTranslation = model.profileTranslation.add(0.01, 0.0, 0.0)
}
if (keyCode == InputConstants.KEY_EQUALS) {
model.profileScale += 0.01F
}
if (keyCode == InputConstants.KEY_MINUS) {
model.profileScale -= 0.01F
}
}
handleProfileDebugKeys(keyCode, selectedPokemon.species.resourceIdentifier, modelWidget.state)
return super.keyPressed(keyCode, scanCode, modifiers)
}
@@ -744,13 +1034,8 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
override fun onClose() {
if (Cobblemon.config.enableDebugKeys) {
val model = VaryingModelRepository.getPoser(selectedPokemon.species.resourceIdentifier, modelWidget.state)
Minecraft.getInstance().player?.sendSystemMessage(Component.literal("Profile Translation: ${model.profileTranslation}"))
Minecraft.getInstance().player?.sendSystemMessage(Component.literal("Profile Scale: ${model.profileScale}"))
Cobblemon.LOGGER.info("override var profileTranslation = Vec3d(${model.profileTranslation.x}, ${model.profileTranslation.y}, ${model.profileTranslation.z})")
Cobblemon.LOGGER.info("override var profileScale = ${model.profileScale}F")
}
logProfileTranslations(selectedPokemon.species.resourceIdentifier, modelWidget.state)
super.onClose()
}
}
@@ -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) {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.summary.widgets
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.client.gui.ProfileTransformType
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.calculateHeadYawAndPitch
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -20,6 +21,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(
@@ -58,6 +60,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
@@ -87,7 +91,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
@@ -96,12 +100,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,8 +127,10 @@ class ModelWidget(
partialTicks = partialTicks,
headYaw = currentYawAndPitch.first,
headPitch = currentYawAndPitch.second,
blockLight = blockLight
blockLight = blockLight,
profileTransformType = ProfileTransformType.SUMMARY,
)
context.flush()
heldItemRenderer.renderOnModel(
pokemon.heldItem,
@@ -88,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,
)
@@ -223,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
}
}
}
}
@@ -125,6 +125,23 @@ abstract class SummaryScrollList<T : ObjectSelectionList.Entry<T>>(
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
public override fun ensureVisible(entry: T) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y - 4
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
public override fun getRowTop(index: Int): Int {
return super.getRowTop(index)
}
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
scrolling = mouseX >= this.scrollbarPosition.toDouble()
&& mouseX < (this.scrollbarPosition + 3).toDouble()
@@ -135,4 +152,6 @@ abstract class SummaryScrollList<T : ObjectSelectionList.Entry<T>>(
private fun correctSize() {
setRectangle(WIDTH, HEIGHT, listX, listY)
}
open fun onPress(entry: T) {}
}
@@ -106,10 +106,11 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
companion object {
private val slotResource = cobblemonResource("textures/gui/summary/summary_mark_slot.png")
}
var x: Int = 0
var y: Int = 0
var slotSelected: (Int) -> Boolean = { false }
fun renderRow(context: GuiGraphics, y: Int, x: Int, mouseX: Int, mouseY: Int): Boolean {
var anyHovered = false
val horizontalSpacing = if (isScrollVisible()) SLOT_SPACING else (SLOT_SPACING + 1)
@@ -133,7 +134,7 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
y = startPosY,
width = SLOT_SIZE,
height = SLOT_SIZE,
vOffset = if (slotHovered) SLOT_SIZE else 0,
vOffset = if (slotHovered || (mark != null && slotSelected.invoke(index))) SLOT_SIZE else 0,
textureHeight = SLOT_SIZE * 2
)
@@ -160,7 +161,7 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
return true
}
private fun isScrollVisible(): Boolean = parent.children().size > 5
fun isScrollVisible(): Boolean = parent.children().size > 5
private fun getHoveredSlotIndex(mouseX: Int, mouseY: Int): Int {
val horizontalSpacing = if (isScrollVisible()) SLOT_SPACING else (SLOT_SPACING + 1)
@@ -15,7 +15,10 @@ import com.cobblemon.mod.common.api.gui.MultiLineLabelK
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.mark.Mark
import com.cobblemon.mod.common.client.gui.common.MarkIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
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.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -27,7 +30,8 @@ import net.minecraft.network.chat.Component
class MarksWidget(
pX: Int, pY: Int,
val pokemon: Pokemon
val pokemon: Pokemon,
val summary: Summary
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("MarksWidget")) {
companion object {
private const val WIDTH = 134
@@ -53,6 +57,9 @@ class MarksWidget(
setHoveredMark = { hoveredMark = it }
)
val controllerComponentManager = ControllerComponentManager(summary)
var lastSelectedMark = 0
init {
children.clear()
activeMark = pokemon.activeMark
@@ -64,6 +71,54 @@ class MarksWidget(
while ((marksList.size < 30) || (marksList.size > 30 && marksList.size % 6 != 0)) marksList.add(null)
marksScrollList.createEntries(marksList)
addWidget(marksScrollList)
marksScrollList.children().forEachIndexed { firstIndex, row ->
row.markList.forEachIndexed { secondIndex, mark ->
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex}"] = ControllerComponent().also {
it.associatedGUIElement = row
it.onPress = {
if (mark != null) {
selectedMark = mark
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
it.onSpecial1 = {
if (selectedMark !== null) {
selectedMark = null
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
it.onSelect = {
lastSelectedMark = firstIndex * 6 + secondIndex
marksScrollList.ensureVisible(row)
}
if (firstIndex - 1 >= 0) {
it.up = {
controllerComponentManager.componentList["mark_${firstIndex-1}_${secondIndex}"]
}
}
if (firstIndex + 1 <= marksScrollList.children().size - 1) {
it.down = {
controllerComponentManager.componentList["mark_${firstIndex+1}_${secondIndex}"]
}
}
if (secondIndex - 1 >= 0) {
it.left = {
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex-1}"]
}
}
if (secondIndex + 1 <= row.markList.size - 1) {
it.right = {
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex+1}"]
}
}
}
}
row.slotSelected = { index ->
controllerComponentManager.active && lastSelectedMark / 6 == firstIndex && lastSelectedMark % 6 == index
}
}
controllerComponentManager.let { it.focused = it.componentList["mark_${lastSelectedMark / 6}_${lastSelectedMark % 6}"] }
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
@@ -45,14 +45,14 @@ class MoveSlotWidget(
const val MOVE_HEIGHT = 22
}
private val moveUpButton = ReorderMoveButton(x, y, true) {
val moveUpButton = ReorderMoveButton(x, y, true) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, true)
}.apply {
addWidget(this)
}
private val moveDownButton = ReorderMoveButton(x, y, false) {
val moveDownButton = ReorderMoveButton(x, y, false) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, false)
}.apply {
@@ -20,6 +20,8 @@ import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.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.net.messages.server.BenchMovePacket
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -49,13 +51,45 @@ class MoveSwapScreen(
private val forgetMoveIconResource = cobblemonResource("textures/gui/summary/summary_move_condensed_empty_icon.png")
}
var controllerComponentManager = ControllerComponentManager(movesWidget.summary).also {
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
public override fun addEntry(entry: MoveSlot): Int {
return super.addEntry(entry)
val i = super.addEntry(entry)
controllerComponentManager.componentList["moves_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = {
entry.onPress()
movesWidget.summary.controllerComponentManager.let { controller -> controller.focused = controller.componentList["center_screen"] }
}
it.onSelect = {
ensureVisible(entry)
entry.slotSelected = true
}
it.onUnselect = {
entry.slotSelected = 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
}
class MoveSlot(val pane: MoveSwapScreen, val move: MoveTemplate?, val ppRaisedStages: Int, pokemon: Pokemon) : Entry<MoveSlot>() {
override fun getNarration() = move?.displayName ?: lang("ui.moves.forget")
val elementalType = move?.getEffectiveElementalType(pokemon) ?: ElementalTypes.NORMAL
var slotSelected = false
override fun render(
context: GuiGraphics,
index: Int,
@@ -80,7 +114,7 @@ class MoveSwapScreen(
y = tweakedRowTop,
height = SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || slotSelected) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -196,7 +230,7 @@ class MoveSwapScreen(
y = tweakedRowTop,
height = SLOT_HEIGHT,
width = 100,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || slotSelected) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2,
)
blitk(
@@ -206,7 +240,7 @@ class MoveSwapScreen(
y = tweakedRowTop + SLOT_HEIGHT / 2 - 4,
height = 7,
width = 7,
vOffset = if (isHovered) 7 else 0,
vOffset = if (isHovered || slotSelected) 7 else 0,
textureHeight = 14,
)
}
@@ -215,21 +249,29 @@ class MoveSwapScreen(
override fun mouseClicked(d: Double, e: Double, i: Int): Boolean {
if (isMouseOver(d, e)) {
val pokemon = pane.movesWidget.summary.selectedPokemon
val isParty = pokemon in CobblemonClient.storage.party
CobblemonNetwork.sendToServer(
BenchMovePacket(
isParty = isParty,
uuid = pokemon.uuid,
oldMove = pane.replacedMove?.template,
newMove = move
)
)
pane.movesWidget.summary.playSound(CobblemonSounds.GUI_CLICK)
pane.movesWidget.summary.displaySideScreen(Summary.PARTY)
onPress()
return true
}
return false
}
fun onPress() {
switchMove()
pane.movesWidget.summary.playSound(CobblemonSounds.GUI_CLICK)
pane.movesWidget.summary.displaySideScreen(Summary.PARTY)
}
fun switchMove() {
val pokemon = pane.movesWidget.summary.selectedPokemon
val isParty = pokemon in CobblemonClient.storage.party
CobblemonNetwork.sendToServer(
BenchMovePacket(
isParty = isParty,
uuid = pokemon.uuid,
oldMove = pane.replacedMove?.template,
newMove = move
)
)
}
}
}
@@ -9,12 +9,15 @@
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.summary.Summary
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.RequestMoveSwapPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -52,8 +55,8 @@ class MovesWidget(
var selectedMove: Move? = null
private var index = -1
private val moves = (
var index = -1
val moves = (
if (summary.selectedPokemon.relearnableMoves.count() > 0)
summary.selectedPokemon.moveSet.getMovesWithNulls()
else summary.selectedPokemon.moveSet
@@ -76,6 +79,79 @@ class MovesWidget(
5
)
val controllerComponentManager = ControllerComponentManager(summary).also { controller ->
var i = 0
moves.forEach { move ->
controller.componentList["move_${i}"] = ControllerComponent().also {
it.associatedGUIElement = move
var upLoc = i - 1
if (upLoc < 0) upLoc = moves.size - 1
it.up = {
var returnVal: ControllerComponent?
if (!summary.moveSwitchingMode) {
returnVal = controller.componentList["move_${upLoc}"]
} else {
returnVal = null
summary.selectedMoveIndex = upLoc
reorderMove(move, true)
}
returnVal
}
var downLoc = i + 1
if (downLoc > moves.size - 1) downLoc = 0
it.down = {
var returnVal: ControllerComponent?
if (!summary.moveSwitchingMode) {
returnVal = controller.componentList["move_${downLoc}"]
} else {
returnVal = null
summary.selectedMoveIndex = downLoc
reorderMove(move, false)
}
returnVal
}
it.onSpecial1 = {
if (!summary.moveSwitchingMode) {
summary.playSound(CobblemonSounds.GUI_CLICK)
summary.displaySideScreen(Summary.MOVE_SWAP, move.move)
}
}
it.onSpecial2 = {
summary.playSound(CobblemonSounds.GUI_CLICK)
summary.moveSwitchingMode = !summary.moveSwitchingMode
if (summary.moveSwitchingMode) {
move.moveUpButton.isFocused = true
move.moveDownButton.isFocused = true
} else {
move.moveUpButton.isFocused = false
move.moveDownButton.isFocused = false
}
}
val staticI = i
it.onSelect = {
summary.selectedMoveIndex = staticI
if (selectedMove != move.move) selectMove(move.move)
if (summary.moveSwitchingMode) {
move.moveUpButton.isFocused = true
move.moveDownButton.isFocused = true
}
}
it.onUnselect = {
move.moveUpButton.isFocused = false
move.moveDownButton.isFocused = false
}
}
i++
}
controller.onNoneSelected = {
summary.selectedMoveIndex = summary.selectedMoveIndex.coerceIn(0, moves.size - 1)
controller.focused = controller.componentList["move_${summary.selectedMoveIndex}"]
true
}
controller.onActiveChange = { state ->
if (state) controller.focused?.onSelect?.invoke() else controller.focused?.onUnselect?.invoke()
}
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val matrices = context.pose()
@@ -50,7 +50,7 @@ class ReorderMoveButton(
texture = if (isUp) moveReorderUpResource else moveReorderDownResource,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble(), offsetY.toFloat())) HEIGHT else 0,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble(), offsetY.toFloat()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2,
scale = SCALE
)
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
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.gui.summary.featurerenderers.BarSummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
@@ -49,16 +50,17 @@ import org.joml.Vector3f
class StatWidget(
pX: Int, pY: Int,
val pokemon: Pokemon,
val tabIndex: Int = 0
val tabIndex: Int = 0,
val summary: Summary
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("StatWidget")) {
companion object {
// Stat tab options
private const val STATS = "stats"
private const val IV = "ivs"
private const val EV = "evs"
private const val RIDE = "ride"
private const val OTHER = "other"
const val STATS = "stats"
const val IV = "ivs"
const val EV = "evs"
const val RIDE = "ride"
const val OTHER = "other"
private const val OTHER_STAT_BARS_PER_PAGE = 4
private const val STAT_TAB_WIDTH_6 = 22
@@ -113,13 +115,13 @@ class StatWidget(
)
}
private val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
private var _statTabIndex: Int = tabIndex
var rawStatTabIndex: Int = tabIndex
var statTabIndex: Int
get() = _statTabIndex.coerceIn(0, statOptions.size - 1)
get() = rawStatTabIndex.coerceIn(0, statOptions.size - 1)
set(value) {
_statTabIndex = value.coerceIn(0, statOptions.size - 1)
rawStatTabIndex = value.coerceIn(0, statOptions.size - 1)
}
private var _rideBehaviourIndex: Int = 0
@@ -302,7 +304,7 @@ class StatWidget(
pokemon.form.riding.behaviours?.let {
val behaviours = it.entries.toList()
val selectedBehaviour = behaviours[rideBehaviourIndex]
val canSwitchStyle = behaviours.size > 1 && centerHovered(pMouseX, pMouseY)
val canSwitchStyle = behaviours.size > 1 && (centerHovered(pMouseX, pMouseY) || (summary.controllerComponentManager.let { it.active && it.currentSelectedComponent == "center_screen" }))
blitk(
matrixStack = matrices,
@@ -120,7 +120,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
fun getLogoResource(langCode: String?): ResourceLocation =
logoResourceMap.entries.find { it.value.contains(langCode?.substringBefore("_")) }?.key ?: logo
}
var selectedTM: TechnicalMachine? = null
var heldStackMove: MoveTemplate? = null
var tmList: SettableObservable<MutableList<TechnicalMachine>> = SettableObservable(mutableListOf())
var output: ItemStack? = null
@@ -159,6 +159,8 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
var oldIngredientItems: Array<Item> = arrayOf(Items.AIR, Items.AIR, Items.AIR)
var oldResultTmSlotItem: Item = Items.AIR
var moveInfoTM: TechnicalMachine? = null
override fun init() {
imageWidth = WIDTH
imageHeight = HEIGHT
@@ -171,6 +173,8 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
logoPosY = topPos + 1
scanPosY = topPos + 1
menu.onSelectedTMUpdate = { updateSelectedTM(it) }
partySlotList.clear()
CobblemonClient.storage.party.forEachIndexed { index, pokemon ->
partySlotList.add(TMPartySlotWidget(
@@ -226,7 +230,14 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
pX = leftPos + 4,
pY = topPos + 29,
tmList,
setTM = { tm, clicked -> setSelectedTM(tm, clicked) }
setTM = { tm, clicked ->
if (clicked) {
if (tm != menu.currentlySelectedTM) SetActiveTMPacket(tm).sendToServer()
setScreenFromMode(TM_BURN_MODE)
} else {
updateMoveInformation(tm)
}
}
)
if (!children().contains(movesScrollingList)) addRenderableWidget(movesScrollingList)
@@ -252,24 +263,21 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
selectedMoveButton = MoveSlotButton(
leftPos + 6,
topPos + 6,
move = selectedTM?.moveName,
pp = selectedTM?.moveName?.pp ?: 0,
move = menu.currentlySelectedTM?.moveName,
pp = menu.currentlySelectedTM?.moveName?.pp ?: 0,
ppAsFraction = false
) {
if ((it as MoveSlotButton).enabled) {
if (::movesScrollingList.isInitialized && movesScrollingList.children().isEmpty()) setMoveList()
playSound(CobblemonSounds.GUI_CLICK)
setScreenFromMode(MOVE_SELECT_MODE)
// Clear active TM
CobblemonNetwork.sendToServer(SetActiveTMPacket(null))
}
}
if (!children().contains(selectedMoveButton)) addRenderableWidget(selectedMoveButton)
startButton = StartButton(leftPos + 5F, topPos + 35F) {
if (!(it as StartButton).disabled) {
selectedTM?.let {
menu.currentlySelectedTM?.let {
val burnActive = menu.containerData?.get(TMMachineBlockEntity.BURN_ACTIVE_INDEX) ?: 0
CobblemonNetwork.sendToServer(SetTMMachineContainerDataPacket(
TMMachineBlockEntity.BURN_ACTIVE_INDEX,
@@ -284,17 +292,14 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
))
}*/
CobblemonNetwork.sendToServer(SetActiveTMPacket(it))
//CobblemonNetwork.sendToServer(SetActiveTMPacket(it))
if (burnActive != 1) playSound(CobblemonSounds.TM_MACHINE_START)
inventory.setChanged()
menu.broadcastChanges()
}
}
}
if (!children().contains(startButton)) addRenderableWidget(startButton)
setScreenFromMode(mode)
initScreen = false
updateSlotItemMemory()
}
@@ -306,7 +311,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
if (scanPosY + 1 >= (topPos + SCREEN_SAVER_HEIGHT) * 2) scanPosY = 0
if (mode == TM_BURN_MODE) {
selectedTM?.let { tm ->
menu.currentlySelectedTM?.let { tm ->
val resultStack = menu.inventory!!.getItem(TMMachineMenu.RESULT_SLOT)
val craftedTmStack = ItemStack(CobblemonItems.TECHNICAL_MACHINE)
.also { TMMoveComponent.setTMMove(it, tm.moveName) }
@@ -339,7 +344,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
}
if (::selectedMoveButton.isInitialized) {
selectedMoveButton.enabled = !isBurnActive() && (heldStackMove == null || heldStackMove == selectedTM?.moveName)
selectedMoveButton.enabled = (heldStackMove == null || heldStackMove == menu.currentlySelectedTM?.moveName)
}
if (isBurnActive()) {
@@ -350,9 +355,6 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
}
}
}
} else {
// Check if screen should be switched upon opening UI
if (!initScreen) loadBurnScreenData()
}
if (heldMoveItemChanged) {
@@ -400,32 +402,6 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
return wasScreenSaverActive
}
private fun loadBurnScreenData() {
var tm: TechnicalMachine? = null
val blockPos = menu.containerData?.let {
val x = it.get(TMMachineBlockEntity.POSITION_X_INDEX)
val y = it.get(TMMachineBlockEntity.POSITION_Y_INDEX)
val z = it.get(TMMachineBlockEntity.POSITION_Z_INDEX)
BlockPos(x, y, z)
}
inventory.player.level().getBlockEntity(blockPos)?.let { blockEntity ->
if (blockEntity is TMMachineBlockEntity) {
Moves.getByName(blockEntity.activeMove)?.let {
tm = TechnicalMachines.moveToTM[it]
}
}
}
// If burn currently in progress or if active TM set, set to burn screen
if (isBurnActive() || tm != null) {
setSelectedTM(tm, true)
}
initScreen = true
}
private fun isBurnActive(): Boolean = (menu.containerData?.get(TMMachineBlockEntity.BURN_ACTIVE_INDEX) ?: 0) == 1
private fun setScreenFromMode(screen: Int) {
@@ -467,7 +443,6 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
private fun toggleTypeSelect(isVisible: Boolean) {
if (isVisible) {
setSelectedTM(null, false)
if (::moveSearchWidget.isInitialized) moveSearchWidget.value = ""
if (selectedPokemon != null) selectedPokemon = null
sortType = null
@@ -505,7 +480,6 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
}
private fun toggleTMBurn(isVisible: Boolean) {
if (!isVisible) setSelectedTM(null, false)
if (::selectedMoveButton.isInitialized) selectedMoveButton.visible = isVisible
if (::startButton.isInitialized) startButton.visible = isVisible
}
@@ -516,35 +490,40 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
return if (learnableMoves.contains(move)) TMPartySlotWidget.CAN_LEARN else TMPartySlotWidget.CANNOT_LEARN
}
fun setSelectedTM(tm: TechnicalMachine?, clicked: Boolean) {
if (selectedTM != tm) {
selectedTM = tm
updatePartySlotStatus(tm?.moveName)
fun updateSelectedTM(tm: TechnicalMachine?) {
if (!initScreen) {
initScreen = true
tm?.let {
setScreenFromMode(TM_BURN_MODE)
sortType = ElementalTypes.get(tm.type)
setMoveList()
}
}
if (::movesScrollingList.isInitialized) {
movesScrollingList.setSlotHighlighted(if (clicked) null else selectedTM?.id)
if (mode == TM_BURN_MODE) updateMoveInformation(tm)
tm?.let {
if (::selectedMoveButton.isInitialized) {
selectedMoveButton.apply {
move = it.moveName
pp = it.moveName.pp
}
}
}
}
fun updateMoveInformation(tm: TechnicalMachine?) {
moveInfoTM = tm
if (::moveDescriptionWidget.isInitialized) {
val selectedDescription = selectedTM?.moveName?.description?.string
val selectedDescription = tm?.moveName?.description?.string
moveDescriptionWidget.setText(
if (selectedDescription != null) listOf(selectedDescription) else emptyList()
)
}
if (clicked) {
CobblemonNetwork.sendToServer(SetActiveTMPacket(tm))
selectedTM?.let {
if (::selectedMoveButton.isInitialized) {
selectedMoveButton.apply {
move = it.moveName
pp = it.moveName.pp
}
setScreenFromMode(TM_BURN_MODE)
}
}
if (::movesScrollingList.isInitialized) {
movesScrollingList.setSlotHighlighted(tm?.id)
}
}
@@ -557,7 +536,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
}
fun renderDiscTypeOverlay(poseStack: PoseStack, isDiscRotating: Boolean, alpha: Float = 1F) {
selectedTM?.let {
menu.currentlySelectedTM?.let {
val primaryRgb = it.moveName.elementalType.primaryColor.toRGB()
val secondaryRgb = it.moveName.elementalType.secondaryColor.toRGB()
blitk(
@@ -706,7 +685,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
}
fun renderMoveInfo(context: GuiGraphics, mouseX: Int, mouseY: Int) {
val moveTemplate = selectedTM?.moveName
val moveTemplate = moveInfoTM?.moveName
blitk(
matrixStack = context.pose(),
@@ -1057,7 +1036,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
movesScrollingList.setDisabled(true, TechnicalMachines.moveToTM[heldStackMove])
if (mode == MOVE_SELECT_MODE) {
val heldStackTm = TechnicalMachines.moveToTM[heldStackMove]
setSelectedTM(if (tmList.get().contains(heldStackTm)) heldStackTm else null, false)
updateMoveInformation(if (tmList.get().contains(heldStackTm)) heldStackTm else null)
// Scroll to move if moves list contains held TM move
if (tmList.get().contains(heldStackTm)) {
@@ -1077,8 +1056,8 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
heldStackMove = null
// Reset selected move on move select screen
if (mode == MOVE_SELECT_MODE) setSelectedTM(null, false)
updatePartySlotStatus(selectedTM?.moveName)
if (mode == MOVE_SELECT_MODE) updateMoveInformation(null)
updatePartySlotStatus(menu.currentlySelectedTM?.moveName)
if (::movesScrollingList.isInitialized) {
movesScrollingList.setDisabled(false)
@@ -1155,7 +1134,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
if (isInventoryKeyPressed(minecraft, keyCode, scanCode)) {
if (searchFocused) { return true }
else {
Minecraft.getInstance().setScreen(null)
onClose()
return true
}
}
@@ -1166,7 +1145,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
this.focused = null
return true
} else {
Minecraft.getInstance().setScreen(null)
onClose()
return true
}
}
@@ -49,7 +49,7 @@ open class PartySlot(
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
if (!isOpposing && isHovered(mouseX, mouseY)) {
if (!isOpposing && (isHovered(mouseX, mouseY) || isFocused)) {
blitk(
matrixStack = matrices,
texture = hoverBackgroundResource,
@@ -49,7 +49,7 @@ class TradeButton(
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
)
@@ -22,6 +22,9 @@ import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.trade.ClientTrade
@@ -55,7 +58,7 @@ class TradeGUI(
val traderName: MutableComponent,
val traderParty: MutableList<TradeablePokemon?>,
val party: MutableList<TradeablePokemon?>
): Screen(lang("trade.gui.title")), CobblemonRenderable {
): Screen(lang("trade.gui.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 293
@@ -118,6 +121,13 @@ class TradeGUI(
var tradeSoundInstance: SoundInstance? = null
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedRow = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
init {
trade.cancelEmitter.subscribe {
cancelTradeSound()
@@ -178,21 +188,25 @@ class TradeGUI(
)
// Trade Button
this.addRenderableWidget(
TradeButton(
x = x + 120,
y = y + 119,
parent = this,
onPress = {
if (offeredPokemon != null && opposingOfferedPokemon != null && protectiveTicks <= 0 && !tradeProcessing) {
ticksElapsed = 0
readyProgress = 0
CobblemonNetwork.sendToServer(ChangeTradeAcceptancePacket(opposingOfferedPokemon!!.uuid, !trade.acceptedOppositeOffer))
playSound(CobblemonSounds.GUI_CLICK)
}
val tradeButton = TradeButton(
x = x + 120,
y = y + 119,
parent = this,
onPress = {
if (offeredPokemon != null && opposingOfferedPokemon != null && protectiveTicks <= 0 && !tradeProcessing) {
ticksElapsed = 0
readyProgress = 0
CobblemonNetwork.sendToServer(ChangeTradeAcceptancePacket(opposingOfferedPokemon!!.uuid, !trade.acceptedOppositeOffer))
playSound(CobblemonSounds.GUI_CLICK)
}
)
}
)
this.addRenderableWidget(tradeButton)
controllerComponentManager.componentList["trade"] = ControllerComponent().also {
it.associatedGUIElement = tradeButton
it.left = { controllerComponentManager.componentList["slot_${lastSelectedRow}_${1}"] }
}
// Party
for (partyIndex in 0..5) {
@@ -210,7 +224,7 @@ class TradeGUI(
}
val pokemon = party[partyIndex]
PartySlot(
val slot = PartySlot(
x = slotX,
y = slotY,
pokemon = pokemon,
@@ -223,6 +237,22 @@ class TradeGUI(
}
}
).also { widget -> addRenderableWidget(widget) }
val row = partyIndex / 2
val column = partyIndex % 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}"] }
it.right = { controllerComponentManager.componentList["trade"] }
}
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${row-1}_${column}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["slot_${row+1}_${column}"] }
it.onSelect = {
lastSelectedRow = row
}
}
}
// Opposing Party
@@ -257,6 +287,10 @@ class TradeGUI(
opposingOfferedPokemonMarkings = MarkingsWidget(x + 177.5, y + 21, opposingOfferedPokemon, false)
addRenderableWidget(offeredPokemonMarkings)
addRenderableWidget(opposingOfferedPokemonMarkings)
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -445,6 +479,8 @@ class TradeGUI(
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, opposingOfferedPokemon!!.heldItemNoCopy(), mouseX, mouseY)
}
renderSelection(matrices)
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
@@ -0,0 +1,363 @@
/*
* 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.client.integration.controlify
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonClient.ridingTurnInputHandler
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.gui.dialogue.DialogueScreen
import com.cobblemon.mod.common.client.gui.interact.battleRequest.BattleConfigureGUI
import com.cobblemon.mod.common.client.gui.interact.moveselect.MoveSelectGUI
import com.cobblemon.mod.common.client.gui.interact.partyselect.PartySelectGUI
import com.cobblemon.mod.common.client.gui.interact.wheel.InteractWheelGUI
import com.cobblemon.mod.common.client.gui.pc.PCGUI
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
import com.cobblemon.mod.common.client.gui.snapshots.SnapshotWarningScreen
import com.cobblemon.mod.common.client.gui.startselection.StarterSelectionScreen
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.trade.TradeGUI
import com.cobblemon.mod.common.client.integration.controlify.gui.CobblemonGUIProcessor
import com.cobblemon.mod.common.client.integration.controlify.gui.InteractWheelProcessor
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinds
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import dev.isxander.controlify.Controlify
import dev.isxander.controlify.api.ControlifyApi
import dev.isxander.controlify.api.bind.ControlifyBindApi
import dev.isxander.controlify.api.bind.InputBindingBuilder
import dev.isxander.controlify.api.bind.InputBindingSupplier
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint
import dev.isxander.controlify.api.entrypoint.InitContext
import dev.isxander.controlify.api.entrypoint.PreInitContext
import dev.isxander.controlify.bindings.BindContext
import dev.isxander.controlify.bindings.ControlifyBindings
import dev.isxander.controlify.bindings.RadialIcons
import dev.isxander.controlify.screenop.ScreenProcessor
import dev.isxander.controlify.screenop.ScreenProcessorFactory
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.player.LocalPlayer
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Items
import org.joml.Vector2d
import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
class CobblemonControlifyEntrypoint : ControlifyEntrypoint {
override fun onControlifyInit(initContext: InitContext) {
ridingTurnInputHandler.put(
cobblemonResource("compat.controlify"),
({ localPlayer: LocalPlayer, xDelta: Double, yDelta: Double, deltaTime: Double ->
val vec = Vector2d(xDelta, yDelta)
val controller = initContext.controlify().currentController.getOrNull()
val vehicle = localPlayer.vehicle
if (vehicle is OrientationControllable && vehicle.orientationController.isActive() && initContext.controlify().currentInputMode().isController && controller != null){
val multiplier = deltaTime * 1500
val config = controller.input().getOrNull()?.config()?.config()
val vSensitivity = config?.vLookSensitivity ?: 0.9f
val hSensitivity = config?.hLookSensitivity ?: 1f
// Should never be null once bindings are set
var vAxis = (RIDING_DOWN!!.on(controller).analogueNow() - RIDING_UP!!.on(controller).analogueNow()).toDouble()
var hAxis = (RIDING_RIGHT!!.on(controller).analogueNow() - RIDING_LEFT!!.on(controller).analogueNow()).toDouble()
vAxis *= multiplier * vSensitivity
hAxis *= multiplier * hSensitivity
vec.x = hAxis
vec.y = vAxis
}
vec
})
)
CobblemonClient.ridingSprintHandler.put(
cobblemonResource("compat.controlify")
) {
initContext.controlify().currentController.getOrNull()?.let { ControlifyBindings.SPRINT.on(it).digitalNow() } == true
}
}
override fun onControlifyPreInit(initContext: PreInitContext) {
val bindings = ControlifyBindApi.get()
bindings.registerBindContext(RIDING_POKEMON)
bindings.registerBindContext(ORIENTATION_CONTROLLER_ACTIVE)
bindings.registerBindContext(HAS_POKEMON)
bindings.registerBindContext(INTERACT_WHEEL_SCREEN)
bindings.registerBindContext(COBBLEMON_CONTROLLER_SCREEN)
RIDING_UP =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_up"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.riding_up"))
.allowedContexts(ORIENTATION_CONTROLLER_ACTIVE, BindContext.IN_GAME)
}
RIDING_DOWN =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_down"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.riding_down"))
.allowedContexts(ORIENTATION_CONTROLLER_ACTIVE, BindContext.IN_GAME)
}
RIDING_LEFT =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_left"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.riding_left"))
.allowedContexts(ORIENTATION_CONTROLLER_ACTIVE, BindContext.IN_GAME)
}
RIDING_RIGHT =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_right"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.riding_right"))
.allowedContexts(ORIENTATION_CONTROLLER_ACTIVE, BindContext.IN_GAME)
}
HIDE_PARTY =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("hide_party"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.hideparty"))
.allowedContexts(BindContext.IN_GAME)
.radialCandidate(RadialIcons.getItem(Items.BARRIER))
.addKeyCorrelation(CobblemonKeyBinds.HIDE_PARTY)
.keyEmulation(CobblemonKeyBinds.HIDE_PARTY)
}
SUMMARY =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("summary"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.summary"))
.allowedContexts(BindContext.IN_GAME)
.radialCandidate(RadialIcons.getItem(CobblemonItems.GREAT_BALL))
.addKeyCorrelation(CobblemonKeyBinds.SUMMARY)
.keyEmulation(CobblemonKeyBinds.SUMMARY)
}
PARTY_OVERLAY_DOWN =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("party_overlay_down"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.downshiftparty"))
.allowedContexts(HAS_POKEMON, BindContext.IN_GAME)
.radialCandidate(RadialIcons.EMPTY)
.addKeyCorrelation(CobblemonKeyBinds.PARTY_OVERLAY_DOWN)
.keyEmulation(CobblemonKeyBinds.PARTY_OVERLAY_DOWN)
}
PARTY_OVERLAY_UP =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("party_overlay_up"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.upshiftparty"))
.allowedContexts(HAS_POKEMON, BindContext.IN_GAME)
.radialCandidate(RadialIcons.EMPTY)
.addKeyCorrelation(CobblemonKeyBinds.PARTY_OVERLAY_UP)
.keyEmulation(CobblemonKeyBinds.PARTY_OVERLAY_DOWN)
}
SEND_OUT_POKEMON =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("send_out_pokemon"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.throwpartypokemon"))
.allowedContexts(HAS_POKEMON, BindContext.IN_GAME)
.radialCandidate(RadialIcons.getItem(CobblemonItems.POKE_BALL))
.addKeyCorrelation(CobblemonKeyBinds.SEND_OUT_POKEMON)
.keyEmulation(CobblemonKeyBinds.SEND_OUT_POKEMON)
}
RIDING_FREELOOK =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_freelook"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.ridingfreelook"))
.allowedContexts(RIDING_POKEMON, BindContext.IN_GAME)
.radialCandidate(RadialIcons.EMPTY)
.addKeyCorrelation(CobblemonKeyBinds.RIDING_FREELOOK)
.keyEmulation(CobblemonKeyBinds.RIDING_FREELOOK)
}
INTERACT_WHEEL_UP_AXIS =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("interact_wheel_up"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.interact_wheel_up"))
.allowedContexts(INTERACT_WHEEL_SCREEN, BindContext.IN_GAME)
}
INTERACT_WHEEL_DOWN_AXIS =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("interact_wheel_down"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.interact_wheel_down"))
.allowedContexts(INTERACT_WHEEL_SCREEN, BindContext.IN_GAME)
}
INTERACT_WHEEL_RIGHT_AXIS =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("interact_wheel_right"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.interact_wheel_right"))
.allowedContexts(INTERACT_WHEEL_SCREEN, BindContext.IN_GAME)
}
INTERACT_WHEEL_LEFT_AXIS =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("interact_wheel_left"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.interact_wheel_left"))
.allowedContexts(INTERACT_WHEEL_SCREEN, BindContext.IN_GAME)
}
GUI_SECONDARY_NEXT_TAB =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("gui_secondary_next_tab"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.gui_secondary_next_tab"))
.allowedContexts(COBBLEMON_CONTROLLER_SCREEN)
}
GUI_SECONDARY_PREV_TAB =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("gui_secondary_prev_tab"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.gui_secondary_prev_tab"))
.allowedContexts(COBBLEMON_CONTROLLER_SCREEN)
}
GUI_START =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("gui_start"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.gui_start"))
.allowedContexts(COBBLEMON_CONTROLLER_SCREEN)
}
addProcessor(InteractWheelGUI::class, InteractWheelProcessor::class)
// This is the lazy way to do all of these (just using the vmouse), if someone with more time and dedication did this that would be better
addProcessor(DialogueScreen::class, CobblemonGUIProcessor::class)
// Below are implemented processors for our GUIs
addProcessor(Summary::class, CobblemonGUIProcessor::class)
addProcessor(BattleGUI::class, CobblemonGUIProcessor::class)
addProcessor(PokedexGUI::class, CobblemonGUIProcessor::class)
addProcessor(StarterSelectionScreen::class, CobblemonGUIProcessor::class)
addProcessor(TradeGUI::class, CobblemonGUIProcessor::class)
addProcessor(PartySelectGUI::class, CobblemonGUIProcessor::class)
addProcessor(MoveSelectGUI::class, CobblemonGUIProcessor::class)
addProcessor(PCGUI::class, CobblemonGUIProcessor::class)
addProcessor(BattleConfigureGUI::class, CobblemonGUIProcessor::class)
addProcessor(SnapshotWarningScreen::class, CobblemonGUIProcessor::class)
addProcessor(CobblemonConfigScreen::class, CobblemonGUIProcessor::class)
// This once don't need any
addProcessor(CookingPotScreen::class, CobblemonGUIProcessor::class)
}
override fun onControllersDiscovered(controlifyApi: ControlifyApi) {
}
// This is jank, but I did it after I got my wisdom teeth extracted so tbh I think I did good enough, but I won't be offended if someone changes it to something sane.
fun addProcessor(t: KClass<out Screen>, processorClazz: KClass<out ScreenProcessor<Screen>>) {
ScreenProcessorFactory.registerProvider(t.java) {
processorClazz.primaryConstructor?.call(it)
}
}
companion object {
var RIDING_UP: InputBindingSupplier? = null
var RIDING_DOWN: InputBindingSupplier? = null
var RIDING_RIGHT: InputBindingSupplier? = null
var RIDING_LEFT: InputBindingSupplier? = null
var HIDE_PARTY: InputBindingSupplier? = null
var SUMMARY: InputBindingSupplier? = null
var PARTY_OVERLAY_DOWN: InputBindingSupplier? = null
var PARTY_OVERLAY_UP: InputBindingSupplier? = null
var SEND_OUT_POKEMON: InputBindingSupplier? = null
var RIDING_FREELOOK: InputBindingSupplier? = null
var INTERACT_WHEEL_UP_AXIS: InputBindingSupplier? = null
var INTERACT_WHEEL_DOWN_AXIS: InputBindingSupplier? = null
var INTERACT_WHEEL_LEFT_AXIS: InputBindingSupplier? = null
var INTERACT_WHEEL_RIGHT_AXIS: InputBindingSupplier? = null
//gui special keybinds
var GUI_SECONDARY_NEXT_TAB: InputBindingSupplier? = null
var GUI_SECONDARY_PREV_TAB: InputBindingSupplier? = null
var GUI_START: InputBindingSupplier? = null
val RIDING_POKEMON: BindContext = BindContext(
cobblemonResource("riding"),
{ mc: Minecraft ->
mc.player?.vehicle is PokemonEntity
}
)
val ORIENTATION_CONTROLLER_ACTIVE: BindContext = BindContext(
cobblemonResource("orientation_controller_active"),
{ mc: Minecraft ->
val localPlayer = mc.player
if (localPlayer == null) return@BindContext false
val vehicle = localPlayer.vehicle
vehicle is OrientationControllable && vehicle.getOrientationController().isActive()
}
)
val HAS_POKEMON: BindContext = BindContext(
cobblemonResource("has_pokemon"),
{mc: Minecraft ->
!CobblemonClient.storage.party.isEmpty()
}
)
val INTERACT_WHEEL_SCREEN: BindContext = BindContext(
cobblemonResource("wheel_screen"),
{ it.screen is InteractWheelGUI}
)
val COBBLEMON_CONTROLLER_SCREEN: BindContext = BindContext(
cobblemonResource("cobblemon_controller_screen"),
{ mc: Minecraft ->
mc.screen is ControllerScreen
}
)
fun controllerActive(): Boolean {
return Controlify.instance().currentInputMode().isController
}
}
}
@@ -0,0 +1,247 @@
/*
* 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.client.integration.controlify.gui
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager.ButtonGuideType
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import dev.isxander.controlify.Controlify
import dev.isxander.controlify.InputMode
import dev.isxander.controlify.bindings.ControlifyBindings
import dev.isxander.controlify.bindings.InputBindingSupplierImpl
import dev.isxander.controlify.controller.ControllerEntity
import dev.isxander.controlify.controller.input.GamepadInputs
import dev.isxander.controlify.font.BindingFontHelper
import dev.isxander.controlify.gui.guide.GuideRenderer
import dev.isxander.controlify.gui.guide.PrecomputedLines
import dev.isxander.controlify.screenop.ScreenProcessor
import dev.isxander.controlify.virtualmouse.VirtualMouseBehaviour
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.network.chat.Component
open class CobblemonGUIProcessor(open val screen: Screen) : ScreenProcessor<Screen>(screen) {
companion object {
val typeToGlyth = mutableMapOf<ButtonGuideType, () -> Component>(
ButtonGuideType.PRESS to { ControlifyBindings.GUI_PRESS.inputGlyph() },
ButtonGuideType.BACK to { ControlifyBindings.GUI_BACK.inputGlyph() },
ButtonGuideType.SPECIAL_1 to { ControlifyBindings.GUI_ABSTRACT_ACTION_1.inputGlyph() },
ButtonGuideType.SPECIAL_2 to { ControlifyBindings.GUI_ABSTRACT_ACTION_2.inputGlyph() },
ButtonGuideType.TAB to { Component.empty().append(ControlifyBindings.GUI_PREV_TAB.inputGlyph()).append(ControlifyBindings.GUI_NEXT_TAB.inputGlyph()) },
ButtonGuideType.SECONDARY_TAB to { Component.empty().append(CobblemonControlifyEntrypoint.GUI_SECONDARY_PREV_TAB!!.inputGlyph()).append(CobblemonControlifyEntrypoint.GUI_SECONDARY_NEXT_TAB!!.inputGlyph()) },
ButtonGuideType.START to { CobblemonControlifyEntrypoint.GUI_START!!.inputGlyph() },
ButtonGuideType.JOYSTICK_HORIZONTAL to { Component.empty().append(ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.inputGlyph()).append(ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.inputGlyph()) },
ButtonGuideType.JOYSTICK_VERTICAL to { Component.empty().append(ControlifyBindings.GUI_SECONDARY_NAVI_UP.inputGlyph()).append(ControlifyBindings.GUI_SECONDARY_NAVI_DOWN.inputGlyph()) }
)
}
var editBoxWidget: EditBox? = null
// Until someone with more dedication than me adds non-vmouse support, we will make heavy use of the vmouse so make that the defualt for us
override fun virtualMouseBehaviour(): VirtualMouseBehaviour {
if (screen is ControllerScreen) {
return VirtualMouseBehaviour.DISABLED
}
return VirtualMouseBehaviour.CURSOR_ONLY
}
override fun onWidgetRebuild() {
(screen as? ControllerScreen)?.getComponentManager()?.let {
it.active = Controlify.instance().let { it.currentInputMode().isController && !it.virtualMouseHandler().isVirtualMouseEnabled }
it.openKeyboard = { editBox ->
editBoxWidget = editBox
}
it.getVisual = { location ->
InputBindingSupplierImpl(location).inputGlyph()
}
}
}
override fun onInputModeChanged(mode: InputMode) {
(screen as? ControllerScreen)?.getComponentManager()?.let {
val active = mode.isController && !Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled
it.active = active
}
}
override fun onVirtualMouseToggled(enabled: Boolean) {
(screen as? ControllerScreen)?.getComponentManager()?.let{
it.active = Controlify.instance().currentInputMode().isController && !enabled
}
}
override fun onControllerUpdate(controller: ControllerEntity) {
val componentManager = (screen as? ControllerScreen)?.getComponentManager()
if (componentManager == null) {
super.onControllerUpdate(controller)
return
}
//It was easier to fully handle this myself
Controlify.instance().virtualMouseHandler().handleControllerInput(controller)
val repeatAvailable = holdRepeatHelper.canNavigate()
if (!Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled) {
//Handling Base
if (ControlifyBindings.GUI_PRESS.on(controller).justPressed()) {
componentManager.press()
}
if (ControlifyBindings.GUI_BACK.on(controller).justPressed()) {
componentManager.onBack.invoke()
}
//Handling Special
if (ControlifyBindings.GUI_ABSTRACT_ACTION_1.on(controller).justPressed()) {
componentManager.special1()
}
if (ControlifyBindings.GUI_ABSTRACT_ACTION_2.on(controller).justPressed()) {
componentManager.special2()
}
if (CobblemonControlifyEntrypoint.GUI_START!!.on(controller).justPressed()) {
componentManager.onStart.invoke()
}
//Handling RJoystick
val guiNaviRight = ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller)
val guiNaviLeft = ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller)
val guiNaviUp = ControlifyBindings.GUI_SECONDARY_NAVI_UP.on(controller)
val guiNaviDown = ControlifyBindings.GUI_SECONDARY_NAVI_DOWN.on(controller)
if (guiNaviRight.digitalNow() && (!guiNaviRight.digitalPrev() || repeatAvailable)) {
holdRepeatHelper.reset()
componentManager.onRHorizontal.invoke(true)
} else if (guiNaviLeft.digitalNow() && (!guiNaviLeft.digitalPrev() || repeatAvailable)) {
holdRepeatHelper.reset()
componentManager.onRHorizontal.invoke(false)
}
if (guiNaviUp.digitalNow() && (!guiNaviUp.digitalPrev() || repeatAvailable)) {
holdRepeatHelper.reset()
componentManager.onRVertical.invoke(true)
} else if (guiNaviDown.digitalNow() && (!guiNaviDown.digitalPrev() || repeatAvailable)) {
holdRepeatHelper.reset()
componentManager.onRVertical.invoke(false)
}
componentManager.rHorizontalState = if (guiNaviRight.digitalNow()) 1 else if (guiNaviLeft.digitalNow()) -1 else 0
componentManager.rVerticalState = if (guiNaviUp.digitalNow()) 1 else if (guiNaviDown.digitalNow()) -1 else 0
//Handle Navigation
handleNavigation(controller, componentManager)
} else {
componentManager.rHorizontalState = 0
componentManager.rVerticalState = 0
handleScreenVMouse(controller, Controlify.instance().virtualMouseHandler())
}
//Main Tab
if (ControlifyBindings.GUI_NEXT_TAB.on(controller).digitalNow() && (repeatAvailable || !ControlifyBindings.GUI_NEXT_TAB.on(controller).digitalPrev())) {
holdRepeatHelper.reset()
componentManager.onTabNav.invoke(true)
} else if (ControlifyBindings.GUI_PREV_TAB.on(controller).digitalNow() && (repeatAvailable || !ControlifyBindings.GUI_PREV_TAB.on(controller).digitalPrev())) {
holdRepeatHelper.reset()
componentManager.onTabNav.invoke(false)
}
//Secondary Tab
if (CobblemonControlifyEntrypoint.GUI_SECONDARY_NEXT_TAB!!.on(controller).digitalNow() && (repeatAvailable || !CobblemonControlifyEntrypoint.GUI_SECONDARY_NEXT_TAB!!.on(controller).digitalPrev())) {
holdRepeatHelper.reset()
componentManager.onSecondaryTabNav.invoke(true)
} else if (CobblemonControlifyEntrypoint.GUI_SECONDARY_PREV_TAB!!.on(controller).digitalNow() && (repeatAvailable || !CobblemonControlifyEntrypoint.GUI_SECONDARY_PREV_TAB!!.on(controller).digitalPrev())) {
holdRepeatHelper.reset()
componentManager.onSecondaryTabNav.invoke(false)
}
if (editBoxWidget != null) {
tryOpenKeyboard(controller, editBoxWidget)
editBoxWidget = null
}
}
private fun handleNavigation(controller: ControllerEntity, componentManager: ControllerComponentManager) {
val repeatAvailable = holdRepeatHelper.canNavigate()
val input = controller.input().orElseThrow()
val state = input.stateNow()
val prevState = input.stateThen()
val guiNaviRight = ControlifyBindings.GUI_NAVI_RIGHT.on(controller)
val guiNaviLeft = ControlifyBindings.GUI_NAVI_LEFT.on(controller)
val guiNaviUp = ControlifyBindings.GUI_NAVI_UP.on(controller)
val guiNaviDown = ControlifyBindings.GUI_NAVI_DOWN.on(controller)
if (guiNaviRight.digitalNow() && (repeatAvailable || !guiNaviRight.digitalPrev())) {
componentManager.right()
holdRepeatHelper.reset()
} else if (guiNaviLeft.digitalNow() && (repeatAvailable || !guiNaviLeft.digitalPrev())) {
componentManager.left()
holdRepeatHelper.reset()
} else if (guiNaviUp.digitalNow() && (repeatAvailable || !guiNaviUp.digitalPrev())) {
componentManager.up()
holdRepeatHelper.reset()
} else if (guiNaviDown.digitalNow() && (repeatAvailable || !guiNaviDown.digitalPrev())) {
componentManager.down()
holdRepeatHelper.reset()
} else if (state.isButtonDown(GamepadInputs.DPAD_RIGHT_BUTTON) && (repeatAvailable || !prevState.isButtonDown(GamepadInputs.DPAD_RIGHT_BUTTON))) {
componentManager.right()
holdRepeatHelper.reset()
} else if (state.isButtonDown(GamepadInputs.DPAD_LEFT_BUTTON) && (repeatAvailable || !prevState.isButtonDown(GamepadInputs.DPAD_LEFT_BUTTON))) {
componentManager.left()
holdRepeatHelper.reset()
} else if (state.isButtonDown(GamepadInputs.DPAD_UP_BUTTON) && (repeatAvailable || !prevState.isButtonDown(GamepadInputs.DPAD_UP_BUTTON))) {
componentManager.up()
holdRepeatHelper.reset()
} else if (state.isButtonDown(GamepadInputs.DPAD_DOWN_BUTTON) && (repeatAvailable || !prevState.isButtonDown(GamepadInputs.DPAD_DOWN_BUTTON))) {
componentManager.down()
holdRepeatHelper.reset()
}
}
override fun render(controller: ControllerEntity, graphics: GuiGraphics, tickDelta: Float) {
if (!controller.genericConfig().config().showScreenGuides) return
val manager = (screen as? ControllerScreen)?.getComponentManager() ?: return
if (!manager.active) return
val minecraft = Minecraft.getInstance()
val guideList = manager.buttonGuides.filter { it.condition.invoke() }
val font = minecraft.font
val leftLinesBuilder = PrecomputedLines.Builder()
val rightLinesBuilder = PrecomputedLines.Builder()
guideList.forEach { guide ->
val builder = if (guide.rightAligned) rightLinesBuilder else leftLinesBuilder
val glyphAfter = font.isBidirectional.xor(guide.rightAligned)
val text = Component.empty()
.append(if (glyphAfter) guide.text else typeToGlyth[guide.type]!!.invoke())
.append(" ")
.append(if (!glyphAfter) guide.text else typeToGlyth[guide.type]!!.invoke())
val ruleNameWidth: Int = font.width(guide.text)
val width = font.width(text)
val glyphWidth = width - ruleNameWidth
val backgroundLeft = if (glyphAfter) 0 else glyphWidth
val backgroundRight = backgroundLeft + ruleNameWidth
val height = BindingFontHelper.getComponentHeight(font, typeToGlyth[guide.type]!!.invoke())
builder.addLine(text, width, height, backgroundLeft, backgroundRight)
}
val method = GuideRenderer::class.java.declaredMethods.first { it.name == "renderLines" } //We love Java Reflections
method.isAccessible = true
method.invoke(null,graphics, leftLinesBuilder.build(), font, minecraft.window.guiScaledWidth, minecraft.window.guiScaledHeight, false, false, true)
method.invoke(null,graphics, rightLinesBuilder.build(), font, minecraft.window.guiScaledWidth, minecraft.window.guiScaledHeight, false, true, true)
}
}
@@ -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.client.integration.controlify.gui
import com.cobblemon.mod.common.client.gui.interact.wheel.InteractWheelGUI
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint
import dev.isxander.controlify.bindings.ControlifyBindings
import dev.isxander.controlify.controller.ControllerEntity
import dev.isxander.controlify.controller.haptic.HapticEffects
import dev.isxander.controlify.screenop.ScreenControllerEventListener
import dev.isxander.controlify.screenop.ScreenProcessor
import dev.isxander.controlify.virtualmouse.VirtualMouseBehaviour
import net.minecraft.client.gui.screens.Screen
import net.minecraft.util.Mth
import kotlin.math.abs
// Lots of this is taken from the RadialMenu in Controlify to make this screen feel consistent with it
class InteractWheelProcessor(val screen: InteractWheelGUI) : ScreenProcessor<Screen>(screen), ScreenControllerEventListener {
private var selectedButton = -1
override fun onWidgetRebuild() {}
override fun virtualMouseBehaviour(): VirtualMouseBehaviour {
return VirtualMouseBehaviour.DISABLED
}
override fun onControllerUpdate(controller: ControllerEntity) {
if (ControlifyBindings.GUI_BACK.on(controller).justPressed()) {
screen.onClose()
return
}
// If not created from entrypoint then something is very wrong
val x = CobblemonControlifyEntrypoint.INTERACT_WHEEL_RIGHT_AXIS!!.on(controller).analogueNow() -
CobblemonControlifyEntrypoint.INTERACT_WHEEL_LEFT_AXIS!!.on(controller).analogueNow()
val y = CobblemonControlifyEntrypoint.INTERACT_WHEEL_DOWN_AXIS!!.on(controller).analogueNow() -
CobblemonControlifyEntrypoint.INTERACT_WHEEL_UP_AXIS!!.on(controller).analogueNow()
val threshold = controller.input().orElseThrow().config().config().buttonActivationThreshold
if (abs(x) >= threshold || abs(y) >= threshold) {
val angle = Mth.wrapDegrees(Mth.RAD_TO_DEG * Mth.atan2(y.toDouble(), x.toDouble()) - 90f) + 180f
val buttonCount = screen.buttons.size
val each = 360f / buttonCount
val newSelected = Mth.floor((angle + each / 2f) / each) % buttonCount
if (newSelected != selectedButton) {
selectedButton = newSelected
controller.hdHaptics().ifPresent { haptics -> haptics.playHaptic(HapticEffects.NAVIGATE) }
}
for (button in 0..<buttonCount) {
val selected = button == selectedButton
screen.buttons[button].isFocused = selected
}
} else if (selectedButton != -1) {
if (screen.buttons[selectedButton].isEnabled) {
screen.buttons[selectedButton].onPress()
}
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.integration.controlify.gui.component
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.screens.Screen
import kotlin.reflect.typeOf
open class ControllerComponent {
var left: () -> ControllerComponent? = { null }
var right: () -> ControllerComponent? = { null }
var up: () -> ControllerComponent? = { null }
var down: () -> ControllerComponent? = { null }
var onPress: (() -> Unit)? = null
var onSpecial1: () -> Unit = {}
var onSpecial2: () -> Unit = {}
var onSelect: () -> Unit = {}
var onUnselect: () -> Unit = {}
var onFocus: () -> Unit = {}
var onUnfocus: () -> Unit = {}
var associatedGUIElement: GuiEventListener? = null
fun pairLeft(leftComponent: ControllerComponent?) {
left = { leftComponent }
leftComponent?.right = { this }
}
fun pairRight(rightComponent: ControllerComponent?) {
right = { rightComponent }
rightComponent?.left = { this }
}
fun pairUp(upComponent: ControllerComponent?) {
up = { upComponent }
upComponent?.down = { this }
}
fun pairDown(downComponent: ControllerComponent?) {
down = { downComponent }
downComponent?.up = { this }
}
}
@@ -0,0 +1,163 @@
/*
* 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.client.integration.controlify.gui.component
import com.cobblemon.mod.common.api.text.text
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.components.Button
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.resources.ResourceLocation
import net.minecraft.sounds.SoundEvents
class ControllerComponentManager(val screen: Screen) {
var active: Boolean = false
get() = field
set(value) {
if (field != value) {
field = value
onActiveChange.invoke(value)
}
if (mainScreenManager()) {
if (!value) {
screen.focused = null
focused?.onUnfocus?.invoke()
} else {
screen.focused = focused?.associatedGUIElement
focused?.onFocus?.invoke()
}
}
}
var focused: ControllerComponent? = null
get() = field
set(value) {
currentSelectedComponent = (componentList.entries.firstOrNull { it.value == value }?.key ?: "")
field?.onUnselect?.invoke()
if (active) { field?.onUnfocus?.invoke() }
field = value
if (active) {
field?.associatedGUIElement.let { screen.focused = it }
field?.onFocus?.invoke()
}
field?.onSelect?.invoke()
}
val componentList: MutableMap<String, ControllerComponent> = mutableMapOf()
var currentSelectedComponent = ""
var onTabNav: (Boolean) -> Unit = {}
var onSecondaryTabNav: (Boolean) -> Unit = {}
var onBack: () -> Unit = {
screen.onClose()
}
var onStart: () -> Unit = {}
var onActiveChange: (Boolean) -> Unit = {}
var onNoneSelected: () -> Boolean = { false }
var onRHorizontal: (Boolean) -> Unit = {}
var onRVertical: (Boolean) -> Unit = {}
var rHorizontalState: Int = 0
var rVerticalState: Int = 0
var openKeyboard: (EditBox) -> Unit = {}
val buttonGuides = mutableListOf<ButtonGuide>()
var getVisual: (ResourceLocation) -> Component = { "".text() }
var onPress: (() -> Unit)? = null
var onSpecial1: () -> Unit = { focused?.onSpecial1?.invoke() }
var onSpecial2: () -> Unit = { focused?.onSpecial2?.invoke() }
fun left(): Boolean {
if (focused == null && onNoneSelected.invoke()) return false
focused?.left?.invoke()?.let {
focused = it
return false
}
return true
}
fun right(): Boolean {
if (focused == null && onNoneSelected.invoke()) return false
focused?.right?.invoke()?.let {
focused = it
return false
}
return true
}
fun up(): Boolean {
if (focused == null && onNoneSelected.invoke()) return false
focused?.up?.invoke()?.let {
focused = it
return false
}
return true
}
fun down(): Boolean {
if (focused == null && onNoneSelected.invoke()) return false
focused?.down?.invoke()?.let {
focused = it
return false
}
return true
}
fun press() {
focused?.let {
if (onPress != null) {
onPress!!.invoke()
} else {
if (it.onPress != null) {
it.onPress!!.invoke()
} else {
(it.associatedGUIElement as? Button)?.onPress()
(it.associatedGUIElement as? Button)?.playDownSound(Minecraft.getInstance().soundManager)
if (it.associatedGUIElement is EditBox) {
openKeyboard.invoke(it.associatedGUIElement as EditBox)
Minecraft.getInstance().soundManager.play(
SimpleSoundInstance.forUI(
SoundEvents.UI_BUTTON_CLICK,
1.0F
)
)
}
}
}
}
}
fun special1() = onSpecial1.invoke()
fun special2() = onSpecial2.invoke()
private fun mainScreenManager(): Boolean {
return screen is ControllerScreen && screen.getComponentManager() == this
}
data class ButtonGuide(
val type: ButtonGuideType,
val condition: () -> Boolean,
val text: Component,
val rightAligned: Boolean = false
)
enum class ButtonGuideType {
PRESS,
BACK,
SPECIAL_1,
SPECIAL_2,
TAB,
SECONDARY_TAB,
START,
JOYSTICK_HORIZONTAL,
JOYSTICK_VERTICAL
}
}
@@ -0,0 +1,97 @@
/*
* 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.client.integration.controlify.gui.component
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.gui.components.AbstractWidget
import org.joml.Vector4i
interface ControllerScreen {
companion object {
private val selectionTexture = cobblemonResource("textures/gui/common/selection_box.png")
const val BASE_SIZE = 3
const val SHADOW_OFFSET = 1
const val TOTAL_SIZE = BASE_SIZE + SHADOW_OFFSET
}
fun getComponentManager(): ControllerComponentManager
fun getSelectionPosAndSize(): Vector4i? {
val manager = getComponentManager()
val focusedWidget = manager.screen.focused as? AbstractWidget ?: return null
return Vector4i(focusedWidget.x - 2, focusedWidget.y - 2, focusedWidget.width + 4, focusedWidget.height + 4)
}
fun renderSelection(matrices: PoseStack) {
if (!getComponentManager().active) return
val positioningData = getSelectionPosAndSize() ?: return
val posX = positioningData.x
val posY = positioningData.y
val width = positioningData.z
val height = positioningData.w
blitk( //NW
matrixStack = matrices,
texture = selectionTexture,
x = posX,
y = posY,
width = TOTAL_SIZE,
height = TOTAL_SIZE,
uOffset = 0,
vOffset = 0,
textureWidth = TOTAL_SIZE * 2,
textureHeight = TOTAL_SIZE * 2
)
blitk( //NE
matrixStack = matrices,
texture = selectionTexture,
x = posX + width - BASE_SIZE,
y = posY,
width = TOTAL_SIZE,
height = TOTAL_SIZE,
uOffset = 4,
vOffset = 0,
textureWidth = TOTAL_SIZE * 2,
textureHeight = TOTAL_SIZE * 2
)
blitk( //SW
matrixStack = matrices,
texture = selectionTexture,
x = posX,
y = posY + height - BASE_SIZE,
width = TOTAL_SIZE,
height = TOTAL_SIZE,
uOffset = 0,
vOffset = 4,
textureWidth = TOTAL_SIZE * 2,
textureHeight = TOTAL_SIZE * 2
)
blitk( //SE
matrixStack = matrices,
texture = selectionTexture,
x = posX + width - BASE_SIZE,
y = posY + height - BASE_SIZE,
width = TOTAL_SIZE,
height = TOTAL_SIZE,
uOffset = 4,
vOffset = 4,
textureWidth = TOTAL_SIZE * 2,
textureHeight = TOTAL_SIZE * 2
)
}
}
@@ -0,0 +1,34 @@
/*
* 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.client.integration.controlify.gui.component
class SubManagerComponent: ControllerComponent() {
var subManager: () -> ControllerComponentManager? = { null }
init {
left = { subManager.invoke()?.left(); null }
right = { subManager.invoke()?.right(); null }
up = { subManager.invoke()?.up(); null }
down = { subManager.invoke()?.down(); null }
onPress = { subManager.invoke()?.press() }
onSpecial1 = { subManager.invoke()?.special1() }
onSpecial2 = { subManager.invoke()?.special2() }
onFocus = {
subManager.invoke()?.active = true
subManager.invoke()?.let { it.screen.focused = it.focused?.associatedGUIElement }
subManager.invoke()?.focused?.onFocus?.invoke()
}
onUnfocus = {
subManager.invoke()?.active = false
subManager.invoke()?.focused?.onUnfocus?.invoke()
}
}
}
@@ -19,7 +19,6 @@ import net.minecraft.world.level.block.Block
object SetActiveTMPacketHandler : ServerNetworkPacketHandler<SetActiveTMPacket> {
override fun handle(packet: SetActiveTMPacket, server: MinecraftServer, player: ServerPlayer) {
val menu = player.containerMenu as? TMMachineMenu ?: return
val inventory = menu.inventory ?: return
val tmData = Cobblemon.playerDataManager.getTMData(player)
if (packet.tm != null && (!packet.tm.isPassivelyObtained() && packet.tm.id !in tmData.learnedTMs)) {
@@ -30,9 +29,17 @@ object SetActiveTMPacketHandler : ServerNetworkPacketHandler<SetActiveTMPacket>
blockEntity.activeMove = packet.tm?.moveName?.name ?: ""
blockEntity.setChanged()
player.level().sendBlockUpdated(blockEntity.blockPos, blockEntity.blockState, blockEntity.blockState, Block.UPDATE_CLIENTS)
}
inventory.setChanged()
menu.broadcastChanges()
blockEntity.burnActive = false
blockEntity.burnProgress = 0
player.level().players().forEach { thePlayer -> //Update all players with matching menu/blockentity
val tmmMenu = thePlayer.containerMenu as? TMMachineMenu ?: return@forEach
if (tmmMenu.tmMachineEntity == blockEntity) {
tmmMenu.currentlySelectedTM = packet.tm
tmmMenu.broadcastChanges()
}
}
}
}
}
@@ -0,0 +1,21 @@
/*
* 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.client.net.gui
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
import com.cobblemon.mod.common.net.messages.client.ui.SynchronizeActiveTMPacket
import net.minecraft.client.Minecraft
object SynchronizeActiveTMHandler: ClientNetworkPacketHandler<SynchronizeActiveTMPacket> {
override fun handle(packet: SynchronizeActiveTMPacket, client: Minecraft) {
val tmmMenu = client.player?.containerMenu as? TMMachineMenu ?: return
tmmMenu.clientSetSelectedTM(packet.tm)
}
}
Loaded 100 of 1122 files, more files were not shown because too many files have changed in this diff. Show more