Compare commits

..
Author SHA1 Message Date
Sterrezo b2f002d630 Update PosableState.kt 2026-10-03 14:44:28 +02:00
Polymeta 5b9d5cbb55 [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-02 08:34:22 +01:00
Polymeta ff890307c2 Merge branch 'feature/change-the-rules' into 'main'
change how item interaction evolutions are processed

Closes #1864 and #1874

See merge request cable-mc/cobblemon!2399
2026-10-02 07:07:29 +00:00
Polymeta dda9d3e790 [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-02 08:02:57 +01:00
Polymeta 1acc02a2ec Merge branch 'more-block-noises' into 'main'
Added sounds for liquid bottle, pill bottle and dice blocks

See merge request cable-mc/cobblemon!2441
2026-10-02 06:56:17 +00:00
Jackson Bowes 9a036a4c01 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2439
2026-10-01 22:48:51 -05:00
Sterrezo 3cea6dd175 Forgot powders 2026-10-01 19:03:52 +02:00
Sterrezo 2fa053f833 Update CHANGELOG.md 2026-10-01 18:55:35 +02:00
Sterrezo ea4f858919 Added sounds for liquid bottle, pill bottle and dice blocks 2026-10-01 18:53:06 +02:00
Polymeta d86021e2a1 Merge branch 'fix/safari-ball-catchrate' into 'main'
fix: change safari ball catchrate modifier

See merge request cable-mc/cobblemon!2440
2026-10-01 05:26:45 +00:00
Jakotens 79faadef59 Slight texture updates to Sudowoodo.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-30 19:54:48 -06:00
Gitoido 9b6629db7f chore: update changelog skip-ci 2026-10-01 02:47:45 +03:00
Gitoido d151a1ce65 fix: change safari ball catchrate modifier
Changed safari ball catchrate modifier to properly account for battle status
2026-10-01 02:44:20 +03:00
ewwww d9ca7b483d tweaked sudowoodo textures 2026-09-30 19:40:29 -04:00
Instrafe 708f7fc1b8 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-30 18:59:11 -04:00
Instrafe 621db85845 The evolution knows where it is because it knows where it isn't 2026-09-30 18:59:01 -04:00
Instrafe b09066c580 Added negate tags to regional and Vivillon evolutions to prevent biome overlap 2026-09-30 18:58:23 -04:00
ewwww 7c45f28827 ekans remodel + animations 2026-09-30 13:39:45 -04:00
ewwww 9c1b2cae22 vikavolt fixed placeholder animations 2026-09-30 08:59:25 -04:00
ewwww 16b2f81bd4 arbok animations 2026-09-30 08:57:47 -04:00
Jackson Bowes 108f0e3d88 Merge branch 'pokemon-return-update' into 'main'
Pokeball Recall and Capture FX Improvement

See merge request cable-mc/cobblemon!2317
2026-09-29 19:44:15 -05:00
Plastered_Crab 4344c127eb Pokeball Recall and Capture FX Improvement 2026-09-29 19:44:15 -05:00
Polymeta 5955547c6f Merge branch 'evolution-tree' into 'main'
Evolution tree

See merge request cable-mc/cobblemon!2385
2026-09-29 10:14:09 +00:00
lion 7a3a1cb8de Evolution tree 2026-09-29 10:14:09 +00:00
Polymeta 1674cb0913 fix typo
Closes #2115
2026-09-29 06:44:27 +00:00
Polymeta 152ce4e923 Merge branch 'shoulder-mount-armor-clipping' into 'main'
Shoulder Mount Enhancements

See merge request cable-mc/cobblemon!2410
2026-09-29 06:42:36 +00:00
Breezyasha 3a6d024217 Shoulder Mount Enhancements 2026-09-29 06:42:36 +00:00
Polymeta f2fcc5ba79 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

Closes #2106

See merge request cable-mc/cobblemon!2428
2026-09-28 20:25:03 +00:00
RedGenesectNinja db0fb8e287 Added implemented flag to Tympole, Palpitoad and Seismitoad 2026-09-28 15:06:55 -05:00
Jerzykv dae36a67d1 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-28 19:11:43 +02:00
Jakotens 6f99862288 Fixed some small texture issues on Pidgeot.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-28 19:11:27 +02:00
Jerzykv 0377747e1e implemented flag 2026-09-28 19:11:26 +02:00
Polymeta 6bc974f90f Merge branch 'keyframe-super-ghostbusters' into 'main'
Fix for doubled effect keyframes

Closes #2107

See merge request cable-mc/cobblemon!2436
2026-09-28 13:14:51 +00:00
Sterrezo aca53de259 Update CHANGELOG.md 2026-09-28 14:50:24 +02:00
Sterrezo 41bde38727 Fixed effect keyframes sometimes getting doubled probably 2026-09-28 14:49:06 +02:00
Polymeta a4b7ee3b38 follow-up fix from registry change
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-28 09:01:58 +01:00
Jakotens 154b653c57 Fixed some small texture issues on Pidgeot.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-27 17:33:09 -06:00
Jerzykv 04489bc969 changelog 2026-09-27 12:39:00 +02:00
Jerzykv 25499b3e7a Fixed #2106 2026-09-27 12:37:49 +02:00
Instrafe bb6bda54dc Updated Changelog 2026-09-26 14:17:30 -04:00
Instrafe 9f3dbdc2d8 Implemented remodel for Shroomish 2026-09-26 14:13:33 -04:00
Jakotens 932148338c Added animations for Treecko and Bonsly. Added remodel and animations for Sudowoodo.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-25 23:05:21 -06:00
Polymeta a0e732dc6e Merge branch 'perf/build-speed' into 'main'
Speed up the Gradle build

See merge request cable-mc/cobblemon!2429
2026-09-24 14:35:25 +00:00
Jerzykv 309f674853 Implemented Minior 2026-09-24 13:38:30 +02:00
terrainwax ea20c2aa4c CI: don't upload dev-shadow jars 2026-09-24 10:43:07 +02:00
terrainwax da5272c473 Speed up the Gradle build
- Exclude assets/data (~250MB) from sources jars
- Limit licenser to .kt/.java so it doesn't scan ~29k resource files
- Enable the Gradle daemon and build cache
- The versioning plugin runs a full JGit status (~28s on this repo) in every project replace by ou own helper that use git local or CI env variable
2026-09-24 10:04:35 +02:00
Jerzykv dc705c7720 Fixed Alolan Diglett's cry 2026-09-24 09:48:23 +02:00
Polymeta a785e574e0 Merge branch 'feature/data-registries' into 'main'
26.1.2 backport of dataregistry

See merge request cable-mc/cobblemon!2285
2026-09-22 21:08:14 +00:00
Polymeta 4b236b0268 Merge branch 'feature/remember-your-tm' into 'main'
load features a lot sooner to avoid form switching

Closes #2085

See merge request cable-mc/cobblemon!2406
2026-09-22 21:07:44 +00:00
Polymeta 1cd86ae5d6 small fixes
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-22 16:43:33 +01:00
Polymeta b7489a0b14 tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-22 15:32:25 +01:00
Polymeta f7554425dc Merge branch 'tm-shelf-mipmap-correction' into 'main'
Fix mip map of TM shelf disc textures to be 16x16

Closes #2081

See merge request cable-mc/cobblemon!2398
2026-09-22 14:26:41 +00:00
ewwww 9bfc577444 oddish, gloom, vileplume, and bellossom remodels + animations 2026-09-22 04:35:07 -04:00
Polymeta 858d2e908a I tidied up too well
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 22:12:18 +01:00
Polymeta b61e0b0c82 tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 22:11:43 +01:00
Polymeta 4d9a6e71c9 use correct texture for upgrade and dubious disc
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 22:07:52 +01:00
Plastered_Crab a0010de504 Merge remote-tracking branch 'origin/tm-shelf-mipmap-correction' into tm-shelf-mipmap-correction
# Conflicts:
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/dubious_disc.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_11.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_13.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_5.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_blocks.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_cat.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_chirp.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_creator.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_creator_music_box.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_far.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_mall.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_mellohi.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_otherside.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_pigstep.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_precipice.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_relic.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_stal.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_strad.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_unknown.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_wait.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_ward.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/technical_machine.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/technical_machine_base.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/technical_machine_overlay.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/upgrade.png
2026-09-21 10:34:36 -07:00
Plastered_Crab cb73a30e23 changeloguwu 2026-09-21 10:18:50 -07:00
ewwww 3ca6d350fe these were forgotten 2026-09-21 11:40:14 -04:00
Instrafe 2997ce8457 Updated Changelog 2026-09-21 09:54:23 -04:00
Instrafe ff279d8310 Poliwag and Politoed now have remodels + Implemented gender differences for Politoed 2026-09-21 09:53:53 -04:00
Polymeta 9a7536b068 you were not supposed to be there
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 14:42:23 +01:00
Polymeta bd973e702e per type texture for disc shelf tms
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 14:40:24 +01:00
Plastered_Crab ede1851d34 Fix mip map of TM shelf disc textures to be 16x16 2026-09-21 14:28:14 +01:00
Polymeta cdb8ee0d8e [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 12:07:55 +01:00
Polymeta e7d86a382c Merge branch 'dex_molang' into 'main'
Molang: Individual Dex molang

See merge request cable-mc/cobblemon!2273
2026-09-21 09:16:10 +00:00
Konoha D eb911df830 Molang: Individual Dex molang 2026-09-21 09:16:10 +00:00
Polymeta 1c30acc53c Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2417
2026-09-21 09:14:23 +00:00
Polymeta 32b0699235 load features a lot sooner to avoid form switching
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 10:13:59 +01:00
Polymeta e9c6580230 fix merge conflicts
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 10:13:27 +01:00
Polymeta f893eb48fd fix some errors I forgot about
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:38:51 +01:00
Polymeta 232e2f18bf MR feedback
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:38:51 +01:00
Polymeta c266c99710 26.1.2 backport of dataregistry
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:38:49 +01:00
Polymeta 2a0e8f6d47 [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:32:44 +01:00
Polymeta e6bd8ec529 Merge branch 'neoforge-reload-fix' into 'main'
Neoforge resource fix

See merge request cable-mc/cobblemon!2333
2026-09-21 08:30:54 +00:00
Crater 911a95e873 Neoforge resource fix 2026-09-21 08:30:53 +00:00
Polymeta 447cfc2ae5 Merge branch 'useful-misc-functions' into 'main'
Smoother ride rotations.

See merge request cable-mc/cobblemon!2407
2026-09-21 08:28:50 +00:00
Polymeta 8dfeb0d260 Merge branch 'no-glass-bottles-in-those' into 'main'
Fixed Mochis and Feathers dropping glass bottles when they shouldn't

See merge request cable-mc/cobblemon!2425
2026-09-21 08:24:28 +00:00
Polymeta caa7b19833 Merge branch 'Snowy-Burrow-Spawns-Fix' into 'main'
Fix Snowy burrows spawns and some floating water in one difting iceberg

See merge request cable-mc/cobblemon!2423
2026-09-21 08:23:17 +00:00
Polymeta 91d6fb62ba dedicated handling for forced evolutions, those take priority and only one at a time
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:18:06 +01:00
Polymeta 8f4a5b2015 fix typo in changelog 2026-09-21 09:18:06 +01:00
Polymeta 07604c9a3b change how item interaction evolutions are processed
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:18:06 +01:00
Polymeta 64dd8ae193 fix license header
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:17:44 +01:00
Polymeta 156ee6f290 new negate requirement
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 08:23:04 +01:00
Gertimoshka fe433d1417 Fixed Mochis and Feathers dropping glass bottles when they shouldn't 2026-09-20 20:10:25 +02:00
Hiroku 0dc662d295 Merge branch 'starter-screen-fixes' into 'main'
Fixes minor but annoying starter screen fixes

See merge request cable-mc/cobblemon!2424
2026-09-20 09:33:15 +00:00
Gertimoshka 081e370bfd User friendlier changelog 2026-09-20 11:13:08 +02:00
Gertimoshka 3ef0567287 simplifications 2026-09-20 11:05:26 +02:00
Gertimoshka 3cad9e5481 Forgor the Changelog 2026-09-20 11:01:52 +02:00
Gertimoshka dc8b7908be Fixed impropper rendering culling of entries, and random starter not being part of height calculation of the category 2026-09-20 10:52:15 +02:00
Slippy 85085a1eca Decaying Crypt tombstones now will generate with TMs in the gidled chest 2026-09-19 22:40:20 -05:00
Slippy bff59dee6e Fix Snowy burrows spawns and some floating water in one difting iceberg 2026-09-19 22:01:17 -05:00
Hiroku bb5828531a Merge branch 'type-gem-fix' into 'main'
Fix typo and update changelog.

See merge request cable-mc/cobblemon!2422
2026-09-20 02:29:36 +00:00
Deltric cd08e98c28 Fix typo and update changelog. 2026-09-19 21:09:32 -05:00
Polymeta bf7d40c9b5 Merge branch 'small-jvm-fix' into 'main'
Added JvmField to Some values in Cobblemon Blocks cause it was forgotten

See merge request cable-mc/cobblemon!2419
2026-09-18 18:30:24 +00:00
Polymeta 5abb3d7f97 Merge branch 'fix/molang-delete-variables-fn' into 'main'
fix: reintroduce `delete_variables` general molang fn

See merge request cable-mc/cobblemon!2421
2026-09-18 18:29:23 +00:00
Gitoido 975ad8186a chore: dangling variable 2026-09-18 18:03:41 +00:00
Gitoido 26b6419f04 fix: reintroduce delete_variables general molang fn 2026-09-18 18:00:44 +00:00
Jerzykv 331f7ca425 Implemented Burmy, Wormadam and Mothim 2026-09-18 07:15:33 +02:00
RedGenesectNinja 71c9ac0ff0 Updated changelog 2026-09-17 15:15:20 -05:00
GenotypeXD 1404cc0ab5 Sun Lizards Implementation Tags 2026-09-17 15:11:31 -05:00
GenotypeXD 6ca908a91d Implemented Helioptile Line 2026-09-17 13:05:12 -05:00
Jerzykv 0948b8da9a Fixed Dragonair not using its idle correctly 2026-09-17 19:17:29 +02:00
Jerzykv 22e04a53b3 changelog 2026-09-17 18:39:10 +02:00
Jerzykv df3217b41b Fixed Bramblin always sleeping 2026-09-17 18:38:48 +02:00
Jerzykv 9ff15a1913 Flygon poser fix 2026-09-17 06:43:03 +02:00
Aram 89e43945a3 Edit CobblemonBlocks.kt 2026-09-16 14:25:26 +02:00
Aram bd8dcddf35 Edit CobblemonBlocks.kt 2026-09-16 14:24:31 +02:00
Polymeta dd372f3160 Merge branch 'recipe-booking' into 'main'
Recipe Book Tweaks

Closes #2088

See merge request cable-mc/cobblemon!2418
2026-09-16 10:03:42 +00:00
dhi_holo c56aab28d4 Recipe Book Tweaks
Closes #2088 as well as fixes a lot of similar issues for gameplay consistency
2026-09-16 18:41:05 +10:00
RedGenesectNinja ca58801794 Implemented Tympole, Palpitoad and Seismitoad 2026-09-15 23:36:40 -05:00
Instrafe 38fa5e2628 Updated Changelog 2026-09-15 16:46:25 -04:00
Instrafe fae1ae7124 Implemented Absol's remodel 2026-09-15 16:45:59 -04:00
Jerzykv 3ec5f76c73 Implemented Deerling and Sawsbuck remodels 2026-09-15 14:15:37 +02:00
ewwww 5bb8aaba55 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-15 05:05:08 -04:00
ewwww 23ba41e981 wiglett + wugtrio underwater tweaks 2026-09-15 05:04:40 -04:00
Instrafe 29b049452b Updated Changelog 2026-09-14 19:24:03 -04:00
Instrafe 0b0f665804 Glimmet and Glimmora now have implemented remodels 2026-09-14 19:23:30 -04:00
Polymeta 3cdf28ffdd Merge branch 'Chorus-Briar-Fix' into 'main'
I'm never doing an update where I have to keep track of 650+ structure files again

See merge request cable-mc/cobblemon!2414
2026-09-14 21:18:29 +00:00
Kenji_64 5d53fba2ce Fixed Rockruff line missing blinks 2026-09-14 20:47:40 +02:00
Slippy c6fb18ea51 I'm never doing an update hwere I have to keep track of 650+ structure files again 2026-09-14 10:48:01 -05:00
Polymeta 2d851cb134 Merge branch 'tm-crafting-corrections' into 'main'
TM Recipes

See merge request cable-mc/cobblemon!2413
2026-09-14 13:03:40 +00:00
dhi_holo f685be0aab TM Recipes
Corrected two incorrect TM recieps, both in the jsons and the spreadsheets
2026-09-14 16:30:06 +10:00
FrankTheFarmer 14cc851562 Tweaked Mimikyus poser. Hopefully fixes an issue nobody can replicate 😭 2026-09-14 09:46:55 +10:00
FrankTheFarmer 7223d1fd68 Merge branch 'main' into implement_pokemon 2026-09-14 09:21:43 +10:00
Polymeta 6ab5ad52ec Merge branch 'typegem-advancement-trigger' into 'main'
Adds typegem item advancement trigger

Closes #2086

See merge request cable-mc/cobblemon!2411
2026-09-13 20:58:08 +00:00
PieDuck 079b2ae766 Merge branch 'typegem-advancement-trigger' of https://gitlab.com/dmnaru/cobblemon into typegem-advancement-trigger 2026-09-13 21:51:29 +01:00
PieDuck 34eea827e7 changelog 2026-09-13 21:51:09 +01:00
PieDuck 06c6ee5a9c Merge branch cobblemon:main into typegem-advancement-trigger 2026-09-13 21:37:22 +01:00
PieDuck 54748c20ea Add type gem item advancement trigger 2026-09-13 21:36:18 +01:00
Instrafe 75f52e4cf4 Updated Changelog 2026-09-13 11:10:48 -04:00
Instrafe 55446dd4b1 Implemented remodels for Binacle and Barbaracle 2026-09-13 11:08:58 -04:00
Polymeta 02e63b6fc8 Merge branch 'gem-block-alert' into 'main'
Fixed Missing Recipe Advancements

See merge request cable-mc/cobblemon!2409
2026-09-13 14:41:13 +00:00
Polymeta 3aebcf66ec rename lang file to match convention 2026-09-13 14:26:44 +00:00
dhi_holo 49a5f716b7 Fixed Missing Recipe Advancements
Why were the advancements in the recipes..?
2026-09-13 22:05:44 +10:00
ewwww 8b825d558f cacnea & cacturne remodels + animations 2026-09-13 06:28:57 -04:00
jackowes 9a7f97f11f changelog 2026-09-12 21:52:56 -05:00
jackowes 95b8d62e4a Fix issue with quat spring and replace orientation controller tick slerp with quat spring 2026-09-12 21:45:26 -05:00
Hiroku 0bd7b4dab3 Merge branch 'decorative-items-restored' into 'main'
Decorative Items Project Returns

See merge request cable-mc/cobblemon!2343
2026-09-13 02:24:00 +00:00
Hiroku 8cbada0cf5 Fix changelog whackiness [skip-ci] 2026-09-13 12:23:40 +10:00
Hiroku f0784fec2d Merge remote-tracking branch 'origin/main' into decorative-items-restored 2026-09-13 12:21:54 +10:00
Hiroku cfe0c5e0a5 Snap on 2026-09-13 11:27:40 +10:00
Hiroku 0bc5bb5caa Snap off and changelog updates 2026-09-13 08:03:56 +10:00
Hiroku 3330941a2c Merge remote-tracking branch 'origin/lang-updates' 2026-09-13 07:58:31 +10:00
Polymeta 79ea082d7e Merge branch '1.8.1-final-engi-fixes' into 'main'
1.8.1 final engi fixes

See merge request cable-mc/cobblemon!2404
2026-09-12 20:09:26 +00:00
Kenji_64 3d78929ff0 Entire Applin line has placeholder anims now! + other Applin related minor fixes 2026-09-12 21:36:12 +02:00
Kenji_64 5d94b03f7a Fixed Lucario's aura particles not emitting 2026-09-12 21:32:48 +02:00
Kenji_64 587305eb52 Added Fickle Beam's double damage text to en_us 2026-09-12 21:28:52 +02:00
Polymeta e363ce5e0e Merge branch 'Log-Spam-Fix' into 'main'
Fixed console spam with carved_ice_spikes. Fixed air pockets in deep sea spires

See merge request cable-mc/cobblemon!2403
2026-09-12 17:14:50 +00:00
Slippy e49b704200 Fixed console spam with carved_ice_spikes. Fixed air pockets in deep sea spires 2026-09-12 11:39:48 -05:00
Polymeta ec8b35860c Merge branch '1.8.1-implementation-fixes' into 'main'
fixed sableye's shoulder mount animations (thanks @kiteman.exe from discord)

See merge request cable-mc/cobblemon!2402
2026-09-12 06:40:07 +00:00
ewwww 74c49bf2da fixed sableye's shoulder mount animations (thanks @kiteman.exe from discord) 2026-09-11 22:39:37 -04:00
Plastered_Crab 4354c3fb03 Fix mip map of TM shelf disc textures to be 16x16 2026-09-10 11:56:30 -07:00
Gustavo Puerari c03e7d9ed3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.8% (9175 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 18:30:26 +00:00
Kenji_64 f25a676bd5 Changelog 2026-09-10 13:26:54 +02:00
Kenji_64 a9906762a0 Fixed head_ai and removed battle condition on Lycanroc (all forms) 2026-09-10 13:23:03 +02:00
Kenji_64 7b5b88eca2 Added head movement to Archaludon and added missing sleep poses to Duraludon line 2026-09-10 13:22:34 +02:00
Kenji_64 291682ba52 Added Diglett line faints (which were in the files, but not implemented) 2026-09-10 12:50:56 +02:00
Kenji_64 546d403b45 Fixed Kingambit waddling its legs around when moving 2026-09-10 12:50:22 +02:00
Gustavo Puerari 2652e53e0c Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.0% (9097 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 10:03:46 +00:00
Gustavo Puerari b423fb1c46 Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.4% (9040 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 00:39:17 +00:00
Gustavo Puerari 8ae26a268a Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.1% (9016 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 00:11:34 +00:00
Gustavo Puerari 39b522abcc Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.0% (9005 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 00:05:18 +00:00
Gustavo Puerari 9bcfcef4fd Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.8% (8983 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-09 23:29:29 +00:00
Gustavo Puerari 60ac2d8058 Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.4% (8947 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-09 23:06:46 +00:00
Gustavo Puerari 9366d28141 Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.4% (8946 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-09 22:24:00 +00:00
阿鱼 75b48196b7 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (9472 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-09-09 14:13:55 +00:00
FrankTheFarmer c9f40e32a1 Changedog 2026-09-09 20:42:39 +10:00
FrankTheFarmer d642b8dd07 Candle dog's dad 🪦 🐶 2026-09-09 20:41:42 +10:00
FrankTheFarmer 549a11116e Candle dog. Dog with the candle. We put candle on him 🕯🐶 2026-09-09 20:39:45 +10:00
Polymeta b5468a4a4f Merge branch 'main' into implement_pokemon 2026-09-09 09:06:51 +01:00
ewwww c5d278ccb7 spheal. 2026-09-09 03:42:50 -04:00
ewwww bf5e15de45 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-09 03:40:21 -04:00
Jerzykv f4af417267 Fixed Alolan Raticate's cry 2026-09-09 03:40:14 -04:00
FrankTheFarmer 69c04acaeb ⚡ Vikavolt 2026-09-09 03:40:14 -04:00
FrankTheFarmer 83a6bb188f 🚎Charjabug💖 2026-09-09 03:40:14 -04:00
FrankTheFarmer 494a8b5e91 🐛Grubbin 2026-09-09 03:40:14 -04:00
FrankTheFarmer c8021f2f08 Im putting bugs into cobblemon changelog 2026-09-09 03:40:14 -04:00
Jerzykv 8cdf3af047 Ampharos now has eye locators 2026-09-09 03:40:14 -04:00
ewwww c90a24e1ab switcheroo part two 2026-09-09 03:40:14 -04:00
FrankTheFarmer 83e3cfc34e Placeholder idle animation for snover 2026-09-09 03:40:14 -04:00
FrankTheFarmer 133dd4e5b7 🏔 Added snover line 🌲 2026-09-09 03:40:13 -04:00
FrankTheFarmer a8cca14b86 Obamasnow 2026-09-09 03:40:13 -04:00
FrankTheFarmer 9b0f356fa4 Donald Snover 2026-09-09 03:40:13 -04:00
FrankTheFarmer f2b0a102b6 What if we started implementing pokemon rn rn 2026-09-09 03:40:13 -04:00
FrankTheFarmer a7d01b230a Merge branch 'main' into implement_pokemon 2026-09-09 16:04:20 +10:00
jackowes 2a7db4b9b8 bring in util functions 2026-09-08 20:15:54 -05:00
Jerzykv 77e5f2501d Fixed Alolan Raticate's cry 2026-09-08 19:56:35 +02:00
FrankTheFarmer e6513717bd ⚡ Vikavolt 2026-09-08 21:34:26 +10:00
FrankTheFarmer 0e0c98f693 🚎Charjabug💖 2026-09-08 21:32:56 +10:00
FrankTheFarmer 528bfa4e22 🐛Grubbin 2026-09-08 21:32:09 +10:00
FrankTheFarmer 83c87779d7 Im putting bugs into cobblemon changelog 2026-09-08 21:30:42 +10:00
FrankTheFarmer dc33235144 Merge branch 'refs/heads/main' into implement_pokemon 2026-09-08 21:29:01 +10:00
FrankTheFarmer 935c658c5f Merge branch 'main' into implement_pokemon 2026-09-08 17:59:22 +10:00
Jerzykv 0426f4fbf9 Ampharos now has eye locators 2026-09-07 08:31:09 +02:00
dhi_holo 75bc03fdcb Merge branch 'main' into decorative-items-restored 2026-09-07 15:19:27 +10:00
Watuneni iteite f65cb9538c Translated using Weblate (Japanese)
Currently translated at 100.0% (9472 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-09-06 14:57:47 +00:00
ewwww 46e771a062 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-05 22:07:28 -04:00
ewwww 4c0095844b Merge remote-tracking branch 'origin/implement_pokemon_2' into implement_pokemon 2026-09-05 22:06:39 -04:00
ewwww 10a2ef27d6 switcheroo part two 2026-08-24 15:36:02 -04:00
dhi_holo 309ff3da13 Loaded Cleanup
Added Loaded Dice, cleaned up some particles
2026-08-12 20:15:52 +10:00
dhi_holo 3af91e2a74 Further Tweaking 2026-08-12 14:26:20 +10:00
dhi_holo 2e027e3db7 Some Visual and Auditory Tweaks 2026-08-12 14:13:21 +10:00
dhi_holo fc40fb02c0 Next Batch
Never-Melt Ice, Wide, Scope, and Zoom Lens, as well as new designs for Upgrade and Dubious Disc
2026-08-12 13:52:13 +10:00
dhi_holo 2c1c0299f9 SPHERICAL!
Adds Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball blocks
2026-08-12 12:24:21 +10:00
FrankTheFarmer 3c7637515c Placeholder idle animation for snover 2026-08-10 13:03:00 +10:00
dhi_holo 08cbbaf68c Merge branch 'main' into decorative-items-restored 2026-08-10 10:21:06 +10:00
Sterrezo 6e911acf01 Sound tweaks
Swapped pot sound types from tumblestone to decorated pots and rock sounds from tumblestone to deepslate.
2026-08-09 12:20:13 +02:00
dhi_holo f6d82a1b28 Changelog
and some last second sound tweaks
2026-08-09 19:17:19 +10:00
dhi_holo 702126aeab Adds Throat Spray and Iron Ball
Adds in Throat Spray and Iron Ball blocks, finishes clean up of King's Rock to look a bit more consistent
2026-08-09 19:04:08 +10:00
dhi_holo df566e8fcb Wearable Hat Cleanup
Fixed King's Rock Blockstate and loot table as well

Wearable "Hat" Items can now be prevented from swapping when trying to open the interaction wheel (sneak right-click), making it not a nightmare to give certain items to Pokemon. Yippee!
2026-08-09 18:53:58 +10:00
dhi_holo c83e0f33b5 Wearable Items Mostly Working Better
Wearable Items now can either be "Blocks" or "Hats"
"Blocks" can be placed as blocks in the world, while "Hats" can be put on the player's head by right clicking them in hand
Need to add some form of override when crouching and looking at a Pokemon though
2026-08-09 17:30:49 +10:00
dhi_holo f279f936b6 Generified Plaque/Wreath/Magnet Code
Moved shared code to a singular class
2026-08-09 16:04:22 +10:00
dhi_holo f8e20bafd9 Import Cleanup 2026-08-09 12:23:19 +10:00
dhi_holo a5211ca7ff Convention Alignment
Changed all stackable item blockstates/model files to utilise the convention from vanilla (seen on blocks such as pink petals)
This commit adds no files, and removes no files, it purely renames and swaps out numbers
2026-08-09 12:06:01 +10:00
FrankTheFarmer 0a5abdb331 🏔 Added snover line 🌲 2026-08-09 11:15:45 +10:00
FrankTheFarmer 4ed7413dd2 Obamasnow 2026-08-09 11:13:17 +10:00
FrankTheFarmer 40b66c24fc Donald Snover 2026-08-09 11:12:56 +10:00
dhi_holo 2f7bedf001 Update scribblenotes - Decorative Items.txt 2026-08-08 18:05:17 +10:00
dhi_holo 44c31a9ddb Update StackableItemBlock.kt
Simplified the way the hitboxes are defined, to allow for future planned hitboxes to actually work as intended
2026-08-08 17:33:34 +10:00
FrankTheFarmer 20a35de3d8 What if we started implementing pokemon rn rn 2026-08-08 14:58:20 +10:00
dhi_holo cfc8a15db1 Update scribblenotes - Decorative Items.txt 2026-08-06 18:35:29 +10:00
dhi_holo d5ba22a86c Removal of Unneeded Lines
deprecate deez nuts
2026-08-06 15:01:34 +10:00
dhi_holo a38505f1c5 Update scribblenotes - Decorative Items.txt
Documenting ideas and plans for future me
2026-08-05 19:28:29 +10:00
dhi_holo 3cf1ba1fb3 Tag Tweaks
Added stuff to relevant tags, removed unused tag from removed feature
2026-08-05 18:36:38 +10:00
dhi_holo 9f5d595827 Import Cleanup
Just removing some imports from reverted changes that snuck into commits
2026-08-05 18:03:06 +10:00
dhi_holo 75673f0204 Mild Polish 2026-08-05 17:09:40 +10:00
dhi_holo b43a7571ff Eject Button
Now with cleaner code, I hope this is more acceptable
2026-08-05 16:13:24 +10:00
dhi_holo b071dc04d3 Metronome
Reimplemented the Metronome, the code has been genericized to allow for more items to be implemented off the same base, although a little bit more work does need to be done there with the comparator output specifically
2026-08-05 15:34:32 +10:00
dhi_holo 12f9ff1647 Wreath, Magnet, and some tweaks to Plaques
Plaques now place the way I had originally set them up, and Galarica Wreaths along with the Magnet are back
2026-08-05 14:34:41 +10:00
dhi_holo 3256a302df Ring Target
Could maybe be polished up, but is good
2026-08-05 13:06:07 +10:00
dhi_holo edbe447477 Basic Decorations
Reimplemented Galarica Cuff, Teapots, Teacups, Weather Rocks, Sachet, Metal Powder, Quick Powder, Bright Powder, Destiny Knot, and Luminous Moss
2026-08-05 12:04:48 +10:00
Gertimoshka 2f43910368 Cleaner Implementation with less duplicate code 2026-08-04 22:21:58 +03:00
dhi_holo c2e87e4cfa Placeable Vitamins Experiment A
Need coder feedback regarding this
Makes Mochi and Feathers require clicking in the air or directly on a Pokemon, if it thinks it can place as a block it prevents usage
2026-08-04 19:25:13 +10:00
dhi_holo e40d2ee806 Create scribblenotes - Decorative Items.txt
Making this a unique commit so it's easy to reference back to later
2026-08-04 14:53:53 +10:00
dhi_holo ba74e7603b Wearables 1
Made Sharp Beak, Covert Cloak, and Shell Helmet wearable
2026-08-04 13:51:49 +10:00
dhi_holo 19c3fe6dc4 Update StackableItemBlock.kt
Fixed hitbox oversight
2026-08-04 13:35:17 +10:00
dhi_holo d0778dd124 Missing Stackables 1
Reimplemented Metal Coat, PP Up, and PP Max's placeable logic
Began work for other Vitamins to work (incomplete)
Added models, textures, blockstates, and loot tables for most incoming blocks
2026-08-04 13:16:46 +10:00
1717 changed files with 94580 additions and 26102 deletions

No files matched your search

-3
View File
@@ -45,6 +45,3 @@ common/.fabric
# MacOS
.DS_Store
/common/config/*
# Temp Files
/temp/
+3
View File
@@ -157,6 +157,9 @@ build:
paths:
- neoforge/build/libs
- fabric/build/libs
exclude:
- neoforge/build/libs/*-dev-shadow.jar
- fabric/build/libs/*-dev-shadow.jar
publish:
stage: deploy
+120 -35
View File
@@ -1,60 +1,145 @@
# Changelog
## [1.8.1 (MONTH Xth, 2026)](#1-8-1)
## [1.8.2 (MONTH xth, 2026)](#1-8-2)
### Additions
### New rideable Pokémon
- The following items can now be placed on the ground as blocks:
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
- Iron Ball, Metal Coat, Throat Spray, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
- The following items can now be placed on various surfaces:
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
- The following items can now be placed as blocks and have additional functionality:
- Ring Target, Metronome, and Eject Button.
- The following items can now be worn by Players and Pokémon:
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
- Wearable items can now be equipped by a player by holding it and right-clicking (or rebound use/interact)
- Wearable items that can be placed down are exempt from this behaviour.
- The evolution tree can now be viewed on the Pokédex, showing a Pokémon's full family line and what's needed to evolve into each stage.
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders and dice.
### Pokémon Added
#### Generation 4
- Snover
- Abomasnow
- Burmy
- Wormadam
- Mothim
#### Generation 5
- Tympole
- Palpitoad
- Seismitoad
#### Generation 6
- Helioptile
- Heliolisk
#### Generation 7
- Grubbin
- Charjabug
- Vikavolt
- Minior
#### Generation 8
- Eternatus
#### Generation 9
- Greavard
- Houndstone
### Model updates for the following Pokémon
- Sudowoodo
- Spheal
- Cacnea
- Cacturne
- Binacle
- Barbaracle
- Glimmet
- Glimmora
- Deerling
- Sawsbuck (All forms)
- Absol
- Poliwag
- Politoed
- Ekans
- Oddish
- Gloom
- Vileplume
- Bellossom
- Shroomish
### Animation updates for the following Pokémon
- Treecko (New Shoulder Mount!)
- Bonsly (New Shoulder Mount!)
- Sudowoodo
- Cacnea
- Cacturne
- Ekans
- Arbok
- Oddish
- Gloom
- Vileplume
- Bellossom
### Changes
- Updated various recipes to display in Recipe Groups within the Recipe Book
- Updated most recipes to display more logically/consistently within the Recipe Book's categories
- Make ride rotations less jittery in multiplayer.
- Changes tm shelf discs to use textures instead of tinting and overlays to line up with item display
- Blank TM can now be inserted into the Disc Shelf
- Recall animation now renders ball in hand with the player arm raised.
- Made regional and Vivillon split-evo locations now mutually exclusive
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
- Exceptions being "forced" evolutions which will continue to stop after the first one
### Fixes
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed NPCs getting their own TM progress file.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed form typo in Rockruff dusk spawn entry.
- Fixed a potential world gen stall with the Type Gem clusters.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait
- Fixed Ampharos' eyes not emitting particles
- Fixed Alolan Raticate's cry not playing
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat
- Fixed odd NeoForge crash due to a dependency being outdated. It will now flag the dependency for being outdated correctly.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed a potential world gen stall caused by Type Gem clusters.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to a having an expression mistakenly assigned as a recoil animation.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to a having an expression mistakenly assigned as a recoil animation.
- Correctly grants "They're Minerals!" advancement when planting a type gem
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??)
- Some other recipes without advancements have also been corrected
- Corrected the TM Recipes for Charge Beam and Payday
- Type Gem blocks now properly generate clusters off of the block.
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport, and you are not viewing top or bottom edges
- Random Starter option now properly handled in height calculation, so now it won't run off into other categories or be un-accessible due to scroll limits if it causes a new row
- Mochi and Feathers no longer drop glass bottles on use
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix issue
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
- Fixed Rockruff line not blinking
- Fixed Flygon sometimes using its running animation instead of walking
- Fixed Bramblin always sleeping outside of battle
- Fixed Dragonair using its battle idle as a normal idle
- Fixed Lycanroc's head movement
- Added head movement to Archaludon
- Fixed Duraludon line not having their sleep animations
- Fixed Duraludon line not having any sleep animations
- Fixed Diglett line not having their faint animations
- Fixed Kingambit moving its legs while walking
- Fixed Alolan Diglett's cry not playing
- Fixed shiny alpha Snorlax using the non-shiny texture
- Various Structure fixes
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations
- Fixed ball capture beam origin coming from behind the ball.
- Fixed Safari Ball catch rate outside of battle
### Developer
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
```json
{
"variant": "attack_defence_ratio",
"pokedexIcon": "minecraft:diamond_sword",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
}
```
- Fixed wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing pokemon to appear without entity link
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon
### Molang & Datapacks
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
### Particles
#### New Move Animations
### Localization
- Updated translations for:
- Updated translations for:
-9
View File
@@ -1,9 +0,0 @@
- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator
--- global posers? stacked posers?
--- red texture seems to be what some people wanted.
/- shoulder mounts part of it
- omni boost
- roar when player comes within reach, adjust levels
- Riding settings to control what the min scale is for a pokemon to be ridable.
+5 -2
View File
@@ -1,3 +1,6 @@
import utilities.gitBranch
import utilities.gitShortCommit
plugins {
id("cobblemon.root-conventions")
}
@@ -6,6 +9,6 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
val isSnapshot = project.property("snapshot")?.equals("true") == true
if (isSnapshot) {
val fixedBranchName = versioning.info.branch.substringAfter("/")
version = "$version-${fixedBranchName}-${versioning.info.build}"
val fixedBranchName = gitBranch().substringAfter("/")
version = "$version-${fixedBranchName}-${gitShortCommit()}"
}
+41
View File
@@ -0,0 +1,41 @@
# Changelog
## [1.8.1 (September 13th, 2026)](#1-8-1)
### Animation updates for the following Pokémon
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
### Fixes
- Fixed a potential world generation stall caused by Type Gem clusters.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed NPCs getting their own TM progress file.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
- Fixed Lucario's aura particles not emitting.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having their sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Simplified Chinese
- Japanese
+15 -6
View File
@@ -1,9 +1,8 @@
import utilities.isSnapshot
import utilities.version
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import utilities.gitBranch
import utilities.gitCommit
/*
*
@@ -72,6 +71,14 @@ tasks.withType<Test> {
}
}
// Git info lives in its own output dir (not processResources) so a new commit only rewrites this one file.
val writeGitInfo by tasks.registering(WriteProperties::class) {
destinationFile = layout.buildDirectory.file("generated/gitInfo/cobblemon-git.properties")
property("commit", gitCommit())
property("branch", gitBranch())
}
sourceSets.main { output.dir(mapOf("builtBy" to writeGitInfo), layout.buildDirectory.dir("generated/gitInfo")) }
tasks.processResources {
exclude("data/**/README.md")
}
@@ -98,11 +105,13 @@ sourceSets {
property("modid", "cobblemon")
property("version", project.version())
property("isSnapshot", if(rootProject.isSnapshot()) "true" else "false")
property("gitCommit", versioning.info.commit)
property("branch", versioning.info.branch)
System.getProperty("buildNumber")?.let { property("buildNumber", it) }
property("timestamp", OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss")) + " UTC")
}
}
}
}
// The platform jars are built from :common:jar, so the remapped common jars are only needed by `publish`
tasks.assemble {
setDependsOn(dependsOn.filterNot { (it as? TaskProvider<*>)?.name in setOf("remapJar", "remapSourcesJar") })
}
@@ -10,11 +10,13 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Final;
@@ -96,4 +98,14 @@ public class HumanoidModelMixin {
MountedPlayerRenderer.INSTANCE.animate((HumanoidModel<AbstractClientPlayer>)(Object)this, pokemonEntity, player, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
@Inject(method = "setupAnim*", at = @At("TAIL"))
public void cobblemon$poseRecallArm(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
if (!(entity instanceof AbstractClientPlayer) || entity.getVehicle() instanceof PokemonEntity) return;
float raise = RecallArmAnimationTracker.INSTANCE.raiseFactor(entity.getUUID());
if (raise <= 0f) return;
this.rightArm.xRot = Mth.lerp(raise, this.rightArm.xRot, (float) (-Math.PI / 2.0));
this.rightArm.yRot = Mth.lerp(raise, this.rightArm.yRot, 0f);
this.rightArm.zRot = Mth.lerp(raise, this.rightArm.zRot, 0f);
}
}
@@ -0,0 +1,32 @@
/*
* 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.client;
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemInHandLayer.class)
public class ItemInHandLayerMixin {
@Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true)
private void cobblemon$hideRecallHandItem(LivingEntity entity, ItemStack itemStack, ItemDisplayContext itemDisplayContext, HumanoidArm humanoidArm, PoseStack poseStack, MultiBufferSource multiBufferSource, int light, CallbackInfo ci) {
if (humanoidArm == HumanoidArm.RIGHT && RecallArmAnimationTracker.INSTANCE.isRecalling(entity.getUUID())) {
ci.cancel();
}
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
import com.cobblemon.mod.common.item.PokedexItem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
@@ -31,6 +32,9 @@ public class ItemInHandRendererMixin {
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
ci.cancel();
}
else if (interactionHand == InteractionHand.MAIN_HAND && FirstPersonRecallBallRenderer.INSTANCE.render(poseStack, multiBufferSource, j, f)) {
ci.cancel();
}
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
}
@@ -8,8 +8,12 @@ object CobblemonBuildDetails {
const val VERSION = "{{ version }}"
const val SNAPSHOT = {{ isSnapshot }}
const val GIT_COMMIT = "{{ gitCommit | default("Not Specified") }}"
const val BRANCH = "{{ branch | default("Not Specified") }}"
// Read at runtime rather than templated in, so a new commit doesn't invalidate compiled code.
private val git = java.util.Properties().apply {
CobblemonBuildDetails::class.java.getResourceAsStream("/cobblemon-git.properties")?.use(::load)
}
@JvmField val GIT_COMMIT: String = git.getProperty("commit", "Not Specified")
@JvmField val BRANCH: String = git.getProperty("branch", "Not Specified")
fun smallCommitHash(): String {
return this.GIT_COMMIT.substring(0, 7)
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.battles.TeamPreviewManager
import com.cobblemon.mod.common.CobblemonBuildDetails.smallCommitHash
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.SeasonResolver
@@ -256,7 +254,6 @@ object Cobblemon {
}
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
PCLinkManager.removeLink(it.player.uuid)
TeamPreviewManager.remove(it.player)
BattleRegistry.onPlayerDisconnect(it.player)
storage.onPlayerDisconnect(it.player)
playerDataManager.onPlayerDisconnect(it.player)
@@ -453,7 +450,6 @@ object Cobblemon {
}
PlatformEvents.SERVER_STOPPED.subscribe {
TeamPreviewManager.clear()
storage.unregisterAll(it.server.registryAccess())
playerDataManager.saveAllStores()
playerDataManager.saveExecutor.shutdown()
@@ -16,6 +16,8 @@ import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
import com.cobblemon.mod.common.block.chest.GildedChestBlock
import com.cobblemon.mod.common.block.decor.ActivatableDecorationBlock
import com.cobblemon.mod.common.block.decor.RingTargetBlock
import com.cobblemon.mod.common.block.general.BaleBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
@@ -621,37 +623,119 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
@JvmField
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val LIGHT_BALL = create("light_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{13}))
@JvmField
val SMOKE_BALL = create("smoke_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val SACHET = create("sachet", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val NEVER_MELT_ICE = create("never_melt_ice", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().friction(0.989F), DecorativeItemBlock.DecorativeItemBlockType.NEVERMELT))
@JvmField
val SCOPE_LENS = create("scope_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.SCOPE))
@JvmField
val WIDE_LENS = create("wide_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.WIDE))
@JvmField
val ZOOM_LENS = create("zoom_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.ZOOM))
@JvmField
val ICY_ROCK = create("icy_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val HEAT_ROCK = create("heat_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val SMOOTH_ROCK = create("smooth_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val DAMP_ROCK = create("damp_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x6))
@JvmField
val DESTINY_KNOT = create("destiny_knot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.KNOT))
@JvmField
val LUMINOUS_MOSS = create("luminous_moss", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.MOSS).mapColor(MapColor.NONE).noOcclusion().lightLevel{5}, DecorativeItemBlock.DecorativeItemBlockType.CENTERED_8x))
@JvmField
val METRONOME = create("metronome", ActivatableDecorationBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion(), ActivatableDecorationBlock.ActivatableDecorationBlockType.METRONOME))
@JvmField
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
@JvmField
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_DICE_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
@JvmField
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
@JvmField
val METAL_COAT = create("metal_coat", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.METAL_COAT))
@JvmField
val UPGRADE = create("upgrade", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@@ -670,6 +754,23 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
@JvmField
@@ -685,6 +786,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
@JvmField
val TM_MACHINE = create(
"tm_machine",
TMMachineBlock(
@@ -713,7 +815,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
@JvmField
val RED_MINT = create("red_mint", MintBlock(MintType.RED, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@JvmField
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
import com.mojang.brigadier.arguments.ArgumentType
import kotlin.reflect.KClass
import net.minecraft.commands.synchronization.ArgumentTypeInfo
import net.minecraft.core.HolderLookup
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
@@ -168,12 +169,16 @@ interface CobblemonImplementation {
* TODO
*
* @param identifier
* @param reloader
* @param reloaderFactory
* @param type
* @param dependencies
*/
fun registerResourceReloader(identifier: ResourceLocation, reloader: PreparableReloadListener, type: PackType, dependencies: Collection<ResourceLocation>)
fun registerResourceReloader(
identifier: ResourceLocation,
type: PackType,
dependencies: Collection<ResourceLocation>,
reloaderFactory: (HolderLookup.Provider) -> PreparableReloadListener
)
/**
* TODO
*
@@ -472,9 +472,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
@JvmField val UPGRADE = noSettingsItem("upgrade")
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
@JvmField val METAL_COAT = itemNameBlockItem("metal_coat", CobblemonBlocks.METAL_COAT)
@JvmField val UPGRADE = itemNameBlockItem("upgrade", CobblemonBlocks.UPGRADE)
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
@@ -483,7 +483,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val PROTECTOR = noSettingsItem("protector")
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
@JvmField val SACHET = noSettingsItem("sachet")
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.SACHET)
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
@@ -492,22 +492,20 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
@JvmField val CHIPPED_POT = itemNameBlockItem("chipped_pot", CobblemonBlocks.CHIPPED_POT)
@JvmField val CRACKED_POT = itemNameBlockItem("cracked_pot", CobblemonBlocks.CRACKED_POT)
@JvmField val MASTERPIECE_TEACUP = itemNameBlockItem("masterpiece_teacup", CobblemonBlocks.MASTERPIECE_TEACUP)
@JvmField val UNREMARKABLE_TEACUP = itemNameBlockItem("unremarkable_teacup", CobblemonBlocks.UNREMARKABLE_TEACUP)
@JvmField val SWEET_APPLE = compostableItem("sweet_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val TART_APPLE = compostableItem("tart_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val SYRUPY_APPLE = compostableItem("syrupy_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
@@ -651,17 +649,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val EXPERIENCE_CANDY_XL = candyItem("exp_candy_xl") { _, _ -> CandyItem.DEFAULT_XL_CANDY_YIELD }
@JvmField
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK))
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
@JvmField
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
@JvmField
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
@JvmField
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
@JvmField
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
@JvmField
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
@JvmField
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
@JvmField
@@ -743,9 +741,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
@JvmField
val PP_UP = create("pp_up", PPUpItem(1))
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
@JvmField
val PP_MAX = create("pp_max", PPUpItem(3))
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
@JvmField
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
@@ -922,11 +920,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
@JvmField val EXP_SHARE = wearableItem("exp_share")
@JvmField val FOCUS_BAND = wearableItem("focus_band")
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
@JvmField val KINGS_ROCK = wearableBlockItem("kings_rock", CobblemonBlocks.KINGS_ROCK)
@JvmField val NEVER_MELT_ICE = wearableBlockItem("never_melt_ice", CobblemonBlocks.NEVER_MELT_ICE)
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
@JvmField val SHARP_BEAK = wearableItem("sharp_beak")
@JvmField val COVERT_CLOAK = wearableItem("covert_cloak")
@JvmField val SHELL_HELMET = wearableItem("shell_helmet")
@JvmField val GALARICA_WREATH = wearableBlockItem("galarica_wreath", CobblemonBlocks.GALARICA_WREATH)
@JvmField val CLEANSE_TAG = wearableBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField val SPELL_TAG = wearableBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
// Held Items
@JvmField
@@ -954,17 +959,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField
val CLEAR_AMULET = heldItem("clear_amulet")
@JvmField
val COVERT_CLOAK = heldItem("covert_cloak")
@JvmField
val DESTINY_KNOT = heldItem("destiny_knot")
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
@JvmField
val DRAGON_FANG = heldItem("dragon_fang")
@JvmField
val EJECT_BUTTON = heldItem("eject_button")
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
@JvmField
val EJECT_PACK = heldItem("eject_pack")
@JvmField
@@ -978,7 +979,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val FAIRY_FEATHER = heldItem("fairy_feather")
@JvmField
val FLAME_ORB = heldItem("flame_orb")
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
@JvmField
val FLOAT_STONE = heldItem("float_stone")
@JvmField
@@ -992,34 +993,32 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
@JvmField
val IRON_BALL = heldItem("iron_ball")
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
@JvmField
val LAGGING_TAIL = heldItem("lagging_tail")
@JvmField
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
@JvmField
val LIFE_ORB = heldItem("life_orb")
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
@JvmField
val LIGHT_BALL = heldItem("light_ball")
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
@JvmField
val LIGHT_CLAY = heldItem("light_clay")
@JvmField
val LOADED_DICE = heldItem("loaded_dice")
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
@JvmField
val LUCKY_EGG = heldItem("lucky_egg")
@JvmField
val LUMINOUS_MOSS = heldItem("luminous_moss")
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
@JvmField
val MAGNET = heldItem("magnet")
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
@JvmField
val METRONOME = heldItem("metronome")
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.METRONOME)
@JvmField
val MIRACLE_SEED = heldItem("miracle_seed")
@JvmField
val MYSTIC_WATER = heldItem("mystic_water")
@JvmField
val NEVER_MELT_ICE = heldItem("never_melt_ice")
@JvmField
val POISON_BARB = heldItem("poison_barb")
@JvmField
val POWER_ANKLET = heldItem("power_anklet")
@@ -1044,13 +1043,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val RED_CARD = heldItem("red_card")
@JvmField
val RING_TARGET = heldItem("ring_target")
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
@JvmField
val ROOM_SERVICE = heldItem("room_service")
@JvmField
val SCOPE_LENS = heldItem("scope_lens")
@JvmField
val SHARP_BEAK = heldItem("sharp_beak")
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
@JvmField
val SHED_SHELL = heldItem("shed_shell")
@JvmField
@@ -1062,9 +1059,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SOFT_SAND = heldItem("soft_sand")
@JvmField
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
@JvmField
val SMOKE_BALL = heldItem("smoke_ball")
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
@JvmField
val SOOTHE_BELL = heldItem("soothe_bell")
@JvmField
@@ -1072,9 +1067,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val TERRAIN_EXTENDER = heldItem("terrain_extender")
@JvmField
val THROAT_SPRAY = heldItem("throat_spray")
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
@JvmField
val TOXIC_ORB = heldItem("toxic_orb")
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
@JvmField
val TWISTED_SPOON = heldItem("twisted_spoon")
@JvmField
@@ -1082,9 +1077,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
@JvmField
val WIDE_LENS = heldItem("wide_lens")
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
@JvmField
val ZOOM_LENS = heldItem("zoom_lens")
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.ZOOM_LENS)
@JvmField
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 0.85F)
@JvmField
@@ -1096,19 +1091,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
@JvmField
val BRIGHT_POWDER = heldItem("bright_powder")
val BRIGHT_POWDER = itemNameBlockItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
@JvmField
val METAL_POWDER = heldItem("metal_powder")
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
@JvmField
val QUICK_POWDER = heldItem("quick_powder")
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
@JvmField
val DAMP_ROCK = heldItem("damp_rock")
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
@JvmField
val HEAT_ROCK = heldItem("heat_rock")
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
@JvmField
val SMOOTH_ROCK = heldItem("smooth_rock")
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
@JvmField
val ICY_ROCK = heldItem("icy_rock")
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
// Mulch
@JvmField
@@ -1599,7 +1594,16 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
name,
WearableItem(name).also {
WearableHatItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
}
}
)
private fun wearableBlockItem(name: String, block: Block, heldItemRemappedName: String? = null): WearableBlockItem = create(
name,
WearableBlockItem(name, block).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
@@ -8,34 +8,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.net.serverhandling.battle.TeamPreviewSelectPokemonHandler
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
import com.cobblemon.mod.common.client.net.battle.CloseTeamPreviewHandler
import com.cobblemon.mod.common.net.messages.client.battle.CloseTeamPreviewPacket
import com.cobblemon.mod.common.client.net.battle.TeamPreviewHandler
import com.cobblemon.mod.common.net.messages.client.battle.TeamPreviewPacket
import com.cobblemon.mod.common.client.net.battle.BattleTimerHandler
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
import com.cobblemon.mod.common.client.net.battle.BattleInformationHandler
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
import com.cobblemon.mod.common.client.net.battle.BattleActorTeamHandler
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
import com.cobblemon.mod.common.client.net.battle.BattleActorInformationHandler
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorInformationPacket
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.net.OpenBehaviourEditorHandler
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
@@ -414,12 +386,6 @@ object CobblemonNetwork {
// Battle packets
list.add(PacketRegisterInfo(BattleEndPacket.ID, BattleEndPacket::decode, BattleEndHandler))
list.add(PacketRegisterInfo(BattleInitializePacket.ID, BattleInitializePacket::decode, BattleInitializeHandler))
list.add(PacketRegisterInfo(BattleActorInformationPacket.ID, BattleActorInformationPacket::decode, BattleActorInformationHandler))
list.add(PacketRegisterInfo(BattleActorTeamPacket.ID, BattleActorTeamPacket::decode, BattleActorTeamHandler))
list.add(PacketRegisterInfo(BattleInformationPacket.ID, BattleInformationPacket::decode, BattleInformationHandler))
list.add(PacketRegisterInfo(BattleTimerPacket.ID, BattleTimerPacket::decode, BattleTimerHandler))
list.add(PacketRegisterInfo(TeamPreviewPacket.ID, TeamPreviewPacket::decode, TeamPreviewHandler))
list.add(PacketRegisterInfo(CloseTeamPreviewPacket.ID, CloseTeamPreviewPacket::decode, CloseTeamPreviewHandler))
list.add(PacketRegisterInfo(BattleQueueRequestPacket.ID, BattleQueueRequestPacket::decode, BattleQueueRequestHandler))
list.add(PacketRegisterInfo(BattleFaintPacket.ID, BattleFaintPacket::decode, BattleFaintHandler))
list.add(PacketRegisterInfo(BattleMakeChoicePacket.ID, BattleMakeChoicePacket::decode, BattleMakeChoiceHandler))
@@ -459,6 +425,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesEvolutionSyncPacket.ID, SpeciesEvolutionSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -607,7 +574,6 @@ object CobblemonNetwork {
// Battle packets
list.add(PacketRegisterInfo(BattleSelectActionsPacket.ID, BattleSelectActionsPacket::decode, BattleSelectActionsHandler))
list.add(PacketRegisterInfo(TeamPreviewSelectPokemonPacket.ID, TeamPreviewSelectPokemonPacket::decode, TeamPreviewSelectPokemonHandler))
list.add(PacketRegisterInfo(SpectateBattlePacket.ID, SpectateBattlePacket::decode, SpectateBattleHandler))
list.add(PacketRegisterInfo(RemoveSpectatorPacket.ID, RemoveSpectatorPacket::decode, RemoveSpectatorHandler))
@@ -657,5 +657,65 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
SoundEvents.WOOL_FALL
)
@JvmField
val ITEM_BLOCK_POWDER_PLACE = this.create("block.item_block.powder.place")
@JvmField
val ITEM_BLOCK_POWDER_BREAK = this.create("block.item_block.powder.break")
@JvmField
val ITEM_BLOCK_POWDER_STEP = this.create("block.item_block.powder.step")
@JvmField
val ITEM_BLOCK_POWDER_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_POWDER_BREAK,
ITEM_BLOCK_POWDER_STEP,
ITEM_BLOCK_POWDER_PLACE,
ITEM_BLOCK_POWDER_STEP,
ITEM_BLOCK_POWDER_STEP
)
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_PLACE = this.create("block.item_block.bottle_liquid.place")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_BREAK = this.create("block.item_block.bottle_liquid.break")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_STEP = this.create("block.item_block.bottle_liquid.step")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_BOTTLE_LIQUID_BREAK,
ITEM_BLOCK_BOTTLE_LIQUID_STEP,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE
)
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_PLACE = this.create("block.item_block.bottle_pills.place")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_BREAK = this.create("block.item_block.bottle_pills.break")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_STEP = this.create("block.item_block.bottle_pills.step")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_BOTTLE_PILLS_BREAK,
ITEM_BLOCK_BOTTLE_PILLS_STEP,
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
ITEM_BLOCK_BOTTLE_PILLS_PLACE
)
@JvmField
val ITEM_BLOCK_DICE_PLACE = this.create("block.item_block.dice.place")
@JvmField
val ITEM_BLOCK_DICE_BREAK = this.create("block.item_block.dice.break")
@JvmField
val ITEM_BLOCK_DICE_STEP = this.create("block.item_block.dice.step")
@JvmField
val ITEM_BLOCK_DICE_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_DICE_BREAK,
ITEM_BLOCK_DICE_STEP,
ITEM_BLOCK_DICE_PLACE,
ITEM_BLOCK_DICE_STEP,
ITEM_BLOCK_DICE_STEP
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
}
@@ -8,16 +8,6 @@
package com.cobblemon.mod.common.api.battles.model
import com.cobblemon.mod.common.net.messages.client.battle.FieldEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationDTO
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
@@ -257,38 +247,6 @@ open class PokemonBattle(
}
}
this.turn = newTurnNumber
notifyAllOfUpdates()
}
fun notifyAllOfUpdates() {
val uuids = this.actors.flatMap { it.getPlayerUUIDs() } + this.spectators
notifyOfUpdates(uuids)
}
fun notifyOfUpdates(uuids: List<UUID>) {
val weather = this.contextManager.get(BattleContext.Type.WEATHER)?.firstOrNull()
val terrain = this.contextManager.get(BattleContext.Type.TERRAIN)?.firstOrNull()
val room = this.contextManager.get(BattleContext.Type.ROOM)?.firstOrNull()
val side1SidedEffects = getSidedFieldEffects(side1)
val side2SidedEffects = getSidedFieldEffects(side2)
val updatePacket = BattleInformationPacket(this.battleId, BattleInformationDTO(
turn = turn,
weather = weather?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
terrain = terrain?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
room = room?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
side1SidedEffects = side1SidedEffects,
side2SidedEffects = side2SidedEffects,
))
uuids.mapNotNull { it.getPlayer() }.forEach {
it.sendPacket(updatePacket)
}
}
private fun getSidedFieldEffects(side: BattleSide): List<FieldEffect> {
val hazards = side.contextManager.get(BattleContext.Type.HAZARD)?.map { FieldEffect(it.id, it.turn, it.origin?.entity?.id) } ?: emptyList()
val screens = side.contextManager.get(BattleContext.Type.SCREEN)?.map { FieldEffect(it.id, it.turn, it.origin?.entity?.id) } ?: emptyList()
val tailwind = side.contextManager.get(BattleContext.Type.TAILWIND)?.firstOrNull()?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) }
return hazards + screens + listOfNotNull(tailwind)
}
fun end() {
@@ -496,7 +454,6 @@ open class PokemonBattle(
}
fun tick() {
if (!ended) actors.filterIsInstance<PlayerBattleActor>().forEach { it.timer?.tick() }
try {
while (dispatchResult.canProceed()) {
val dispatch = dispatches.poll() ?: break
@@ -8,10 +8,6 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.net.NetworkPacket
@@ -52,20 +48,6 @@ abstract class BattleActor(
var responses = mutableListOf<ShowdownActionResponse>()
val expectingPassActions = mutableListOf<ShowdownActionResponse>()
var mustChoose = false
set(value) {
if (field == value) return
field = value
if (this is PlayerBattleActor) {
timer?.let {
if (value) {
it.startSelection()
sendUpdate(BattleTimerPacket(it.mustChooseBy()))
} else {
it.endSelection()
}
}
}
}
var itemsUsed = mutableListOf<BagItem>()
/** For when battles start, it's the number of Pokémon that are still in the process of being sent out (animation wise) */
@@ -11,6 +11,9 @@ package com.cobblemon.mod.common.api.conditional
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
/**
@@ -21,11 +24,19 @@ import java.lang.reflect.Type
* @author Hiroku
* @since July 17th, 2022
*/
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>> {
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>>, JsonSerializer<RegistryLikeCondition<B>> {
val registryLikeConditions: MutableList<(JsonElement) -> RegistryLikeCondition<B>?>
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): RegistryLikeCondition<B> {
return registryLikeConditions.firstNotNullOfOrNull { it(json) }
?: throw IllegalArgumentException("Unable to deserialize $json")
}
override fun serialize(src: RegistryLikeCondition<B>, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
return when (src) {
is RegistryLikeTagCondition<B> -> JsonPrimitive(RegistryLikeTagCondition.PREFIX + src.tag.location())
is RegistryLikeIdentifierCondition<B> -> JsonPrimitive(src.identifier.toString())
else -> throw IllegalArgumentException("Unable to serialize $src")
}
}
}
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.data
import com.cobblemon.mod.common.util.endsWith
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import net.minecraft.core.HolderLookup
import java.io.BufferedReader
import java.io.File
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
/**
* A [DataRegistry] that consumes JSON files.
* Every deserialized instance is attached to an [Identifier].
* For example a file under data/mymod/[resourcePath]/entry.json would be backed by the identifier modid:entry.
*
* @param T The type of the data consumed by this registry.
*
* @author Licious
* @since August 5th, 2022
*/
interface ServerJsonDataRegistry<T> : DataRegistry {
/**
* The [TypeToken] of type [T].
*/
val typeToken: TypeToken<T>
/**
* The folder location for the data this registry will consume.
*/
val resourcePath: String
override val type: PackType
get() = PackType.SERVER_DATA
override fun reload(manager: ResourceManager) {
//NO-OP, handled by reload(manager, registry)
}
fun reload(manager: ResourceManager, registry: HolderLookup.Provider) {
val gson = setupParser(registry)
val data = hashMapOf<ResourceLocation, T>()
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
if (identifier.namespace == "pixelmon") {
return@forEach
}
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
data[resolvedIdentifier] = parse(reader, resolvedIdentifier, gson)
}
}
}
reload(data, registry)
}
fun parse(reader: BufferedReader, identifier: ResourceLocation, gson: Gson): T {
return try {
gson.fromJson(reader, typeToken.type)
} catch (exception: Exception) {
throw ExecutionException("Error loading JSON for data: $identifier", exception)
}
}
/**
* Allows the creation of [Gson] instances which have adapters that are registry-aware, e.g. [com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter]
*/
fun setupParser(registry: HolderLookup.Provider): Gson
/**
* Reloads this registry from the deserialized data.
*
* @param data A map of the data associating an instance to the respective identifier from the [ResourceManager].
*/
fun reload(data: Map<ResourceLocation, T>, registry: HolderLookup.Provider)
companion object {
const val JSON_EXTENSION = ".json"
}
}
@@ -40,7 +40,7 @@ object Fossils: JsonDataRegistry<Fossil> {
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
.create()
override val typeToken: TypeToken<Fossil> = TypeToken.get(Fossil::class.java)
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.gui
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.network.chat.Component
@@ -55,6 +54,8 @@ abstract class ParentWidget(
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (!active) return false
return children.any {
it.mouseClicked(pMouseX, pMouseY, pButton)
}
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.gui
import kotlin.math.max
import kotlin.math.min
interface TreeNode<T> {
val children: List<TreeNode<T>>
}
class TreeDrawingNode<T>(
val value: T,
val children: List<TreeDrawingNode<T>>,
) {
var x = 0
var y = 0
var offset = 0
}
fun <T> createDrawingTree(treeNode: TreeNode<T>): TreeDrawingNode<T> {
val root = setup(treeNode, 0, mutableListOf<Int>(), mutableListOf<Int>())
addMods(root, 0)
return root
}
@Suppress("UNCHECKED_CAST")
private fun <T> setup(
treeNode: TreeNode<T>,
depth: Int,
nexts: MutableList<Int>,
offset: MutableList<Int>,
): TreeDrawingNode<T> {
val children = treeNode.children.map { child ->
setup(child, depth + 1, nexts, offset)
}
val treeDrawingNode = TreeDrawingNode<T>(treeNode as T, children)
treeDrawingNode.y = depth
while (nexts.size <= depth) {
nexts.add(0)
}
while (offset.size <= depth) {
offset.add(0)
}
var place =
if (children.isEmpty()) {
nexts[depth]
} else if (children.size == 1) {
// children.first().x - 1
children.first().x
} else {
// (children[0].x + children[1].x) / 2
(children.first().x + children.last().x) / 2
}
offset[depth] = max(offset[depth], nexts[depth] - place)
if (children.isEmpty()) {
treeDrawingNode.x = place
} else {
treeDrawingNode.x = place + offset[depth]
}
nexts[depth] += 2
treeDrawingNode.offset = offset[depth]
return treeDrawingNode
}
private fun <T> addMods(
treeNode: TreeDrawingNode<T>,
modSum: Int,
) {
treeNode.x += modSum
val newModSum = modSum + treeNode.offset
treeNode.children.forEach { child ->
addMods(child, newModSum)
}
}
data class TreeDrawingBounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int) {
val width = maxX - minX
val height = maxY - minY
val xCenter = (maxX + minX) / 2.0
val yCenter = (maxY + minY) / 2.0
}
fun <T> calculateBounds(treeDrawingNode: TreeDrawingNode<T>): TreeDrawingBounds {
var minX = treeDrawingNode.x
var maxX = treeDrawingNode.x
var minY = treeDrawingNode.y
var maxY = treeDrawingNode.y
for (child in treeDrawingNode.children) {
val childBounds = calculateBounds(child)
minX = min(minX, childBounds.minX)
maxX = max(maxX, childBounds.maxX)
minY = min(minY, childBounds.minY)
maxY = max(maxY, childBounds.maxY)
}
return TreeDrawingBounds(minX, maxX, minY, maxY)
}
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.api.interaction
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.data.ServerJsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
@@ -26,6 +26,7 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.advancements.critereon.ItemPredicate
import net.minecraft.core.HolderLookup
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
@@ -36,28 +37,31 @@ import net.minecraft.world.level.block.Block
import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.level.material.Fluid
object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
override val id = cobblemonResource("pokemon_interactions")
override val type = PackType.SERVER_DATA
override fun setupParser(registry: HolderLookup.Provider): Gson {
return GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter(registry))
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
.setPrettyPrinting()
.create()
}
override val observable = SimpleObservable<PokemonInteractions>()
override val typeToken: TypeToken<PokemonInteractionSet> = TypeToken.get(PokemonInteractionSet::class.java)
override val resourcePath = "pokemon_interactions"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
.setPrettyPrinting()
.create()
val interactions = mutableListOf<PokemonInteractionSet>()
@@ -65,7 +69,7 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
PokemonInteractionsSyncPacket(interactions).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>, registry: HolderLookup.Provider) {
interactions.clear()
val split = data.entries
.partition {
@@ -87,4 +91,4 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
.flatMap { it.interactions }
.firstOrNull(interactionCheck)
}
}
}
@@ -55,6 +55,11 @@ object GeneralMoLangFunctions {
struct.map.remove(variable)
DoubleValue.ONE
}
functions["delete_variables"] = { params ->
val struct = params.get<VariableStruct>(0)
struct.map.clear()
DoubleValue.ONE
}
functions["get_variable"] = { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.pokedex.SeenCount
@@ -74,6 +75,27 @@ object PokedexMoLangFunctions : AbstractMoLangFunctionHolder<AbstractPokedexMana
functions["caught_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
functions["seen_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
functions["dex_caught_count"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtCount))
else DoubleValue.ZERO
}
functions["dex_seen_count"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenCount))
else DoubleValue.ZERO
}
functions["dex_caught_percent"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtPercent).toDouble())
else DoubleValue.ZERO
}
functions["dex_seen_percent"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenPercent).toDouble())
else DoubleValue.ZERO
}
when (pokedex) {
is PokedexManager -> {
functions["player_id"] = { StringValue(pokedex.uuid.toString()) }
@@ -8,8 +8,10 @@
package com.cobblemon.mod.common.api.orientation
import com.cobblemon.mod.common.util.math.QuatSpring
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.simpleSpringDamperQuat
import net.minecraft.client.Minecraft
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
@@ -26,6 +28,8 @@ open class OrientationController(val entity: LivingEntity) {
val FORWARDS: Vector3f = Vector3f(0.0f, 0.0f, -1.0f)
val UP: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)
val LEFT: Vector3f = Vector3f(-1.0f, 0.0f, 0.0f)
private const val TICK_DT = 1f / 20f // Minecraft server tick rate
private const val RENDER_SMOOTH_HALFLIFE = 0.1f // seconds to close ~half the gap
}
open var active: Boolean = false
@@ -33,6 +37,8 @@ open class OrientationController(val entity: LivingEntity) {
var orientation: Matrix3f? = null
private set
private val renderSpring = QuatSpring()
private var renderOrientationO: Matrix3f? = null
private var renderOrientation: Matrix3f? = null
@@ -54,6 +60,8 @@ open class OrientationController(val entity: LivingEntity) {
orientation = null
renderOrientationO = null
renderOrientation = null
renderSpring.quat.identity()
renderSpring.angVel.set(0f, 0f, 0f)
active = false
}
@@ -77,18 +85,26 @@ open class OrientationController(val entity: LivingEntity) {
return oldQuat
}
/** Every tick move the current render orientation towards the last known true orientation **/
fun tick() {
if (!active || orientation == null) return
renderOrientationO = renderOrientation
val current = orientation
val renderMatrix = this.renderOrientation ?: current
val renderQuat = Quaternionf().setFromUnnormalized(renderMatrix)
val targetQuat = Quaternionf().setFromUnnormalized(current)
val dampingFactor = 0.66f // Smooth interpolation to reduce jitter of orientations recieved from the server.
renderQuat.slerp(targetQuat, dampingFactor)
val current = orientation!!
val targetQuat = Quaternionf().setFromUnnormalized(current)
if (renderOrientation == null) {
// First tick: snap the spring to the target instead of easing in from identity
renderSpring.quat.set(targetQuat)
renderSpring.angVel.set(0f, 0f, 0f)
}
// Use a "quat spring" to smoothly move the render orientation towards the goal orientation.
// It takes into account the orientations angular velocities and should be much more resilient
// to sharp changes in goal orientation or late/missed packets.
simpleSpringDamperQuat(renderSpring, targetQuat, 0.15f, TICK_DT)
val newRenderOrientation = Matrix3f()
renderQuat.get(newRenderOrientation)
renderSpring.quat.get(newRenderOrientation)
renderOrientation = newRenderOrientation
}
@@ -108,7 +108,7 @@ object CatchRateModifiers {
* Used by [PokeBalls.SAFARI_BALL].
* Checks if the target is not battling, if true boost by *1.5
*/
val SAFARI = WorldStateModifier { _, entity -> if (!entity.isBattling) 1.5F else 1F }
val SAFARI = MultiplierModifier(1.5f) { _, poke -> !poke.entity?.isBattling!! }
/**
* Used by [PokeBalls.PARK_BALL].
@@ -13,6 +13,10 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Gender
import net.minecraft.resources.ResourceLocation
@@ -72,6 +76,57 @@ abstract class AbstractPokedexManager {
} ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?): PokedexEntryProgress {
if (form == null) return getHighestKnowledgeFor(entry)
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.UNREGISTERED
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
if (!hasAllAspects) {
return PokedexEntryProgress.UNREGISTERED
}
return speciesRecord.getFormRecord(form.displayForm)?.knowledge ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?, aspects: Iterable<String>): PokedexEntryProgress {
val formKnowledge = getHighestKnowledgeFor(entry, form)
if (formKnowledge == PokedexEntryProgress.UNREGISTERED) return PokedexEntryProgress.UNREGISTERED
val seenAspects = getSeenAspects(entry)
// Aspects that select one of the species' own forms are already covered by the form knowledge check above,
// so exempt them (and every other value of the same feature, since defaults like "phony" never appear on a form directly).
val species = PokemonSpecies.getByIdentifier(entry.speciesId)
val formAspects = species?.forms?.flatMapTo(hashSetOf()) { it.aspects } ?: emptySet()
val formSelectingFeatureAspects = species?.let(SpeciesFeatures::getFeaturesFor).orEmpty()
.mapNotNull { provider ->
val allAspects: Collection<String> = when (provider) {
is ChoiceSpeciesFeatureProvider -> provider.getAllAspects()
is WeightedChoiceSpeciesFeatureProvider -> provider.getAllAspects().keys
else -> return@mapNotNull null
}
allAspects.takeIf { it.any { aspect -> aspect in formAspects } }
}
.flatten()
.toHashSet()
val allSeenAspects = aspects
.filter { aspect ->
if (Dexes.equivalentFormsLookup[aspect] != null) return@filter false
if (aspect in formAspects || aspect in formSelectingFeatureAspects) return@filter false
if (aspect.uppercase() in setOf(Gender.MALE.name, Gender.FEMALE.name, Gender.GENDERLESS.name)) return@filter false
if ("shiny".equals(aspect, ignoreCase = true)) return@filter false
return@filter true
}
.all { aspect -> seenAspects.contains(aspect) }
if (!allSeenAspects) return PokedexEntryProgress.UNREGISTERED
return formKnowledge
}
fun getEncounteredForms(entry: PokedexEntry) : List<PokedexForm> {
return getFormsWithKnowledge(entry, PokedexEntryProgress.SEEN)
}
@@ -10,6 +10,9 @@ package com.cobblemon.mod.common.api.pokedex
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.PokedexDexSyncPacket
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
@@ -40,6 +43,17 @@ object Dexes : JsonDataRegistry<PokedexDef> {
//Maps a dex id to its PokedexDef
val dexEntryMap = linkedMapOf<ResourceLocation, PokedexDef>()
val equivalentForms = mapOf(
"galar" to listOf("region-bias-galar", "bias-galar", "galar-bias", "galarian", "galar"),
"alola" to listOf("region-bias-alola", "bias-alola", "alola-bias", "alolan", "alola"),
"hisui" to listOf("region-bias-hisui", "bias-hisui", "hisui-bias", "hisuian", "hisui"),
"paldea" to listOf("region-bias-paldea", "bias-paldea", "paldea-bias", "paldean", "paldea"),
)
val equivalentFormsLookup = equivalentForms.flatMap { (key, values) ->
values.map { it to key }
}.toMap()
override fun reload(data: Map<ResourceLocation, PokedexDef>) {
dexEntryMap.clear()
data.entries.sortedBy { it.value.sortOrder }.forEach { (id, def) -> dexEntryMap[id] = def }
@@ -49,4 +63,41 @@ object Dexes : JsonDataRegistry<PokedexDef> {
override fun sync(player: ServerPlayer) {
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
}
fun findDexEntryBySpeciesId(regionId: ResourceLocation, speciesId: ResourceLocation): PokedexEntry? {
return dexEntryMap[regionId]?.getEntries()?.firstOrNull { it -> it.speciesId == speciesId }
}
fun findDexEntryBySpeciesId(speciesId: ResourceLocation): PokedexEntry? {
return findDexEntryBySpeciesId(cobblemonResource("national"), speciesId)
}
fun findRootDexEntryBySpeciesId(speciesId: ResourceLocation, formName: String): Pair<PokedexEntry, PokedexForm> {
var rootSpecies = PokemonSpecies.getByIdentifier(speciesId)!!
val initialFormEquivalents = getFormEquivalents(formName)
var rootForm = rootSpecies.forms.firstOrNull { initialFormEquivalents.contains(it.name.lowercase()) }
?: rootSpecies.standardForm
var preEvolution = rootForm.preEvolution
while (preEvolution != null) {
rootSpecies = preEvolution.species
rootForm = preEvolution.resolveForm(rootForm.aspects.toSet())
preEvolution = rootForm.preEvolution
}
val entry = findDexEntryBySpeciesId(rootSpecies.resourceIdentifier)
?: throw RuntimeException("Root dex entry not found")
val formEquivalents = getFormEquivalents(rootForm.name)
val form =
entry.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) } ?:
entry.forms.first { it.displayForm.equals("Normal", ignoreCase = true) }
return entry to form
}
fun getFormEquivalents(formName: String): List<String> {
val key = equivalentFormsLookup[formName.lowercase()]
return equivalentForms[key] ?: listOf(formName.lowercase())
}
}
@@ -121,6 +121,7 @@ class SpeciesDexRecord {
fun addAspects(addedAspects: Set<String>) {
aspects.addAll(addedAspects)
pokedexManager.onSpeciesRecordUpdated(this)
}
/** Returns true if the given Pokémon contains new information. Internal because it's only to be called from [FormDexRecord.wouldBeDifferent]. */
@@ -44,6 +44,9 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.net.messages.client.data.FormEvolutionOverride
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionEntry
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
@@ -128,7 +131,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
@@ -243,6 +246,18 @@ object PokemonSpecies : JsonDataRegistry<Species> {
override fun sync(player: ServerPlayer) {
SpeciesRegistrySyncPacket(species.toList()).sendToPlayer(player)
// Sent after the species themselves so every species is already registered by the time this is decoded.
val evolutionEntries = species.map { singleSpecies ->
val formOverrides = singleSpecies.forms.mapNotNull { form ->
if (form.ownEvolutions == null && form.ownPreEvolution == null) {
null
} else {
FormEvolutionOverride(form.name, form.ownEvolutions, form.ownPreEvolution)
}
}
SpeciesEvolutionEntry(singleSpecies.resourceIdentifier, singleSpecies.evolutions, singleSpecies.preEvolution, formOverrides)
}
SpeciesEvolutionSyncPacket(evolutionEntries).sendToPlayer(player)
}
/**
@@ -25,6 +25,8 @@ interface PreEvolution {
val form: FormData
fun resolveForm(extraAspects: Set<String> = emptySet()): FormData = form
companion object {
fun of(species: Species, form: FormData = species.standardForm): PreEvolution = StandardPreEvolution(species, form)
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.api.pokemon.requirement
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.resources.ResourceLocation
import java.util.WeakHashMap
/**
* Represents a universal requirement suitable for various checks on pokemon..
@@ -32,4 +34,11 @@ fun interface Requirement {
*/
fun check(pokemon: Pokemon): Boolean
val pokedexIcon: ResourceLocation? get() = pokedexIconOverrides[this]
val pokedexTooltip: String? get() = pokedexTooltipOverrides[this]
companion object {
val pokedexIconOverrides = WeakHashMap<Requirement, ResourceLocation>()
val pokedexTooltipOverrides = WeakHashMap<Requirement, String>()
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.core.BlockPos
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.phys.AABB
import kotlin.math.floor
/**
* Walks every block position inside [searchBox] and turns each solid block into one
* full-cube [FieldPrimitive].
*
* @author Jackson
* @since July 27th, 2026
*/
fun collectPrimitivesFromBlocks(
level: BlockGetter,
searchBox: AABB,
shape: PrimitiveShape,
includeFluids: Boolean = false
): List<FieldPrimitive> {
val primitives = mutableListOf<FieldPrimitive>()
val minX = floor(searchBox.minX).toInt()
val maxX = floor(searchBox.maxX).toInt()
val minY = floor(searchBox.minY).toInt()
val maxY = floor(searchBox.maxY).toInt()
val minZ = floor(searchBox.minZ).toInt()
val maxZ = floor(searchBox.maxZ).toInt()
val pos = BlockPos.MutableBlockPos()
for (x in minX..maxX) {
for (y in minY..maxY) {
for (z in minZ..maxZ) {
pos.set(x, y, z)
val state = level.getBlockState(pos)
val isSolid = !state.getCollisionShape(level, pos).isEmpty
val isFluid = includeFluids && !state.fluidState.isEmpty
if (!isSolid && !isFluid) continue
primitives.add(
FieldPrimitive(
shape = shape,
centerX = x + 0.5f, centerY = y + 0.5f, centerZ = z + 0.5f,
halfWidth = 0.5f, halfHeight = 0.5f, halfDepth = 0.5f
)
)
}
}
}
return primitives
}
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.world.phys.Vec3
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
/**
* Helpful Functions when working with signed distance fields/functions.
* https://iquilezles.org/articles/distfunctions/
*
* @author Jackson
* @since July 27th, 2026
*/
/** Which distance formula a primitive uses. */
enum class PrimitiveShape {
BOX,
ROUNDED_BOX,
ELLIPSOID
}
/** Tunable constants for the field. */
data class DistanceFieldTuning(
val smoothingRange: Float = 0.5f,
val roundingRadius: Float = 0.08f
)
/** A single shape in the field - a center position, half-extents, and which formula to use. */
data class FieldPrimitive(
val shape: PrimitiveShape,
val centerX: Float, val centerY: Float, val centerZ: Float,
val halfWidth: Float, val halfHeight: Float, val halfDepth: Float
)
/** Fast approximate 1/sqrt(x) - the Quake III trick, cheaper than a real sqrt + divide. */
fun fastInverseSqrt(value: Float): Float {
var x = value
val halfX = 0.5f * x
var bits = java.lang.Float.floatToIntBits(x)
bits = 0x5f3759df - (bits shr 1)
x = java.lang.Float.intBitsToFloat(bits)
x *= (1.5f - halfX * x * x)
return x
}
/** Exact distance from a point to an axis-aligned box. Inputs are relative to the box's own center. */
fun boxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
val qx = abs(relX) - halfWidth
val qy = abs(relY) - halfHeight
val qz = abs(relZ) - halfDepth
val ox = max(qx, 0f)
val oy = max(qy, 0f)
val oz = max(qz, 0f)
val outside = sqrt(ox * ox + oy * oy + oz * oz)
val inside = min(max(qx, max(qy, qz)), 0f)
return outside + inside
}
/** Same as boxDistance but with rounded corners/edges https://iquilezles.org/articles/distfunctions/ */
fun roundedBoxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float, roundingRadius: Float): Float =
boxDistance(relX, relY, relZ, halfWidth - roundingRadius, halfHeight - roundingRadius, halfDepth - roundingRadius) - roundingRadius
/** Approximate (lower-bound) distance to an ellipsoid https://iquilezles.org/articles/ellipsoids/. */
fun ellipsoidDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
val px = relX / halfWidth
val py = relY / halfHeight
val pz = relZ / halfDepth
val inverseMagnitude = fastInverseSqrt(px * px + py * py + pz * pz)
val trueDistance = sqrt(relX * relX + relY * relY + relZ * relZ)
return trueDistance * (1.0f - inverseMagnitude)
}
/** Distance from a point to one primitive, dispatched by its shape. */
fun distanceToPrimitive(primitive: FieldPrimitive, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
val relX = sampleX - primitive.centerX
val relY = sampleY - primitive.centerY
val relZ = sampleZ - primitive.centerZ
return when (primitive.shape) {
PrimitiveShape.BOX -> boxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
PrimitiveShape.ROUNDED_BOX -> roundedBoxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth, tuning.roundingRadius)
PrimitiveShape.ELLIPSOID -> ellipsoidDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
}
}
///** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
//fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
// if (smoothingRange <= 0.001f) return min(a, b)
// val k = smoothingRange * 4.0f
// val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
// return b + (a - b) * h - k * h * (1f - h)
//}
//
///** Folds every primitive's distance together into one field value at a point. */
//fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
// var result = Float.POSITIVE_INFINITY
// for (primitive in primitives) {
// val distance = distanceToPrimitive(primitive, sampleX, sampleY, sampleZ, tuning)
// result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
// }
// return result
//}
/** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
if (smoothingRange <= 0.001f) return min(a, b)
val k = smoothingRange * 4.0f
val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
return b + (a - b) * h - k * h * (1f - h)
}
/** Folds nearby primitive distances together into one field value at a point. */
fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
if (primitives.isEmpty()) return Float.POSITIVE_INFINITY
val distances = FloatArray(primitives.size) { distanceToPrimitive(primitives[it], sampleX, sampleY, sampleZ, tuning) }
val minDistance = distances.min()
if (tuning.smoothingRange <= 0.001f) return minDistance
// Anything more than a few blend-widths past the closest primitive is cut - its
// contribution would be negligible in a correct blend anyway, but left in, it still
// drags the chained result down the more of them there are.
val cutoff = tuning.smoothingRange * 3f
var result = Float.POSITIVE_INFINITY
for (distance in distances) {
if (distance - minDistance > cutoff) continue
result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
}
return result
}
/** Finite-difference gradient of the field at a point - points away from the surface, magnitude ~1 for a clean field. */
fun estimateGradient(primitives: List<FieldPrimitive>, point: Vec3, tuning: DistanceFieldTuning, epsilon: Float = 0.05f): Vec3 {
val px = point.x.toFloat()
val py = point.y.toFloat()
val pz = point.z.toFloat()
val dx = sampleField(primitives, px + epsilon, py, pz, tuning) - sampleField(primitives, px - epsilon, py, pz, tuning)
val dy = sampleField(primitives, px, py + epsilon, pz, tuning) - sampleField(primitives, px, py - epsilon, pz, tuning)
val dz = sampleField(primitives, px, py, pz + epsilon, tuning) - sampleField(primitives, px, py, pz - epsilon, tuning)
val gradient = Vec3(dx.toDouble(), dy.toDouble(), dz.toDouble())
return if (gradient.lengthSqr() > 1e-8) gradient.normalize() else Vec3(0.0, 1.0, 0.0)
}
@@ -0,0 +1,105 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.core.particles.DustParticleOptions
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
/** One shell to draw - a target field value, a color, a particle size. */
data class DistanceFieldLayer(
val isoValue: Float,
val color: Vector3f,
val particleSize: Float = 1.0f
)
/**
* Marches a grid of vertical columns through the field around [center] and spawns a
* particle everywhere it crosses one of [layers]' target values. Server-side only.
*/
fun visualizeDistanceField(
referenceEntity: Entity,
center: Vec3,
layers: List<DistanceFieldLayer>,
shape: PrimitiveShape = PrimitiveShape.BOX,
horizontalRadius: Double = 4.0,
verticalRange: Double = 4.0,
sampleSpacing: Double = 0.5,
marchStep: Double = 0.1,
tuning: DistanceFieldTuning = DistanceFieldTuning()
) {
val level = referenceEntity.level()
if (level !is ServerLevel) return
val searchBox = AABB(
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
center.x + horizontalRadius, center.y, center.z + horizontalRadius
)
val primitives = collectPrimitivesFromBlocks(level, searchBox, shape, includeFluids = true)
if (primitives.isEmpty()) return
var x = -horizontalRadius
while (x <= horizontalRadius) {
var z = -horizontalRadius
while (z <= horizontalRadius) {
marchColumn(level, primitives, center, x, z, verticalRange, marchStep, layers, tuning)
z += sampleSpacing
}
x += sampleSpacing
}
}
private fun marchColumn(
level: ServerLevel,
primitives: List<FieldPrimitive>,
center: Vec3,
offsetX: Double,
offsetZ: Double,
verticalRange: Double,
marchStep: Double,
layers: List<DistanceFieldLayer>,
tuning: DistanceFieldTuning
) {
val previousValue = FloatArray(layers.size) { Float.NaN }
var y = verticalRange
while (y >= -verticalRange) {
val worldX = (center.x + offsetX).toFloat()
val worldY = (center.y + y).toFloat()
val worldZ = (center.z + offsetZ).toFloat()
val fieldValue = sampleField(primitives, worldX, worldY, worldZ, tuning)
for (layerIndex in layers.indices) {
val layer = layers[layerIndex]
val previous = previousValue[layerIndex]
if (!previous.isNaN() && crossedTarget(previous, fieldValue, layer.isoValue)) {
val t = (layer.isoValue - previous) / (fieldValue - previous)
val crossingY = (y + marchStep) - marchStep * t
spawnLayerParticle(level, center.add(offsetX, crossingY, offsetZ), layer)
}
previousValue[layerIndex] = fieldValue
}
y -= marchStep
}
}
private fun crossedTarget(previous: Float, current: Float, target: Float): Boolean =
(previous - target) * (current - target) < 0f
private fun spawnLayerParticle(level: ServerLevel, pos: Vec3, layer: DistanceFieldLayer) {
val particle = DustParticleOptions(layer.color, layer.particleSize)
level.sendParticles(particle, pos.x, pos.y, pos.z, 1, 0.0, 0.0, 0.0, 0.0)
}
@@ -129,12 +129,7 @@ class ParticleEvent(
onDespawn = {},
getParticleColor = storm.getParticleColor,
targetPos = storm.targetPos,
runtime = MoLangRuntime().setup().also { runtime ->
// Weather sub-emitters share the parent's lifetime condition.
storm.runtime.environment.query.functions["is_weather_active"]?.let {
runtime.environment.query.addFunction("is_weather_active", it)
}
},
runtime = MoLangRuntime().setup(),
entity = storm.entity
)
//The reason this doesn't use the newStorms runtime is that the parent storm can add queries AFTER
@@ -216,4 +211,4 @@ class EventSoundEffect(
).apply(instance, ::EventSoundEffect)
}
}
}
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.TimeRange.Companion.timeRanges
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.MutableComponent
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
/**
@@ -49,4 +51,25 @@ class TimeRange : IntRanges {
constructor() : super()
constructor(vararg ranges: IntRange) : super(*ranges)
fun displayName(): MutableComponent? {
val key = timeRanges.entries.firstOrNull { entry -> this == entry.value }?.key ?: return null
return lang("ui.time_range.$key")
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TimeRange) return false
if (other.ranges.size != this.ranges.size) return false
other.ranges.forEachIndexed { index, otherRange ->
if (this.ranges[index] != otherRange) return false
}
return true
}
override fun hashCode(): Int {
return ranges.hashCode()
}
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.level.Level
/**
@@ -42,4 +44,7 @@ enum class MoonPhase {
fun ofWorld(world: Level): MoonPhase = VALUES[world.moonPhase]
}
fun displayName(): MutableComponent {
return lang("ui.moon_phase.${this.name.lowercase()}")
}
}
@@ -28,11 +28,7 @@ data class BattleFormat(
var ruleSet: Set<String> = setOf(),
val gen: Int = 9,
var adjustLevel: Int = -1, // Stop gap rule before a more general system for rules enforced by Cobblemon is implemented.
val isOpenTeamSheet: Boolean = false,
) {
val hasTeamPreview: Boolean
get() = ruleSet.any { it.equals(BattleRules.TEAM_PREVIEW, ignoreCase = true) || it.equals("teampreview", ignoreCase = true) }
companion object {
fun setBattleRules(
battleFormat: BattleFormat,
@@ -125,4 +121,4 @@ data class BattleFormat(
}
""".trimIndent().replace("\n", "")
}
}
}
@@ -211,16 +211,7 @@ object ShowdownInterpreter {
}
// Broadcasts a generic lang to notify players of ability activations (effects are broadcasted separately)
@JvmOverloads
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon, revealedAbility: Effect = effect) {
// Direct -ability messages omit the ability: prefix. A copied ability can also differ
// from the effect announced in chat (for example, Trace).
if (revealedAbility.type == Effect.Type.ABILITY || revealedAbility.type == Effect.Type.PURE) {
battle.dispatchGo {
pokemon.revealedAbility = revealedAbility.id
pokemon.sendUpdate()
}
}
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon) {
battle.dispatchWaiting(0.5F) {
val lang = battleLang("ability.generic", pokemon.getName(), effect.typelessData).yellow()
battle.broadcastChatMessage(lang)
@@ -330,4 +321,4 @@ object ShowdownInterpreter {
fun registerSideInstructionParser(id: String, callback: (PokemonBattle, BattleActor, InstructionSet, BattleMessage) -> InterpreterInstruction) {
sideInstructionParser[id] = callback
}
}
}
@@ -1,151 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.net.messages.client.battle.CloseTeamPreviewPacket
import com.cobblemon.mod.common.net.messages.client.battle.TeamPreviewPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.party
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import java.time.Duration
import java.time.Instant
import java.util.UUID
object TeamPreviewManager {
private val previews = mutableListOf<TeamPreview>()
val TEAM_PREVIEW_TIME_SECONDS = 60
fun tick() {
val expired = previews.filter { Duration.between(it.started, Instant.now()).seconds >= TEAM_PREVIEW_TIME_SECONDS }
expired.forEach { cancel(it) }
val ready = previews.filter {
hasEligibleSelections(it.sideA) && hasEligibleSelections(it.sideB)
}
ready.forEach { start(it) }
}
fun add(sideA: TeamPreviewSide, sideB: TeamPreviewSide, openTeamSheet: Boolean, onStart: (TeamPreview) -> Unit, onCancel: (TeamPreview) -> Unit) {
require(sideA.uuid != sideB.uuid) { "A team preview requires two distinct participants" }
require(listOf(sideA, sideB).all { it.neededSelections in 1..it.party.count { pokemon -> pokemon != null } }) {
"Each side must be able to fill its requested selection"
}
if (previews.any { it.sideA.uuid == sideA.uuid || it.sideB.uuid == sideA.uuid || it.sideA.uuid == sideB.uuid || it.sideB.uuid == sideB.uuid }) return
val now = Instant.now()
previews.add(TeamPreview(sideA, sideB, now, onStart, onCancel))
if (sideA.isPlayer) {
sideA.uuid.getPlayer()?.sendPacket(
TeamPreviewPacket(
sideA.neededSelections,
sideA.party,
sideB.party,
openTeamSheet,
now.plusSeconds(TEAM_PREVIEW_TIME_SECONDS.toLong()),
sideB.name
)
)
}
if (sideB.isPlayer) {
sideB.uuid.getPlayer()?.sendPacket(
TeamPreviewPacket(
sideB.neededSelections,
sideB.party,
sideA.party,
openTeamSheet,
now.plusSeconds(TEAM_PREVIEW_TIME_SECONDS.toLong()),
sideA.name
)
)
}
}
fun clear() {
previews.toList().forEach { cancel(it) }
}
fun remove(player: ServerPlayer) {
val preview = previews.firstOrNull { it.sideA.isPlayer && it.sideA.uuid == player.uuid || it.sideB.isPlayer && it.sideB.uuid == player.uuid } ?: return
cancel(preview)
}
fun cancel(preview: TeamPreview) {
if (!previews.remove(preview)) return
if (preview.sideA.isPlayer) {
preview.sideA.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
}
if (preview.sideB.isPlayer) {
preview.sideB.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
}
preview.onCancel(preview)
}
fun start(preview: TeamPreview) {
if (!previews.remove(preview)) return
if (preview.sideA.isPlayer) {
preview.sideA.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
}
if (preview.sideB.isPlayer) {
preview.sideB.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
}
preview.onStart(preview)
}
fun changeSelections(uuid: UUID, selections: Set<Int>) {
val preview = previews.firstOrNull { it.sideA.uuid == uuid || it.sideB.uuid == uuid } ?: return
val isSideA = preview.sideA.uuid == uuid
val side = if (isSideA) preview.sideA else preview.sideB
if (!side.isPlayer || !isValidSelection(side.party, selections, side.neededSelections, requireComplete = false)) return
previews.remove(preview)
if (isSideA) {
previews.add(
preview.copy(
sideA = preview.sideA.copy(selections = selections.toSet()),
)
)
}
else {
previews.add(
preview.copy(
sideB = preview.sideB.copy(selections = selections.toSet()),
)
)
}
}
fun hasEligibleSelections(side: TeamPreviewSide): Boolean =
isValidSelection(side.party, side.selections, side.neededSelections)
internal fun isValidSelection(party: List<*>, selections: Set<Int>, needed: Int, requireComplete: Boolean = true): Boolean {
if (needed < 1 || selections.size > needed || (requireComplete && selections.size != needed)) return false
return selections.all { it in party.indices && party[it] != null }
}
}
data class TeamPreview(
val sideA: TeamPreviewSide,
val sideB: TeamPreviewSide,
val started: Instant,
val onStart: (TeamPreview) -> Unit,
val onCancel: (TeamPreview) -> Unit
)
data class TeamPreviewSide(
val uuid: UUID,
val name: Component,
val isPlayer: Boolean,
val party: List<Pokemon?>,
val selections: Set<Int>,
val neededSelections: Int
) {
constructor(player: ServerPlayer, neededSelections: Int): this(player.uuid, player.name, true, player.party().toGappyList(), emptySet(), neededSelections)
constructor(pokemon: Pokemon, name: Component? = null): this(pokemon.uuid, name ?: pokemon.getDisplayName(), false, listOf(null, null, pokemon, null, null, null, null), setOf(2), 1)
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.battles.actor
import com.cobblemon.mod.common.battles.timers.PlayerBattleTimer
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
@@ -35,8 +33,6 @@ class PlayerBattleActor(
pokemonList: List<BattlePokemon>,
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
var timer: PlayerBattleTimer? = null
override val initialPos: Vec3?
override val entity: ServerPlayer?
get() = this.uuid.getPlayer()
@@ -37,7 +37,7 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
val optionalPokemon = message.battlePokemonFromOptional(battle)
// If there is an optional effect causing the activation, broadcast that instead of the standard effect
ShowdownInterpreter.broadcastAbility(battle, optionalEffect ?: effect, pokemon, revealedAbility = effect)
ShowdownInterpreter.broadcastAbility(battle, optionalEffect ?: effect, pokemon)
battle.dispatch {
val pokemonName = pokemon.getName()
@@ -63,4 +63,4 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
else return@dispatch GO
}
}
}
}
@@ -83,10 +83,6 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
}
battle.broadcastChatMessage(lang)
val changedStat = Stats.getStat(statKey)
val delta = if (isBoost) stages else -stages
pokemon.statChanges[changedStat] = ((pokemon.statChanges[changedStat] ?: 0) + delta).coerceIn(-6, 6)
pokemon.sendUpdate()
val boostBucket = if (isBoost) BattleContext.Type.BOOST else BattleContext.Type.UNBOOST
val context = ShowdownInterpreter.getContextFromAction(message, boostBucket, battle)
// TODO: replace with context that tracks detailed information such as # of stages
@@ -26,8 +26,6 @@ class ClearAllBoostInstruction(val message: BattleMessage): InterpreterInstructi
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
battle.activePokemon.forEach {
it.battlePokemon?.statChanges?.clear()
it.battlePokemon?.sendUpdate()
it.battlePokemon?.contextManager?.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
}
battle.broadcastChatMessage(battleLang("clearallboost"))
@@ -30,8 +30,6 @@ class ClearBoostInstruction(val message: BattleMessage): InterpreterInstruction
val pokemonName = battlePokemon.getName()
val lang = battleLang("clearboost", pokemonName)
battle.broadcastChatMessage(lang)
battlePokemon.statChanges.clear()
battlePokemon.sendUpdate()
battlePokemon.contextManager.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
battle.minorBattleActions[battlePokemon.uuid] = message
}
@@ -36,8 +36,6 @@ class ClearNegativeBoostInstruction(val message: BattleMessage): InterpreterInst
battle.broadcastChatMessage(lang)
}
battlePokemon.statChanges.entries.removeIf { it.value < 0 }
battlePokemon.sendUpdate()
battlePokemon.contextManager.clear(BattleContext.Type.UNBOOST)
battle.minorBattleActions[battlePokemon.uuid] = message
}
@@ -32,10 +32,6 @@ class CopyBoostInstruction(val message: BattleMessage): InterpreterInstruction {
val lang = battleLang("copyboost.generic", pokemonName, targetPokemonName)
battle.broadcastChatMessage(lang)
val changes = targetPokemon.statChanges.toMap()
pokemon.statChanges.clear()
pokemon.statChanges.putAll(changes)
pokemon.sendUpdate()
pokemon.contextManager.copy(targetPokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
battle.minorBattleActions[pokemon.uuid] = message
}
@@ -155,13 +155,6 @@ class DamageInstruction(
val newHealthRatio: Float
val remainingHealth = newHealth.split("/")[0].toInt()
val previousHealth = battlePokemon.effectedPokemon.currentHealth
val damageDealt = (previousHealth - remainingHealth).coerceAtLeast(0)
val maxHp = battlePokemon.effectedPokemon.maxHealth
if (damageDealt > 0 && maxHp > 0) {
val percent = Math.round(damageDealt.toFloat() / maxHp * 100F).coerceAtLeast(1)
battle.broadcastChatMessage(battleLang("damage_dealt", battlePokemon.getName(), "$percent%"))
}
if (effect != null) {
val lang = when (effect.id) {
@@ -229,4 +222,4 @@ class DamageInstruction(
}
}
}
}
@@ -35,8 +35,6 @@ class EndItemInstruction(val message: BattleMessage): InterpreterInstruction {
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val itemEffect = message.effectAt(1) ?: return@dispatchWaiting
battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
battlePokemon.revealedHeldItem = net.minecraft.world.item.ItemStack.EMPTY
battlePokemon.sendUpdate()
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.remove(itemEffect.id, BattleContext.Type.ITEM)
if (message.hasOptionalArgument("eat")) {
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
@@ -60,11 +58,6 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
battle.actors.forEach { actor ->
actor.sendUpdate(BattleSetTeamPokemonPacket(actor.pokemonList.map { it.effectedPokemon }))
battle.actors.forEach { viewer ->
viewer.sendUpdate(BattleActorTeamPacket(actor.uuid, actor.pokemonList.map {
it.toBattleDTO(ally = viewer == actor)
}))
}
val req = actor.request ?: return@forEach
actor.sendUpdate(BattleQueueRequestPacket(req))
}
@@ -75,10 +68,8 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
battle.dispatchGo {
battle.started = true
battle.activePokemon.mapNotNull { it.battlePokemon }.forEach { it.sendUpdate() }
battle.notifyAllOfUpdates()
battle.side1.playCries()
afterOnServer(seconds = 1.0F) { battle.side2.playCries() }
}
}
}
}
@@ -28,8 +28,6 @@ class InvertBoostInstruction(val message: BattleMessage): InterpreterInstruction
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
pokemon.statChanges.replaceAll { _, value -> -value }
pokemon.sendUpdate()
val name = pokemon.getName()
battle.broadcastChatMessage(battleLang("invertboost", name))
@@ -34,8 +34,6 @@ class ItemInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting(1.5F) {
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
battlePokemon.heldItemManager.handleStartInstruction(battlePokemon, battle, message)
battlePokemon.revealedHeldItem = battlePokemon.effectedPokemon.heldItem.copy()
battlePokemon.sendUpdate()
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.ITEM, battle))
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
@@ -76,14 +74,6 @@ class MoveInstruction(
battle.dispatch { UntilDispatch { instructionSet.getMostRecentInstruction<MoveInstruction>(this)?.future?.isDone != false } }
battle.dispatch {
val knownMove = userPokemon.revealedMoves.firstOrNull { it?.move == move.displayName }
if (knownMove != null) {
knownMove.timesUsed++
} else {
val slot = userPokemon.revealedMoves.indexOfFirst { it == null }
if (slot >= 0) userPokemon.revealedMoves[slot] = MoveDTO(move.displayName, 1)
}
userPokemon.sendUpdate()
val pokemonName = userPokemon.getName()
ShowdownInterpreter.lastCauser[battle.battleId] = message
// For Spread targets the message data only gives the pnx strings. So we can't know what pokemon are actually targeted until the previous messages have been interpreted
@@ -19,13 +19,6 @@ import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
*/
object PostUpdateInstruction : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
battle.actors.forEach { it.postUpdate() }
battle.notifyAllOfUpdates()
val viewers = battle.actors.flatMap { it.getPlayerUUIDs() } + battle.spectators
battle.actors.forEach { actor ->
actor.pokemonList.forEach { it.updateBattleActorInformation(viewers) }
}
}
battle.dispatchGo { battle.actors.forEach { it.postUpdate() } }
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
@@ -29,12 +27,6 @@ class SetBoostInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val stat = message.argumentAt(1)?.let { Stats.getStat(it) }
val amount = message.argumentAt(2)?.toIntOrNull()
if (stat != null && amount != null) {
pokemon.statChanges[stat] = amount.coerceIn(-6, 6)
pokemon.sendUpdate()
}
val pokemonName = pokemon.getName()
val effectID = message.effect()?.id ?: return@dispatchWaiting
val lang = battleLang("setboost.$effectID", pokemonName)
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
@@ -31,15 +29,6 @@ class SwapBoostInstruction(val message: BattleMessage): InterpreterInstruction {
val pokemonName = pokemon.getName()
val targetPokemon = message.battlePokemon(1, battle) ?: return@dispatchWaiting
val targetPokemonName = targetPokemon.getName()
val stats = message.argumentAt(2)?.split(',')?.map { Stats.getStat(it.trim()) }
?: (pokemon.statChanges.keys + targetPokemon.statChanges.keys).toList()
stats.forEach { stat ->
val previous = pokemon.statChanges[stat] ?: 0
pokemon.statChanges[stat] = targetPokemon.statChanges[stat] ?: 0
targetPokemon.statChanges[stat] = previous
}
pokemon.sendUpdate()
targetPokemon.sendUpdate()
val effectID = message.effect()?.id ?: return@dispatchWaiting
val lang = when (effectID) {
"guardswap", "powerswap", "heartswap" -> battleLang("swapboost.$effectID", pokemonName)
@@ -129,14 +129,6 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
activePokemon.battlePokemon?.let { oldPokemon ->
if (publicMessage.effect()?.id == "batonpass") oldPokemon.contextManager.swap(pokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
pokemon.statChanges.clear()
if (publicMessage.effect()?.id == "batonpass") pokemon.statChanges.putAll(oldPokemon.statChanges)
oldPokemon.statChanges.clear()
if (oldPokemon.transformed != null) {
oldPokemon.revealedMoves.indices.forEach { oldPokemon.revealedMoves[it] = null }
oldPokemon.revealedAbility = null
}
oldPokemon.transformed = null
oldPokemon.contextManager.clear(BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
battle.majorBattleActions[oldPokemon.uuid] = publicMessage
@@ -223,8 +215,6 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
}
private fun broadcastSwitch(battle: PokemonBattle, actor: BattleActor, newPokemon: BattlePokemon, illusion: BattlePokemon?) {
// The active slot and its illusion are now set, so reveal the identity that entered battle.
newPokemon.sendUpdate()
val publicPokemon = (illusion ?: newPokemon).effectedPokemon
val publicLang = publicPokemon.nickname?.let { nickname ->
battleLang("switch.other.nickname", actor.getName(), nickname, publicPokemon.species.translatedName)
@@ -239,4 +229,4 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
}
}
}
}
@@ -30,8 +30,6 @@ class TerastallizeInstruction(val message: BattleMessage): InterpreterInstructio
val battlePokemon = message.battlePokemon(0, battle) ?: return
val type = message.effectAt(1)?.let { TeraTypes.get(it.id) } ?: return
battle.dispatchWaiting {
battlePokemon.terastallized = type
battlePokemon.sendUpdate()
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("terastallize", pokemonName, type.displayName).yellow())
CobblemonEvents.TERASTALLIZATION.post(TerastallizationEvent(battle, battlePokemon, type))
@@ -46,16 +46,6 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
}
battle.dispatchWaiting {
pokemon.transformed = targetPokemon
pokemon.statChanges.clear()
pokemon.statChanges.putAll(targetPokemon.statChanges)
for (index in pokemon.revealedMoves.indices) {
pokemon.revealedMoves[index] = targetPokemon.moveSet.get(index)?.let {
com.cobblemon.mod.common.net.messages.client.battle.MoveDTO(it.displayName, 0)
}
}
pokemon.revealedAbility = targetPokemon.effectedPokemon.ability.name
pokemon.sendUpdate()
val mock = pokemon.entity?.effects?.mockEffect?.mock
val pokemonName = pokemon.getName()
val targetPokemonName = targetPokemon.getName()
@@ -73,4 +63,4 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
}
@@ -8,24 +8,6 @@
package com.cobblemon.mod.common.battles.pokemon
import net.minecraft.world.item.ItemStack
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorInformationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
import com.cobblemon.mod.common.api.types.tera.TeraType
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
@@ -138,13 +120,6 @@ open class BattlePokemon(
val contextManager = ContextManager()
var revealed = false
var revealedAbility: String? = null
val revealedMoves: MutableList<MoveDTO?> = mutableListOf(null, null, null, null)
var revealedHeldItem: ItemStack? = null
var transformed: BattlePokemon? = null
var terastallized: TeraType? = null
open fun getName(): MutableComponent {
val displayPokemon = getIllusion()?.effectedPokemon ?: effectedPokemon
return if (actor is PokemonBattleActor || actor is MultiPokemonBattleActor) {
@@ -154,72 +129,8 @@ open class BattlePokemon(
}
}
fun toBattleDTO(ally: Boolean, forceReveal: Boolean = false): BattlePokemonDTO {
val moves = (transformed?.effectedPokemon ?: effectedPokemon).moveSet.getMovesWithNulls().map { move ->
move?.let { MoveDTO(it.displayName, revealedMoves.firstOrNull { known -> known?.move == it.displayName }?.timesUsed ?: 0) }
}
val boostMultipliers = statChanges.entries.mapNotNull { (stat, stage) ->
val standardStat = stat as? Stats ?: return@mapNotNull null
val base = if (standardStat == Stats.ACCURACY || standardStat == Stats.EVASION) 3.0 else 2.0
val clamped = stage.coerceIn(-6, 6)
standardStat to if (clamped >= 0) (base + clamped) / base else base / (base - clamped)
}.toMap().filterValues { it != 1.0 }
val format = if (::actor.isInitialized && actor.isInitialized()) actor.battle.format else null
val isTeamSheet = format?.isOpenTeamSheet == true
if (ally || isTeamSheet) {
val allyDto = BattleInitializePacket.ActiveBattlePokemonDTO.fromPokemon(this, ally, getIllusion())
return BattlePokemonDTO(
this.effectedPokemon.uuid,
this.effectedPokemon.isFainted(),
(transformed?.effectedPokemon ?: effectedPokemon).ability.name,
moves,
this.effectedPokemon.heldItem,
boostMultipliers,
if (ally) effectedPokemon.speed else null,
terastallized = terastallized,
allyDto
)
}
else {
// Team Preview reveals identities, while moves, abilities and items still require discovery.
val nonAllyDto = if (forceReveal || revealed || format?.hasTeamPreview == true) {
BattleInitializePacket.ActiveBattlePokemonDTO.fromPokemon(this, false, getIllusion())
} else null
return BattlePokemonDTO(
this.effectedPokemon.uuid,
this.effectedPokemon.isFainted(),
revealedAbility,
revealedMoves,
revealedHeldItem,
boostMultipliers,
speed = null,
terastallized = terastallized,
nonAllyDto
)
}
}
fun updateBattleActorInformation(uuids: List<UUID>) {
uuids.forEach {
if (actor.uuid == it) {
it.getPlayer()?.sendPacket(
BattleActorInformationPacket(actor.uuid, toBattleDTO(true))
)
}
else {
it.getPlayer()?.sendPacket(
BattleActorInformationPacket(actor.uuid, toBattleDTO(false))
)
}
}
}
fun sendUpdate() {
actor.sendUpdate(BattleUpdateTeamPokemonPacket(effectedPokemon))
revealed = revealed || isSentOut()
updateBattleActorInformation(actor.battle.actors.flatMap { it.getPlayerUUIDs() } + actor.battle.spectators)
}
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
@@ -230,7 +141,5 @@ open class BattlePokemon(
!isSentOut() && !willBeSwitchedIn && health > 0
}
fun getIllusion(): BattlePokemon? = if (::actor.isInitialized) {
actor.activePokemon.find { it.battlePokemon == this }?.illusion
} else null
}
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
}
@@ -1,28 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.timers
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ForfeitActionResponse
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import java.time.Instant
interface PlayerBattleTimer {
val actor: PlayerBattleActor
val battle: PokemonBattle
fun startSelection()
fun endSelection()
fun tick()
fun mustChooseBy(): Instant
fun timeout() {
actor.setActionResponses(listOf(ForfeitActionResponse()))
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.timers
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import java.time.Duration
import java.time.Instant
class ShowdownTimer(override val battle: PokemonBattle, override val actor: PlayerBattleActor): PlayerBattleTimer {
private lateinit var turnStart: Instant
private var secondsRemaining = 210
private var hasSelected = true
override fun tick() {
if (hasSelected || !::turnStart.isInitialized || battle.ended) return
val secondsSinceStart = Duration.between(turnStart, Instant.now()).seconds
if (secondsRemaining - secondsSinceStart <= 0) {
hasSelected = true
timeout()
}
}
override fun startSelection() {
turnStart = Instant.now()
hasSelected = false
}
override fun endSelection() {
if (hasSelected || !::turnStart.isInitialized) return
hasSelected = true
val secondsSinceStart = Duration.between(turnStart, Instant.now()).seconds.toInt()
secondsRemaining = secondsRemaining - secondsSinceStart + 10
}
override fun mustChooseBy(): Instant {
return turnStart.plusSeconds(secondsRemaining.toLong())
}
}
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.decor
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class ActivatableDecorationBlock(settings: Properties, val type: ActivatableDecorationBlockType) : HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
enum class ActivatableDecorationBlockType: StringRepresentable {
//For basic use, also ignore camelcase errors, it's just dumb
CENTERED_8x,
//For specific items
METRONOME;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val CODEC: MapCodec<ActivatableDecorationBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
ActivatableDecorationBlockType.CODEC.fieldOf("type").forGetter(ActivatableDecorationBlock::type)
).apply(it, ::ActivatableDecorationBlock) }
val ACTIVE = BooleanProperty.create("active")
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
val METRONOME_HITBOX = Shapes.box(0.25, 0.0, 0.375, 0.75, 0.8125, 0.75)
fun getShape(type: ActivatableDecorationBlockType): VoxelShape {
return when (type) {
ActivatableDecorationBlockType.METRONOME -> METRONOME_HITBOX
else -> CENTERED_8x
}
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(ACTIVE, false)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<ActivatableDecorationBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, ACTIVE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
return rotateShape(Direction.NORTH, direction, getShape(type))
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return getCollisionShape(state, level, pos, context).singleEncompassing()
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if (level.isClientSide) {
level.setBlockAndUpdate(pos, state.cycle(ACTIVE))
return InteractionResult.SUCCESS
} else {
this.push(state, level, pos, null as Player?)
return InteractionResult.CONSUME
}
}
fun push(state: BlockState, level: Level, pos: BlockPos?, player: Player?) {
var state = state
state = state.cycle(ACTIVE)
level.setBlock(pos!!, state, 3)
level.gameEvent(player, if (state.getValue(ACTIVE)) GameEvent.BLOCK_ACTIVATE else GameEvent.BLOCK_DEACTIVATE, pos)
}
override fun hasAnalogOutputSignal(state: BlockState): Boolean {
return true
}
//TODO: Replace this forced 4 with a value that can be set by the block, only relevant when more blocks of this class are added.
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
return if (level.getBlockState(pos).getValue(ACTIVE) as Boolean) 4 else 0
}
}
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class DecorativeItemBlock(settings: Properties, val type: DecorativeItemBlockType): HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
enum class DecorativeItemBlockType: StringRepresentable {
//For basic use, also ignore camelcase errors, it's just dumb
CENTERED_8x,
CENTERED_6x,
FLATTENED_8x6,
FLATTENED_8x4,
FLATTENED_10x4,
//For specific items
TEACUP,
TEAPOT,
KNOT,
POUCH,
NEVERMELT,
SCOPE,
WIDE,
ZOOM;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val CODEC: MapCodec<DecorativeItemBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
DecorativeItemBlockType.CODEC.fieldOf("type").forGetter(DecorativeItemBlock::type)
).apply(it, ::DecorativeItemBlock) }
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
val CENTERED_6x = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
val FLAT6_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.375, 0.75)
val FLAT4_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.25, 0.75)
val SMOOSHED4_HITBOX = Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.25, 0.8125)
val TEACUP_HITBOX = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.25, 0.6875)
val TEAPOT_HITBOX = Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.625, 0.8125)
val KNOT_HITBOX = Shapes.box(0.375, 0.0, 0.375, 0.75, 0.375, 0.75)
val POUCH_HITBOX = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.3125, 0.6875)
val NEVERMELT_HITBOX = Shapes.box(0.25, 0.0, 0.3125, 0.6875, 0.25, 0.75)
val SCOPE_HITBOX = Shapes.box(0.625, 0.0, 0.4375, 0.9375, 0.125, 0.75)
val WIDE_HITBOX = Shapes.box(0.625, 0.0, 0.125, 0.9375, 0.0625, 0.625)
val ZOOM_HITBOX = Shapes.box(0.125, 0.0, 0.1875, 0.375, 0.1875, 0.5)
fun getShape(type: DecorativeItemBlockType): VoxelShape {
return when (type) {
DecorativeItemBlockType.TEAPOT -> TEAPOT_HITBOX
DecorativeItemBlockType.TEACUP -> TEACUP_HITBOX
DecorativeItemBlockType.KNOT -> KNOT_HITBOX
DecorativeItemBlockType.POUCH -> POUCH_HITBOX
DecorativeItemBlockType.NEVERMELT -> NEVERMELT_HITBOX
DecorativeItemBlockType.SCOPE -> SCOPE_HITBOX
DecorativeItemBlockType.WIDE -> WIDE_HITBOX
DecorativeItemBlockType.ZOOM -> ZOOM_HITBOX
DecorativeItemBlockType.FLATTENED_10x4 -> SMOOSHED4_HITBOX
DecorativeItemBlockType.FLATTENED_8x4 -> FLAT4_HITBOX
DecorativeItemBlockType.FLATTENED_8x6 -> FLAT6_HITBOX
DecorativeItemBlockType.CENTERED_6x -> CENTERED_6x
else -> CENTERED_8x
}
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(WATERLOGGED, false))
}
override fun codec(): MapCodec<DecorativeItemBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
return rotateShape(Direction.NORTH, direction, getShape(type))
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
}
}
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.ButtonBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockSetType
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class EjectButtonBlock(properties: Properties, ticksToStayPressed: Int) : ButtonBlock(BlockSetType.IRON, ticksToStayPressed, properties), SimpleWaterloggedBlock {
companion object {
val FLOOR_UNPOWERED = Shapes.or(
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875),
Shapes.box(0.375, 0.1875, 0.375, 0.625, 0.3125, 0.625) )
val FLOOR_POWERED = Shapes.or(
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875),
Shapes.box(0.375, 0.1875, 0.375, 0.625, 0.25, 0.625) )
val FLOOR_PANEL = Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875)
val CEILING_UNPOWERED = Shapes.or(
Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875),
Shapes.box(0.375, 0.6875, 0.375, 0.625, 0.8125, 0.625) )
val CEILING_POWERED = Shapes.or(
Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875),
Shapes.box(0.375, 0.75, 0.375, 0.625, 0.8125, 0.625) )
val CEILING_PANEL = Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875)
val WALL_UNPOWERED = Shapes.or(
Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0),
Shapes.box(0.375, 0.375, 0.6875, 0.625, 0.625, 0.8125) )
val WALL_POWERED = Shapes.or(
Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0),
Shapes.box(0.375, 0.375, 0.75, 0.625, 0.625, 0.8125) )
val WALL_PANEL = Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(POWERED, false)
.setValue(WATERLOGGED, false)
)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, FACE, POWERED, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
for (direction in context.nearestLookingDirections) {
val blockState: BlockState
if (direction.getAxis() === Direction.Axis.Y) {
blockState = (defaultBlockState()
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
} else {
blockState = (defaultBlockState()
.setValue(FACE, AttachFace.WALL) as BlockState)
.setValue(FACING, direction.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
}
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
}
return null
}
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR -> if (state.getValue(POWERED)) {FLOOR_POWERED} else {FLOOR_UNPOWERED}
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> if (state.getValue(POWERED)) {WALL_POWERED} else {WALL_UNPOWERED}
else -> rotateShape(Direction.NORTH, direction, if (state.getValue(POWERED)) {WALL_POWERED} else {WALL_UNPOWERED})
}
AttachFace.CEILING -> if (state.getValue(POWERED)) {CEILING_POWERED} else {CEILING_UNPOWERED}
}
}
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR -> FLOOR_PANEL
AttachFace.WALL -> when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_PANEL
else -> rotateShape(Direction.NORTH, direction, WALL_PANEL)
}
AttachFace.CEILING -> CEILING_PANEL
}
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return if (state.getValue(FACE) != AttachFace.WALL) {
val direction = getConnectedDirection(state)
canSupportCenter(level, pos.relative(direction.opposite), direction)
} else {
level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
}
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class GalaricaWreathBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
companion object {
val CODEC: MapCodec<GalaricaWreathBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::GalaricaWreathBlock)
}
val FLOOR_NORTH = Shapes.box(0.125, 0.0, 0.125, 0.875, 0.25, 0.875)
val WALL_NORTH = Shapes.box(0.125, 0.125, 0.75, 0.875, 0.875, 1.0)
val CEILING_NORTH = Shapes.box(0.125, 0.125, 0.375, 0.875, 0.625, 0.5625)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<GalaricaWreathBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
} else FLOOR_NORTH
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
}
AttachFace.CEILING ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
} else CEILING_NORTH
}
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class MagnetBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
companion object {
val CODEC: MapCodec<MagnetBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::MagnetBlock)
}
val FLOOR_NORTH = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.1875, 0.75)
val WALL_NORTH = Shapes.box(0.25, 0.25, 0.8125, 0.75, 0.75, 1.0)
val CEILING_NORTH = Shapes.box(0.25, 0.8125, 0.25, 0.75, 1.0, 0.75)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<MagnetBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
} else FLOOR_NORTH
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
}
AttachFace.CEILING ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
} else CEILING_NORTH
}
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return 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.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class OrbBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
companion object {
val CODEC: MapCodec<OrbBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::OrbBlock)
}
val FLOOR_NORTH = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
val WALL_NORTH = Shapes.box(0.3125, 0.3125, 0.625, 0.6875, 0.6875, 1.0)
val CEILING_NORTH = Shapes.box(0.3125, 0.625, 0.375, 0.6875, 1.0, 0.6875)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<OrbBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
} else FLOOR_NORTH
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
}
AttachFace.CEILING ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
} else CEILING_NORTH
}
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return true
}
}
@@ -13,25 +13,15 @@ import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
class PlaqueBlock(properties : Properties) : SixFaceRotationalBlock(properties) {
companion object {
val CODEC: MapCodec<PlaqueBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::PlaqueBlock)
@@ -54,40 +44,6 @@ class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBl
)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, FACE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
for (direction in context.nearestLookingDirections) {
val blockState: BlockState
if (direction.getAxis() === Direction.Axis.Y) {
blockState = (defaultBlockState()
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
} else {
blockState = (defaultBlockState()
.setValue(FACE, AttachFace.WALL) as BlockState)
.setValue(FACING, direction.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
}
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
}
return null
}
override fun codec(): MapCodec<PlaqueBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
@@ -108,21 +64,4 @@ class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBl
} else CEILING_NORTH
}
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return if (state.getValue(FACE) == AttachFace.CEILING) {
val direction = getConnectedDirection(state)
canSupportCenter(level, pos.relative(direction.opposite), direction)
} else {
super.canSurvive(state, level, pos)
}
}
}
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.decor
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class RingTargetBlock(settings: Properties) : TargetBlock(settings), SimpleWaterloggedBlock {
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(WATERLOGGED, false)
.setValue(OUTPUT_POWER, 0))
}
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
var blockState = defaultBlockState()
.setValue(WATERLOGGED, ctx.level.getFluidState(ctx.clickedPos).type == Fluids.WATER)
val worldView = ctx.level
val blockPos = ctx.clickedPos
ctx.nearestLookingDirections.forEach { direction ->
if (direction.axis.isHorizontal) {
blockState = blockState.setValue(FACING, direction.opposite) as BlockState
if (blockState.canSurvive(worldView, blockPos)) {
return blockState
}
}
}
return null
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
builder.add(WATERLOGGED)
builder.add(*arrayOf<Property<*>>(OUTPUT_POWER))
}
override fun getShape(
state: BlockState,
blockGetter: BlockGetter,
pos: BlockPos,
collisionContext: CollisionContext
): VoxelShape {
return when (state.getValue(FACING)) {
Direction.SOUTH -> FULL_PLANE_SOUTH
Direction.WEST -> FULL_PLANE_WEST
Direction.EAST -> FULL_PLANE_EAST
else -> FULL_PLANE_NORTH
}
}
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (direction.opposite == state.getValue(FACING) && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)))
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
}
companion object {
val CODEC: MapCodec<RingTargetBlock> = RecordCodecBuilder.mapCodec {
it.group(
propertiesCodec()
).apply(it, ::RingTargetBlock) }
val OUTPUT_POWER = BlockStateProperties.POWER;
val FULL_PLANE_NORTH = Shapes.box(0.0, 0.0, 0.925, 1.0, 1.0, 1.0)
val FULL_PLANE_WEST = Shapes.box(0.925, 0.0, 0.0, 1.0, 1.0, 1.0)
val FULL_PLANE_SOUTH = Shapes.box(0.0, 0.0, 0.0, 1.0, 1.0, 0.075)
val FULL_PLANE_EAST = Shapes.box(0.0, 0.0, 0.0, 0.075, 1.0, 1.0)
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
open class SixFaceRotationalBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
companion object {
val CODEC = simpleCodec(::SixFaceRotationalBlock)
}
override fun codec(): MapCodec<out SixFaceRotationalBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, FACE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
for (direction in context.nearestLookingDirections) {
val blockState: BlockState
if (direction.getAxis() === Direction.Axis.Y) {
blockState = (defaultBlockState()
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
} else {
blockState = (defaultBlockState()
.setValue(FACE, AttachFace.WALL) as BlockState)
.setValue(FACING, direction.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
}
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
}
return null
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return if (state.getValue(FACE) == AttachFace.CEILING) {
val direction = getConnectedDirection(state)
canSupportCenter(level, pos.relative(direction.opposite), direction)
} else if (state.getValue(FACE) == AttachFace.WALL) {
level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
} else {
super.canSurvive(state, level, pos)
}
}
}
@@ -39,7 +39,13 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
ETHER,
MAX_POTION,
POTION,
STATUS_RESTORE;
STATUS_RESTORE,
VITAMIN,
METAL_COAT,
IRON_BALL,
THROAT_SPRAY,
UPGRADE,
DICE;
override fun getSerializedName(): String = this.name.lowercase()
@@ -57,98 +63,78 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
StackableItemBlockType.CODEC.fieldOf("type").forGetter(StackableItemBlock::type)
).apply(it, ::StackableItemBlock) }
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>> = mapOf(
StackableItemBlockType.BATTLE_ITEM to listOf(
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.34375, 0.40625, 0.4375, 0.65625),
Shapes.box(0.59375, 0.0, 0.40625, 0.90625, 0.4375, 0.71875)
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>>
get() = mapOf(
StackableItemBlockType.BATTLE_ITEM to listOf(
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
Shapes.box(0.09375, 0.0, 0.34375, 0.90625, 0.4375, 0.71875),
Shapes.box(0.09375, 0.0, 0.09375, 0.90625, 0.4375, 0.90625),
Shapes.box(0.09375, 0.0, 0.09375, 0.96875, 0.4375, 0.90625)
),
Shapes.or(
Shapes.box(0.59375, 0.0, 0.59375, 0.90625, 0.4375, 0.90625),
Shapes.box(0.09375, 0.0, 0.53125, 0.40625, 0.4375, 0.84375),
Shapes.box(0.34375, 0.0, 0.09375, 0.65625, 0.4375, 0.40625)
StackableItemBlockType.ETHER to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.5, 0.75),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.5, 1.0),
Shapes.box(0.125, 0.0, 0.0625, 0.9375, 0.5, 1.0)
),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.09375, 0.40625, 0.4375, 0.40625),
Shapes.box(0.59375, 0.0, 0.15625, 0.90625, 0.4375, 0.46875),
Shapes.box(0.65625, 0.0, 0.59375, 0.96875, 0.4375, 0.90625),
Shapes.box(0.15625, 0.0, 0.53125, 0.46875, 0.4375, 0.84375)
)
),
StackableItemBlockType.ETHER to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
Shapes.or(
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5, 0.75),
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5, 0.6875)
StackableItemBlockType.MAX_POTION to listOf(
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
Shapes.box(0.09375, 0.0, 0.3125, 0.90625, 0.5, 0.75),
Shapes.box(0.09375, 0.0, 0.0625, 0.90625, 0.5, 1.0),
Shapes.box(0.09375, 0.0, 0.0625, 0.96875, 0.5, 1.0)
),
Shapes.or(
Shapes.box(0.375, 0.0, 0.125, 0.625, 0.5, 0.5),
Shapes.box(0.125, 0.0, 0.5625, 0.375, 0.5, 0.9375),
Shapes.box(0.625, 0.0, 0.625, 0.875, 0.5, 1.0)
StackableItemBlockType.POTION to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.5625, 0.8125),
Shapes.box(0.125, 0.0, 0.0625, 0.875, 0.5625, 1.0),
Shapes.box(0.125, 0.0, 0.0, 0.9375, 0.5625, 1.0)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.0625, 0.375, 0.5, 0.4375),
Shapes.box(0.625, 0.0, 0.125, 0.875, 0.5, 0.5),
Shapes.box(0.6875, 0.0, 0.625, 0.9375, 0.5, 1.0),
Shapes.box(0.1875, 0.0, 0.5625, 0.4375, 0.5, 0.9375)
)
),
StackableItemBlockType.MAX_POTION to listOf(
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.3125, 0.40625, 0.5, 0.6875),
Shapes.box(0.59375, 0.0, 0.375, 0.90625, 0.5, 0.75)
StackableItemBlockType.STATUS_RESTORE to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.4375, 0.75),
Shapes.box(0.125, 0.0, 0.0625, 0.875, 0.4375, 0.9375),
Shapes.box(0.125, 0.0, 0.0, 0.9375, 0.4375, 0.9375)
),
Shapes.or(
Shapes.box(0.34375, 0.0, 0.0625, 0.65625, 0.5, 0.4375),
Shapes.box(0.09375, 0.0, 0.5625, 0.40625, 0.5, 0.9375),
Shapes.box(0.59375, 0.0, 0.625, 0.90625, 0.5, 1.0)
StackableItemBlockType.METAL_COAT to listOf(
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875),
Shapes.box(0.125, 0.0, 0.0625, 0.9375, 0.375, 0.875),
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375)
),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.0625, 0.40625, 0.5, 0.4375),
Shapes.box(0.15625, 0.0, 0.5625, 0.46875, 0.5, 0.9375),
Shapes.box(0.65625, 0.0, 0.625, 0.96875, 0.5, 1.0),
Shapes.box(0.59375, 0.0, 0.125, 0.90625, 0.5, 0.5)
).singleEncompassing()
),
StackableItemBlockType.POTION to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
Shapes.or(
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5625, 0.75),
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5625, 0.8125)
StackableItemBlockType.VITAMIN to listOf(
Shapes.box(0.375, 0.0, 0.375, 0.625, 0.4375, 0.625),
Shapes.box(0.1875, 0.0, 0.3125, 0.8125, 0.4375, 0.6875),
Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.4375, 0.8125),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.4375, 0.875)
),
Shapes.or(
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.5625, 0.5),
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.5625, 1.0),
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.5625, 0.9375)
StackableItemBlockType.IRON_BALL to listOf(
Shapes.box(0.25, 0.0, 0.25, 0.75, 0.4375, 0.75),
Shapes.box(0.0, 0.0, 0.1875, 1.0, 0.4375, 0.8125),
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
Shapes.box(0.25, 0.4375, 0.3125, 0.6875, 0.875, 0.75)
)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.5625, 0.4375),
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.5625, 0.5),
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.5625, 1.0),
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.5625, 0.9375)
)
),
StackableItemBlockType.STATUS_RESTORE to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
Shapes.or(
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.4375, 0.75),
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.4375, 0.75)
StackableItemBlockType.THROAT_SPRAY to listOf(
Shapes.box(0.375, 0.0, 0.1875, 0.625, 0.5625, 0.75),
Shapes.box(0.125, 0.0, 0.1875, 0.875, 0.5625, 0.8125),
Shapes.box(0.125, 0.0, 0.0, 0.875, 0.5625, 1.0),
Shapes.box(0.075, 0.0, 0.0, 0.9375, 0.5625, 1.0)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.4375, 0.875),
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.4375, 0.9375),
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.4375, 0.4375)
StackableItemBlockType.UPGRADE to listOf(
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.125, 0.6875),
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.25, 0.6875),
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875),
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.5, 0.6875)
),
Shapes.or(
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.4375, 0.875),
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.4375, 0.375),
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.4375, 0.4375),
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.4375, 0.9375)
StackableItemBlockType.DICE to listOf(
Shapes.box(0.125, 0.0, 0.6875, 0.3125, 0.1875, 0.875),
Shapes.box(0.125, 0.0, 0.625, 0.5625, 0.1875, 0.875),
Shapes.box(0.125, 0.0, 0.4375, 0.5625, 0.1875, 0.875),
Shapes.box(0.125, 0.0, 0.4375, 0.5625, 0.375, 0.875)
)
)
)
fun getShape(type: StackableItemBlockType, count: Int): VoxelShape = northShapeMap[type]?.let { list -> list[(count - 1).coerceIn(0, list.lastIndex)] } ?: Shapes.block()
fun getMaxAmount(type: StackableItemBlockType): Int = min(northShapeMap[type]?.size ?: 1 , MAX_AMOUNT)
@@ -184,7 +170,7 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return getCollisionShape(state, level, pos, context).singleEncompassing()
return getCollisionShape(state, level, pos, context)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
@@ -128,6 +128,7 @@ class DiscShelfBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobbl
fun isValidItem(stack: ItemStack): Boolean {
return stack.item is TechnicalMachineItem
|| stack.item == CobblemonItems.BLANK_TM
|| stack.item == CobblemonItems.UPGRADE
|| stack.item == CobblemonItems.DUBIOUS_DISC
|| stack.has(DataComponents.JUKEBOX_PLAYABLE)
@@ -31,6 +31,7 @@ import com.cobblemon.mod.common.client.sound.instances.CancellableSoundInstance
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.giveOrDropItemStack
import com.cobblemon.mod.common.util.lang
@@ -235,6 +236,7 @@ class FossilMultiblockStructure (
entity.setPos(fixedPosition.center.subtract(0.0, 0.5, 0.0))
// TODO: Find a correct way to set the new entity's Yaw rotation. (Face away from the machine)
if (world.addFreshEntity(entity)) {
pokemon.state = SentOutState(entity)
CobblemonEvents.FOSSIL_REVIVED.post(FossilRevivedEvent(pokemon, null))
return true
} else {
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
@@ -24,6 +22,7 @@ import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.entity.TintBlockEntity
import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
@@ -46,6 +45,7 @@ import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
import com.cobblemon.mod.common.client.render.generic.GenericBedrockRenderer
import com.cobblemon.mod.common.client.render.item.CobblemonBuiltinItemRendererRegistry
import com.cobblemon.mod.common.client.render.item.PokemonItemRenderer
import com.cobblemon.mod.common.client.render.layer.PlayerRecallBallRenderer
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
@@ -126,7 +126,6 @@ object CobblemonClient {
storage.onLogout()
battle = null
battleOverlay.onLogout()
ClientBattleInformationRepository.clear()
ClientTaskTracker.clear()
checkedStarterScreen = false
overlay.resetAttachedToast()
@@ -241,6 +240,12 @@ object CobblemonClient {
CobblemonBlocks.POKE_CAKE
)
this.implementation.registerBlockRenderType(
RenderType.translucent(),
CobblemonBlocks.UPGRADE,
CobblemonBlocks.DUBIOUS_DISC
)
this.implementation.registerBlockRenderType(
RenderType.cutout(),
CobblemonBlocks.GILDED_CHEST,
@@ -386,7 +391,31 @@ object CobblemonClient {
CobblemonBlocks.X_SP_ATK,
CobblemonBlocks.X_SP_DEF,
CobblemonBlocks.X_SPEED,
CobblemonBlocks.TM_MACHINE
CobblemonBlocks.TM_MACHINE,
CobblemonBlocks.GALARICA_CUFF,
CobblemonBlocks.GALARICA_WREATH,
CobblemonBlocks.CHIPPED_POT,
CobblemonBlocks.CRACKED_POT,
CobblemonBlocks.UNREMARKABLE_TEACUP,
CobblemonBlocks.MASTERPIECE_TEACUP,
CobblemonBlocks.LUMINOUS_MOSS,
CobblemonBlocks.ICY_ROCK,
CobblemonBlocks.HEAT_ROCK,
CobblemonBlocks.SMOOTH_ROCK,
CobblemonBlocks.DAMP_ROCK,
CobblemonBlocks.DESTINY_KNOT,
CobblemonBlocks.METRONOME,
CobblemonBlocks.KINGS_ROCK,
CobblemonBlocks.FLAME_ORB,
CobblemonBlocks.LIFE_ORB,
CobblemonBlocks.TOXIC_ORB,
CobblemonBlocks.LIGHT_BALL,
CobblemonBlocks.SMOKE_BALL,
CobblemonBlocks.NEVER_MELT_ICE,
CobblemonBlocks.WIDE_LENS,
CobblemonBlocks.ZOOM_LENS,
CobblemonBlocks.SCOPE_LENS
)
this.createBoatModelLayers()
@@ -408,8 +437,10 @@ object CobblemonClient {
var renderer: LivingEntityRenderer<Player, PlayerModel<Player>>? =
skinMap?.get(PlayerSkin.Model.WIDE) as LivingEntityRenderer<Player, PlayerModel<Player>>
renderer?.addLayer(PokemonOnShoulderRenderer(renderer))
renderer?.addLayer(PlayerRecallBallRenderer(renderer))
renderer = skinMap[PlayerSkin.Model.SLIM] as LivingEntityRenderer<Player, PlayerModel<Player>>?
renderer?.addLayer(PokemonOnShoulderRenderer(renderer))
renderer?.addLayer(PlayerRecallBallRenderer(renderer))
}
private fun registerMenuScreens() {
@@ -486,7 +517,6 @@ object CobblemonClient {
}
fun endBattle() {
ClientBattleInformationRepository.clear()
battle = null
battleOverlay.lastKnownBattle = null
BattleMusicController.endMusic()
@@ -84,7 +84,6 @@ class ClientBattle(
pendingActionRequests.map { it.response!! }
)
)
ClientBattleInformationRepository.mustChooseBy = null
mustChoose = false
}
}
@@ -1,28 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.battle
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationDTO
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
import java.time.Instant
import java.util.UUID
object ClientBattleInformationRepository {
val actors = mutableMapOf<UUID, MutableList<BattlePokemonDTO>>()
val battles = mutableMapOf<UUID, BattleInformationDTO>()
val actorActualSides = mutableMapOf<UUID, Int>()
var mustChooseBy: Instant? = null
fun clear() {
actors.clear()
battles.clear()
actorActualSides.clear()
mustChooseBy = null
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.client.battle
import net.minecraft.util.FormattedCharSequence
import net.minecraft.network.chat.Component
/**
* A wrapper around the client battle message information. This is essentially an appending list of all
@@ -22,21 +21,6 @@ import net.minecraft.network.chat.Component
class ClientBattleMessageQueue {
var listeners = mutableListOf<(FormattedCharSequence) -> Unit>()
private val messages = mutableListOf<FormattedCharSequence>()
private val components = mutableListOf<Component>()
private val componentListeners = mutableListOf<(Component) -> Unit>()
fun addComponents(messages: Iterable<Component>) {
messages.forEach { message ->
components.add(message)
componentListeners.toList().forEach { it(message) }
}
}
fun subscribeComponents(listener: (Component) -> Unit): () -> Unit {
componentListeners.add(listener)
components.forEach(listener)
return { componentListeners.remove(listener) }
}
fun add(messages: Iterable<FormattedCharSequence>) {
this.messages.addAll(messages)
@@ -47,4 +31,4 @@ class ClientBattleMessageQueue {
this.listeners.add(listener)
messages.forEach(listener)
}
}
}
@@ -1,120 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.battle
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
object MoveEffectivenessCalculator {
fun getMoveEffectiveness(attack: ElementalType, defenderType1: ElementalType, defenderType2: ElementalType?): Effectiveness {
val type1Effectiveness = getEffectiveness(attack, defenderType1)
val type2Effectiveness = if (defenderType2 == null) 1f else getEffectiveness(attack, defenderType2)
val typeEffectiveness = type1Effectiveness * type2Effectiveness
return when {
typeEffectiveness == 0f -> Effectiveness.IMMUNE
typeEffectiveness == 0.25f -> Effectiveness.MOSTLY_INEFFECTIVE
typeEffectiveness == 0.5f -> Effectiveness.NOT_VERY_EFFECTIVE
typeEffectiveness == 1f -> Effectiveness.EFFECTIVE
typeEffectiveness == 2f -> Effectiveness.SUPER_EFFECTIVE
typeEffectiveness == 4f -> Effectiveness.EXTREMELY_EFFECTIVE
else -> Effectiveness.IMMUNE
}
}
fun getEffectiveness(attack: ElementalType, defender: ElementalType): Float {
if (isImmune(attack, defender)) return 0f
if (isSuperEffective(attack, defender)) return 2f
if (isNotVeryEffective(attack, defender)) return 0.5f
return 1f
}
fun isImmune(attack: ElementalType, defender: ElementalType): Boolean {
when (attack) {
ElementalTypes.NORMAL -> return defender == ElementalTypes.GHOST
ElementalTypes.FIRE -> return false
ElementalTypes.WATER -> return false
ElementalTypes.GRASS -> return false
ElementalTypes.ELECTRIC -> return defender == ElementalTypes.GROUND
ElementalTypes.ICE -> return false
ElementalTypes.FIGHTING -> return defender == ElementalTypes.GHOST
ElementalTypes.POISON -> return defender == ElementalTypes.STEEL
ElementalTypes.GROUND -> return defender == ElementalTypes.FLYING
ElementalTypes.FLYING -> return false
ElementalTypes.PSYCHIC -> return defender == ElementalTypes.DARK
ElementalTypes.BUG -> return false
ElementalTypes.ROCK -> return false
ElementalTypes.GHOST -> return defender == ElementalTypes.NORMAL
ElementalTypes.DRAGON -> return defender == ElementalTypes.FAIRY
ElementalTypes.DARK -> return false
ElementalTypes.STEEL -> return false
ElementalTypes.FAIRY -> return false
}
return false
}
fun isSuperEffective(attack: ElementalType, defender: ElementalType): Boolean {
when (attack) {
ElementalTypes.NORMAL -> return false
ElementalTypes.FIRE -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.ICE, ElementalTypes.BUG, ElementalTypes.STEEL)
ElementalTypes.WATER -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.GROUND, ElementalTypes.ROCK)
ElementalTypes.GRASS -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.GROUND, ElementalTypes.ROCK)
ElementalTypes.ELECTRIC -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.FLYING)
ElementalTypes.ICE -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.GROUND, ElementalTypes.FLYING, ElementalTypes.DRAGON)
ElementalTypes.FIGHTING -> return defender in listOf(ElementalTypes.NORMAL, ElementalTypes.ICE, ElementalTypes.ROCK, ElementalTypes.DARK, ElementalTypes.STEEL)
ElementalTypes.POISON -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.FAIRY)
ElementalTypes.GROUND -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.ELECTRIC, ElementalTypes.POISON, ElementalTypes.ROCK, ElementalTypes.STEEL)
ElementalTypes.FLYING -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.FIGHTING, ElementalTypes.BUG)
ElementalTypes.PSYCHIC -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.POISON)
ElementalTypes.BUG -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.PSYCHIC, ElementalTypes.DARK)
ElementalTypes.ROCK -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.ICE, ElementalTypes.FLYING, ElementalTypes.BUG)
ElementalTypes.GHOST -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.GHOST)
ElementalTypes.DRAGON -> return defender in listOf(ElementalTypes.DRAGON)
ElementalTypes.DARK -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.GHOST)
ElementalTypes.STEEL -> return defender in listOf(ElementalTypes.ICE, ElementalTypes.ROCK, ElementalTypes.FAIRY)
ElementalTypes.FAIRY -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.DRAGON, ElementalTypes.DARK)
}
return false
}
fun isNotVeryEffective(attack: ElementalType, defender: ElementalType): Boolean {
when (attack) {
ElementalTypes.NORMAL -> return defender in listOf(ElementalTypes.ROCK, ElementalTypes.STEEL)
ElementalTypes.FIRE -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ROCK, ElementalTypes.DRAGON)
ElementalTypes.WATER -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.GRASS, ElementalTypes.DRAGON)
ElementalTypes.GRASS -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.GRASS, ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.BUG, ElementalTypes.DRAGON, ElementalTypes.STEEL)
ElementalTypes.ELECTRIC -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.ELECTRIC, ElementalTypes.DRAGON)
ElementalTypes.ICE -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ICE, ElementalTypes.STEEL)
ElementalTypes.FIGHTING -> return defender in listOf(ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.PSYCHIC, ElementalTypes.BUG, ElementalTypes.FAIRY)
ElementalTypes.POISON -> return defender in listOf(ElementalTypes.POISON, ElementalTypes.GROUND, ElementalTypes.ROCK, ElementalTypes.GHOST)
ElementalTypes.GROUND -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.BUG)
ElementalTypes.FLYING -> return defender in listOf(ElementalTypes.ELECTRIC, ElementalTypes.ROCK, ElementalTypes.STEEL)
ElementalTypes.PSYCHIC -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.STEEL)
ElementalTypes.BUG -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.FIGHTING, ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.GHOST, ElementalTypes.STEEL, ElementalTypes.FAIRY)
ElementalTypes.ROCK -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.GROUND, ElementalTypes.STEEL)
ElementalTypes.GHOST -> return defender in listOf(ElementalTypes.DARK)
ElementalTypes.DRAGON -> return defender in listOf(ElementalTypes.STEEL)
ElementalTypes.DARK -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.DARK, ElementalTypes.FAIRY)
ElementalTypes.STEEL -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ELECTRIC, ElementalTypes.STEEL)
ElementalTypes.FAIRY -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.POISON, ElementalTypes.STEEL)
}
return false
}
}
enum class Effectiveness {
EXTREMELY_EFFECTIVE,
SUPER_EFFECTIVE,
EFFECTIVE,
NOT_VERY_EFFECTIVE,
MOSTLY_INEFFECTIVE,
IMMUNE
}
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.battle.preview
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
import net.minecraft.network.chat.Component
import java.time.Instant
class ClientBattleTeamPreview(
val selections: Int,
val team: List<BattlePokemonDTO?>,
val opponent: List<BattlePokemonDTO?>,
val mustPickBy: Instant,
val opponentName: Component
) {
val selection = mutableListOf<BattlePokemonDTO>()
fun select(pokemon: BattlePokemonDTO) {
if (pokemon !in team || pokemon in selection || selection.size >= selections) return
selection.add(pokemon)
sendChangePacket()
}
fun unselect(pokemon: BattlePokemonDTO) {
selection.remove(pokemon)
sendChangePacket()
}
private fun sendChangePacket() {
val indexes = selection.map { team.indexOf(it) }
if (indexes.any { it == -1 }) return
CobblemonNetwork.sendToServer(TeamPreviewSelectPokemonPacket(indexes.toSet()))
}
}
@@ -32,6 +32,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer.Companion.ease
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
@@ -320,6 +321,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
ballOffset = 0f
ballRotOffset = 0f
sendOutPosition = null
currentEntity.ownerUUID?.let { RecallArmAnimationTracker.start(it, currentEntity.exposedBall) }
afterOnClient(seconds = BEAM_EXTEND_TIME) {
activeSendoutScale = 1F
@@ -71,7 +71,7 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
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) {
if (stackHeight < height && stackHeight + entry.height > 0) {
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
}
stackHeight += entry.height
@@ -20,12 +20,10 @@ import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGeneralActionS
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection
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.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
@@ -46,16 +44,15 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
val forfeitResource = cobblemonResource("textures/gui/battle/battle_menu_forfeit.png")
}
lateinit var messagePane: BattleMessagePane
private lateinit var messagePane: BattleMessagePane
var opacity = 0F
val actor = CobblemonClient.battle?.side1?.actors?.find { it.uuid == Minecraft.getInstance().player?.uuid }
val specBackButton = BattleBackButton(OPTION_ROOT_X - 3F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val specBackButton = BattleBackButton(12f, Minecraft.getInstance().window.guiScaledHeight - 32f)
var queuedActions = mutableListOf<() -> Unit>()
override fun init() {
super.init()
if (this::messagePane.isInitialized) messagePane.dispose()
messagePane = BattleMessagePane(CobblemonClient.battle!!.messages)
messagePane.opacity = CobblemonClient.battleOverlay.opacityRatio.toFloat().coerceAtLeast(0.3F)
addRenderableWidget(messagePane)
@@ -124,17 +121,16 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
val currentSelection = getCurrentActionSelection()
if (currentSelection == null || currentSelection is BattleGeneralActionSelection) {
if (currentSelection == null || currentSelection is BattleGeneralActionSelection ) {
drawScaledText(
context = context,
text = battleLang("ui.hide_label", PartySendBinding.boundKey().displayName),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = Minecraft.getInstance().window.guiScaledHeight / 5,
y = (Minecraft.getInstance().window.guiScaledHeight / 5),
opacity = 0.75F * opacity,
centered = true
)
}
if (currentSelection is ForfeitConfirmationSelection) {
} else if (currentSelection is ForfeitConfirmationSelection) {
drawScaledText(
context = context,
text = battleLang("ui.forfeit_confirmation", PartySendBinding.boundKey().displayName),
@@ -148,11 +144,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
queuedActions.forEach { it() }
queuedActions.clear()
CobblemonClient.battleOverlay.mouseX = mouseX
CobblemonClient.battleOverlay.mouseY = mouseY
// The benched-team portrait columns + active hover panels are queued during the HUD pass and drawn
// here, last, so they sit above the switch-menu underlay, our own widgets and the chat.
CobblemonClient.battleOverlay.renderDeferredColumns(context)
}
override fun renderBackground(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -160,6 +151,8 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
return if (request.forceSwitch) {
BattleSwitchPokemonSelection(this, request)
} else {
@@ -182,23 +175,11 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
CobblemonClient.battle?.minimised = true
}
override fun removed() {
if (this::messagePane.isInitialized) messagePane.dispose()
super.removed()
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (CobblemonClient.battleOverlay.columnMouseDragged(mouseX, mouseY)) return true
if (this::messagePane.isInitialized && messagePane.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) return true
if (this::messagePane.isInitialized) messagePane.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (this::messagePane.isInitialized) messagePane.mouseReleased(mouseX, mouseY, button)
if (CobblemonClient.battleOverlay.columnMouseReleased()) return true
return super.mouseReleased(mouseX, mouseY, button)
}
override fun charTyped(chr: Char, modifiers: Int): Boolean {
if (chr.toString().equals(PartySendBinding.boundKey().displayName.string, ignoreCase = true) && CobblemonClient.battleOverlay.opacity == BattleOverlay.MAX_OPACITY && PartySendBinding.canAction()) {
return minimizeBattle()
@@ -207,18 +188,14 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
// Grab a benched-team portrait column to drag it (left-click). Consumes the click if one was hit.
if (button == 0 && CobblemonClient.battleOverlay.columnMouseClicked(mouseX, mouseY)) return true
if (button == PartySendBinding.boundKey().value && CobblemonClient.battleOverlay.opacity == BattleOverlay.MAX_OPACITY && PartySendBinding.canAction()) {
return minimizeBattle()
}
val battle = CobblemonClient.battle
if (battle?.spectating == true) {
if (specBackButton.isHovered(mouseX, mouseY)) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
}
if (battle?.spectating == true && specBackButton.isHovered(mouseX, mouseY)) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
}
return super.mouseClicked(mouseX, mouseY, button)
}
@@ -229,4 +206,4 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
PartySendBinding.actioned()
return true
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,682 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.battle
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.TextColor
import net.minecraft.network.chat.contents.TranslatableContents
import java.text.DecimalFormat
import kotlin.math.ceil
import kotlin.math.floor
/**
* The "Info" style expanded stat panel for a single battle Pokémon (form, buffs/debuffs, ability,
* moves, speed/speed-tier, types, held item). It renders to the *side* of a tile, with a connector
* pointing back at it, and expands inwards (towards the screen centre) on both sides.
*
* Shared by the team Info screen (the team Info screen)
* and the on-field benched-team portrait column ([BattleOverlay.drawPartyColumn]) so the two stay identical.
*
* Knowledge gating is inherited from [dto]: the team owner gets full data while opponents only carry
* what has been revealed, so unrevealed ability/moves show "?" and speed falls back to a tier range.
*/
object BattlePokemonInfoPanel {
private val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
// Variant for non-active Pokémon: the left (Buffs/Debuffs) column is cleared for a Current HP gauge.
private val expandedPokemonInfoInactive = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center_inactive.png")
// Same battle health-bar texture the active Pokémon's tile uses, for the Current HP gauge.
private val healthBar = cobblemonResource("textures/gui/battle/health_bar.png")
private val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
private val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
private val multiplierFormat = DecimalFormat("0.##")
// Boost value slots span x=46..65; leave one pixel of padding at the battle text scale.
internal const val BOOST_VALUE_CENTER_X = 55.5
internal const val BOOST_VALUE_MAX_WIDTH = 36 // 18 GUI pixels at BattleOverlay.SCALE.
fun renderUnknownTile(context: GuiGraphics, x: Float, y: Float, reversed: Boolean, scale: Float = 1F, opacity: Number = 1F) {
val matrices = context.pose()
val insetX = if (reversed) 8 else 5
matrices.pushPose()
matrices.translate(x.toDouble(), y.toDouble(), 0.0)
matrices.scale(scale, scale, scale)
blitk(
matrixStack = matrices,
texture = BattleOverlay.battleInfoUnderlay,
x = insetX,
y = 4,
width = 28,
height = 28,
alpha = opacity
)
// Draw the complete 8x10 asset at twice its size, centred in the portrait window.
blitk(
matrixStack = matrices,
texture = BattleOverlay.questionMarkIcon,
x = (insetX + 6) / 2F,
y = 4,
width = 8,
height = 10,
scale = 2F,
alpha = opacity
)
blitk(
matrixStack = matrices,
texture = if (reversed) BattleOverlay.partyColumnTileReversed else BattleOverlay.partyColumnTile,
x = 0,
y = 0,
width = 41,
height = 39,
alpha = opacity
)
matrices.popPose()
}
fun render(
context: GuiGraphics,
tileX: Float,
tileY: Float,
tileWidth: Float,
reversed: Boolean,
pokemon: ClientBattlePokemon,
dto: BattlePokemonDTO,
showGap: Boolean = true,
showBuffs: Boolean = true
) {
context.pose().pushPose()
context.pose().translate(0.0, 0.0, 300.0)
// Panel sits just beside the tile (inwards) with the connector bridging the gap.
val startX = if (reversed) tileX - 136 else tileX + tileWidth - 1
val startY = tileY
val species = pokemon.species
val form = species.getForm(pokemon.state.currentAspects)
// The connector is only shown for the Info screen tiles, not the on-field draggable column.
if (showGap) {
val gap = if (reversed) hoverGapReversed else hoverGap
val xOffset = if (reversed) 136 else -7
blitk(
matrixStack = context.pose(),
texture = gap,
x = startX + xOffset,
y = startY,
height = 36,
width = 8,
textureHeight = 36,
textureWidth = 8,
)
}
blitk(
matrixStack = context.pose(),
texture = if (showBuffs) expandedPokemonInfo else expandedPokemonInfoInactive,
x = startX,
y = startY,
height = 112,
width = 137,
textureHeight = 112,
textureWidth = 137,
)
drawScaledText(
context = context,
text = "deltaclient.ui.battle.label.pokemon_form".asTranslated().bold(),
x = startX + 36,
y = startY + 6.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = getFormText(species, form),
x = startX + 36,
y = startY + 13.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
if (showBuffs) {
drawScaledText(
context = context,
text = "deltaclient.ui.battle.label.buffs".asTranslated().bold(),
x = startX + 36,
y = startY + 23.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = "cobblemon.stat.attack.name".asTranslated().statColored(Stats.ATTACK),
x = startX + 10,
y = startY + 31.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = false
)
dto.buffs[Stats.ATTACK]?.let {
drawScaledText(
context = context,
text = getBoostText(Stats.ATTACK, it),
x = startX + BOOST_VALUE_CENTER_X,
y = startY + 31.5,
scale = BattleOverlay.SCALE,
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.defence.name".asTranslated().statColored(Stats.DEFENCE),
x = startX + 10,
y = startY + 39.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = false
)
dto.buffs[Stats.DEFENCE]?.let {
drawScaledText(
context = context,
text = getBoostText(Stats.DEFENCE, it),
x = startX + BOOST_VALUE_CENTER_X,
y = startY + 39.5,
scale = BattleOverlay.SCALE,
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "deltaclient.ui.battle.stats.special_attack".asTranslated().statColored(Stats.SPECIAL_ATTACK),
x = startX + 10,
y = startY + 47.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = false
)
dto.buffs[Stats.SPECIAL_ATTACK]?.let {
drawScaledText(
context = context,
text = getBoostText(Stats.SPECIAL_ATTACK, it),
x = startX + BOOST_VALUE_CENTER_X,
y = startY + 47.5,
scale = BattleOverlay.SCALE,
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "deltaclient.ui.battle.stats.special_defence".asTranslated().statColored(Stats.SPECIAL_DEFENCE),
x = startX + 10,
y = startY + 55.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = false
)
dto.buffs[Stats.SPECIAL_DEFENCE]?.let {
drawScaledText(
context = context,
text = getBoostText(Stats.SPECIAL_DEFENCE, it),
x = startX + BOOST_VALUE_CENTER_X,
y = startY + 55.5,
scale = BattleOverlay.SCALE,
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.speed.name".asTranslated().statColored(Stats.SPEED),
x = startX + 10,
y = startY + 63.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = false
)
dto.buffs[Stats.SPEED]?.let {
drawScaledText(
context = context,
text = getBoostText(Stats.SPEED, it),
x = startX + BOOST_VALUE_CENTER_X,
y = startY + 63.5,
scale = BattleOverlay.SCALE,
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.accuracy.name".asTranslated(),
x = startX + 10,
y = startY + 71.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = false
)
dto.buffs[Stats.ACCURACY]?.let {
drawScaledText(
context = context,
text = getBoostText(Stats.ACCURACY, it),
x = startX + BOOST_VALUE_CENTER_X,
y = startY + 71.5,
scale = BattleOverlay.SCALE,
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.evasion.name".asTranslated(),
x = startX + 10,
y = startY + 79.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = false
)
dto.buffs[Stats.EVASION]?.let {
drawScaledText(
context = context,
text = getBoostText(Stats.EVASION, it),
x = startX + BOOST_VALUE_CENTER_X,
y = startY + 79.5,
scale = BattleOverlay.SCALE,
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
} else {
// Non-active Pokémon have no stat stages; the inactive panel asset already clears the
// Buffs/Debuffs column, so we just draw a "Current HP" gauge on that clean area.
drawScaledText(
context = context,
text = "deltaclient.ui.battle.label.current_hp".asTranslated().bold(),
x = startX + 36,
y = startY + 23.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
// Owner sees flat HP (hp/maxHp); opponents carry a 0..1 ratio.
val hpRatio = (if (pokemon.isHpFlat) pokemon.hpValue / pokemon.maxHp.toInt() else pokemon.hpValue)
.coerceIn(0f, 1f)
val (hpRed, hpGreen) = getDepletableRedGreen(hpRatio)
// Same battle health-bar texture + tint the active Pokémon uses, sized to fill the asset's
// bar slot beneath the "Current HP" label (slot ≈ x8..64, y30..36).
val barX = startX + 8
val barW = hpRatio * 57f
blitk(
matrixStack = context.pose(),
texture = healthBar,
x = barX,
y = startY + 30,
width = barW,
height = 6,
textureWidth = 99,
textureHeight = 6,
red = hpRed * 0.8f,
green = hpGreen * 0.8f,
blue = 0.27f
)
// The texture's right end is angled, so a left-clipped fill has a border on every side except
// the right. Re-draw the texture's square left-edge column (texel 0) at the fill's right edge
// to close the border into a full rectangle.
if (barW >= 1f) {
blitk(
matrixStack = context.pose(),
texture = healthBar,
x = barX + barW - 1f,
y = startY + 30,
width = 1f,
height = 6,
textureWidth = 99,
textureHeight = 6,
red = hpRed * 0.8f,
green = hpGreen * 0.8f,
blue = 0.27f
)
}
// Percentage centred inside the bar — and exact values for your own Pokémon — exactly like
// the active Pokémon's health bar does in battle.
val hpText = if (pokemon.isHpFlat) {
"${pokemon.hpValue.toInt()}/${pokemon.maxHp.toInt()} (${ceil(hpRatio * 100).toInt()}%)"
} else {
"${ceil(hpRatio * 100).toInt()}%"
}
drawScaledText(
context = context,
text = hpText.text(),
x = startX + 36,
y = startY + 31.0,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "deltaclient.ui.battle.label.ability".asTranslated().bold(),
x = startX + 100,
y = startY + 6.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
val ability = dto.ability?.let { Abilities.get(it) }
val abilityName = ability?.displayName?.asTranslated() ?: "?".text()
drawScaledText(
context = context,
text = abilityName,
x = startX + 100,
y = startY + 13.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = "deltaclient.ui.battle.label.moves".asTranslated().bold(),
x = startX + 100,
y = startY + 23.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
if (dto.moves.size != 4) {
for (i in 0 until 4) {
drawScaledText(
context = context,
text = "?".text(),
x = startX + 100,
y = startY + 31.5 + i * 8,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
}
}
else {
val questionMarkText = "?".text()
dto.moves.forEachIndexed { index, moveDTO ->
val move = moveDTO?.move ?: questionMarkText
val moveColor = moveDTO?.move?.let { typeColorFor(it) }
drawScaledText(
context = context,
text = move.string.text().let { t -> if (moveColor != null) t.colored(moveColor) else t },
x = if (move == questionMarkText) startX + 100 else startX + 74,
y = startY + 31.5 + index * 8,
scale = BattleOverlay.SCALE,
shadow = true,
centered = move == questionMarkText
)
if (moveDTO != null) {
drawScaledTextJustifiedRight(
context = context,
text = moveDTO.timesUsed.toString().text(),
x = startX + 127.5,
y = startY + 31.5 + index * 8,
scale = BattleOverlay.SCALE,
shadow = true,
)
}
}
}
val dtoSpeed = dto.speed
if (dtoSpeed != null) {
val speedBoost = dto.buffs[Stats.SPEED] ?: 1.0
val speedAfterBoost = (dtoSpeed * speedBoost).toInt()
drawScaledText(
context = context,
text = "cobblemon.stat.speed.name".asTranslated(),
x = startX + 100,
y = startY + 65.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = speedAfterBoost.toString().text(),
x = startX + 100,
y = startY + 72.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
}
else {
drawScaledText(
context = context,
text = "deltaclient.ui.battle.label.speed_tier".asTranslated(),
x = startX + 100,
y = startY + 65.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
val speedBoost = dto.buffs[Stats.SPEED] ?: 1.0
val speedTier = getSpeedRange(form, pokemon.level, speedBoost)
drawScaledText(
context = context,
text = speedTier,
x = startX + 100,
y = startY + 72.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "deltaclient.ui.battle.label.held_item".asTranslated(),
x = startX + 40,
y = startY + 99.5,
scale = BattleOverlay.SCALE,
shadow = true,
centered = true
)
TypeIcon(
x = startX + 100.5,
y = startY + 88,
type = form.primaryType,
secondaryType = form.secondaryType,
small = false,
centeredX = true
).render(context)
if (form.secondaryType != null) {
blitk(
matrixStack = context.pose(),
texture = cobblemonResource("textures/gui/battle/type_spacer_double.png"),
x = (startX + 69.0) / 0.45,
y = (startY + 91.5) / 0.45,
height = 24,
width = 140,
textureHeight = 24,
textureWidth = 140,
scale = 0.45f
)
}
else {
blitk(
matrixStack = context.pose(),
texture = cobblemonResource("textures/gui/summary/type_spacer.png"),
x = (startX + 70.5) / 0.45,
y = (startY + 91.5) / 0.45,
height = 24,
width = 132,
textureHeight = 24,
textureWidth = 132,
scale = 0.45f
)
}
val heldItem = dto.heldItem
if (heldItem != null && !heldItem.isEmpty()) {
context.pose().pushPose()
context.pose().scale(0.95F, 0.95F, 0.95F)
val itemX = (startX + 4) / 0.95
val itemY = (startY + 91) / 0.95
context.renderItem(heldItem, itemX.toInt(), itemY.toInt())
context.pose().popPose()
}
else {
blitk(
matrixStack = context.pose(),
texture = BattleOverlay.questionMarkIcon,
x = startX + 6,
y = startY + 92.5,
height = 11,
width = 10,
)
}
context.pose().popPose()
}
private fun getSpeedRange(form: FormData, level: Int, speedBoost: Double): MutableComponent {
val base = form.baseStats[Stats.SPEED] ?: 0
val maxIV = 31
val maxEV = 252
val minNatureMod = 0.9
val maxNatureMod = 1.1
val minSpeed = getStat(base, level, 0, 0, minNatureMod) * speedBoost
val maxSpeed = getStat(base, level, maxIV, maxEV, maxNatureMod) * speedBoost
return "deltaclient.ui.battle.speed_tier".asTranslated(minSpeed.toInt(), maxSpeed.toInt())
}
private fun getStat(base: Int, level: Int, iv: Int, ev: Int, natureMod: Double): Int {
return floor((floor(((2.0 * base + iv + floor(ev / 4.0)) * level) / 100) + 5) * natureMod).toInt()
}
internal fun getBoostText(stat: Stats, multiplier: Double): MutableComponent {
val stage = stageFromMultiplier(stat, multiplier)
val sign = if (stage > 0) "+" else ""
val str = "$sign$stage (${multiplierFormat.format(multiplier)}x)"
return when {
multiplier > 1.0 -> str.green()
multiplier < 1.0 -> str.red()
else -> str.text()
}
}
// Converts a stat-boost multiplier back to its stage. Accuracy/Evasion use a /3 base, others /2.
private fun stageFromMultiplier(stat: Stats, multiplier: Double): Int {
val base = if (stat == Stats.ACCURACY || stat == Stats.EVASION) 3.0 else 2.0
return if (multiplier >= 1.0) Math.round(multiplier * base - base).toInt()
else Math.round(base - base / multiplier).toInt()
}
// The type colour for a move from its "cobblemon.move.<name>" display name, or null if unknown.
internal fun typeColorFor(move: Component): Int? {
val key = (move.contents as? TranslatableContents)?.key ?: return null
if (!key.startsWith("cobblemon.move.")) return null
val type = Moves.getByName(key.removePrefix("cobblemon.move."))?.elementalType?.name ?: return null
return typeColor(type)
}
private fun typeColor(type: String): Int = when (type.lowercase()) {
"bug" -> 0xA8B820
"dark" -> 0x705848
"dragon" -> 0x7038F8
"electric" -> 0xF8D030
"fairy" -> 0xEE99AC
"fighting" -> 0xC03028
"fire" -> 0xF08030
"flying" -> 0xA890F0
"ghost" -> 0x705898
"grass" -> 0x78C850
"ground" -> 0xE0C068
"ice" -> 0x98D8D8
"normal" -> 0xA8A878
"poison" -> 0xA040A0
"psychic" -> 0xF85888
"rock" -> 0xB8A038
"steel" -> 0xB8B8D0
"water" -> 0x6890F0
else -> 0xFFFFFF
}
private fun getFormText(species: Species, form: FormData): MutableComponent {
val speciesName = species.name
val formName = form.name
return if (formName == "Normal") speciesName.text() else "$speciesName-$formName".text()
}
}
internal fun MutableComponent.colored(rgb: Int): MutableComponent = this.withStyle { it.withColor(TextColor.fromRgb(rgb)) }
// Buffs/Debuffs stat names are all white. (Per-stat colours can be re-enabled by returning them here.)
internal fun statColor(stat: Stats): Int? = null
internal fun MutableComponent.statColored(stat: Stats): MutableComponent {
val rgb = statColor(stat) ?: return this
return this.colored(rgb)
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.battle.preview
import com.cobblemon.mod.common.client.battle.preview.ClientBattleTeamPreview
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
class TeamPreviewGUI(val preview: ClientBattleTeamPreview) : Screen(battleLang("gui.team_preview")), CobblemonRenderable {
private lateinit var widget: TeamPreviewWidget
override fun init() {
super.init()
widget = addRenderableWidget(TeamPreviewWidget(preview))
}
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
super.render(guiGraphics, mouseX, mouseY, partialTick)
}
override fun shouldCloseOnEsc() = false
override fun isPauseScreen() = false
}
@@ -1,973 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.battle.preview
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.gui.ParentWidget
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.renderSprite
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
import com.cobblemon.mod.common.client.battle.preview.ClientBattleTeamPreview
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.PORTRAIT_DIAMETER
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.SCALE
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.questionMarkIcon
import com.cobblemon.mod.common.client.render.SpriteType
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
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.toHex
import com.mojang.blaze3d.platform.InputConstants
import com.mojang.blaze3d.platform.Lighting
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.narration.NarratableEntry
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import org.joml.Quaternionf
import org.joml.Vector3f
import java.math.RoundingMode
import java.text.DecimalFormat
import java.time.Duration
import java.time.Instant
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.max
class TeamPreviewWidget(val preview: ClientBattleTeamPreview) : ParentWidget(
pX = 0,
pY = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
pWidth = Minecraft.getInstance().window.guiScaledWidth,
pHeight = Minecraft.getInstance().window.guiScaledHeight,
"cobblemon.battle.ui.team_info".asTranslated()
) {
companion object {
const val SLOT_HORIZONTAL_SPACING = 4F
const val SLOT_VERTICAL_SPACING = 2F
const val BACKGROUND_HEIGHT = 148
val underlayTexture = cobblemonResource("textures/gui/battle/selection_underlay.png")
val title = cobblemonResource("textures/gui/battle/team_preview_title.png")
val checkmark = cobblemonResource("textures/gui/battle/checkmark.png")
var visible = false
}
val teamTiles = mutableListOf<PokemonTile>()
val opponentTiles = mutableListOf<PokemonTile>()
init {
addTiles()
}
fun addTiles() {
preview.team.forEachIndexed { index, pokemon ->
if (pokemon == null) return@forEachIndexed
val (slotX, slotY) = getSlotPosition(index, 1)
teamTiles.add(PokemonTile(slotX, slotY, pokemon, this, false))
}
preview.opponent.forEachIndexed { index, pokemon ->
if (pokemon == null) return@forEachIndexed
val (slotX, slotY) = getSlotPosition(index, 2)
opponentTiles.add(PokemonTile(slotX, slotY, pokemon, this, true))
}
}
fun getSlotPosition(index: Int, side: Int): Pair<Float, Float> {
val startX = if (side == 1) 10 else width - 10 - (41 + SLOT_HORIZONTAL_SPACING) * 2
val startY = y + 15
val row = index / 2
val column = index % 2
val slotX = startX.toFloat() + column * (SLOT_HORIZONTAL_SPACING + 41)
val slotY = startY.toFloat() + row * (SLOT_VERTICAL_SPACING + 39)
return Pair(slotX, slotY)
}
private fun getHoveredTile(team: Boolean, mouseX: Int, mouseY: Int): Int {
val tiles = if (team) teamTiles else opponentTiles
return tiles.indexOfFirst { it.isHovered(mouseX.toDouble(), mouseY.toDouble()) }
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrixStack = context.pose()
val mc = Minecraft.getInstance()
blitk(
matrixStack = matrixStack,
texture = underlayTexture,
x = x,
y = y,
width = width,
height = BACKGROUND_HEIGHT
)
blitk(
matrixStack = matrixStack,
texture = title,
x = (mc.window.guiScaledWidth / 2) - (103 / 2),
y = 4,
width = 103,
height = 22
)
drawScaledText(
context = context,
text = "cobblemon.battle.ui.team_preview.title".asTranslated().bold(),
x = (mc.window.guiScaledWidth / 2),
y = 6,
scale = 0.75f,
shadow = true,
centered = true
)
val totalSecondsRemaining = Duration.between(Instant.now(), preview.mustPickBy).seconds
val minutesRemaining = max(0, totalSecondsRemaining / 60)
val secondsRemaining = max(0, totalSecondsRemaining % 60)
val secondsRemainingStr = String.format("%02d", secondsRemaining)
val timer = "$minutesRemaining:$secondsRemainingStr"
val text = when {
totalSecondsRemaining <= 15 -> timer.text().red().bold()
totalSecondsRemaining <= 30 -> timer.text().yellow().bold()
else -> timer.text().bold()
}
drawScaledText(
context = context,
text = text,
x = (mc.window.guiScaledWidth / 2) + 2,
y = 15,
scale = 0.75f,
shadow = true,
centered = true
)
val ellipsis = ".".repeat(3 - (secondsRemaining % 3).toInt())
if (preview.selection.size == preview.selections) {
blitk(
matrixStack = matrixStack,
texture = checkmark,
x = (mc.window.guiScaledWidth / 2) - 39,
y = 24,
width = 8,
height = 6
)
val offset = when (ellipsis.length) {
1 -> 0.5
2 -> 1.0
else -> 2.0
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.team_preview.waiting".asTranslated(ellipsis).bold(),
x = (mc.window.guiScaledWidth / 2) + 2.0 + offset,
y = 26,
scale = 0.5f,
shadow = true,
centered = true
)
}
blitk(
matrixStack = matrixStack,
texture = cobblemonResource("textures/gui/battle/name_plate.png"),
x = 10,
y = y - 8,
width = 128,
height = 8
)
drawScaledText(
context = context,
text = mc.player!!.name.string.text(),
y = y - 8 + 2,
x = x + 14 + 10,
scale = SCALE,
shadow = true
)
blitk(
matrixStack = matrixStack,
texture = cobblemonResource("textures/gui/battle/nameplate_reversed.png"),
x = mc.window.guiScaledWidth - 14 - 128,
y = y - 8,
width = 128,
height = 8
)
drawScaledTextJustifiedRight(
context = context,
text = preview.opponentName.string.text(),
y = y - 8 + 2,
x = mc.window.guiScaledWidth - 14 - 12,
scale = SCALE,
shadow = true
)
teamTiles.forEachIndexed { index, tile ->
val hoveredTile = getHoveredTile(true, mouseX, mouseY)
if (shouldHide(hoveredTile, index, false)) return@forEachIndexed
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
}
opponentTiles.forEachIndexed { index, tile ->
val hoveredTile = getHoveredTile(false, mouseX, mouseY)
if (shouldHide(hoveredTile, index, true)) return@forEachIndexed
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
}
}
private fun shouldHide(hoveredTile: Int, currentTile: Int, reversed: Boolean): Boolean {
if (hoveredTile != -1) {
val hoveredRow = hoveredTile / 2
val hoveredColumn = hoveredTile % 2
val currentRow = currentTile / 2
val currentColumn = currentTile % 2
if (!reversed && hoveredColumn < currentColumn && hoveredRow <= currentRow) return true
if (reversed && hoveredColumn > currentColumn && hoveredRow <= currentRow) return true
}
return false
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
val tile = teamTiles.firstOrNull { it.isHovered(pMouseX, pMouseY) }
if (tile != null && tile.pokemonDto in preview.team) {
if (pButton == InputConstants.MOUSE_BUTTON_LEFT) {
if (tile.pokemonDto !in preview.selection && preview.selection.size < preview.selections) {
preview.select(tile.pokemonDto)
}
}
else if (pButton == InputConstants.MOUSE_BUTTON_RIGHT) {
if (tile.pokemonDto in preview.selection) {
preview.unselect(tile.pokemonDto)
}
}
}
playDownSound(Minecraft.getInstance().soundManager)
return super.mouseClicked(pMouseX, pMouseY, pButton)
}
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {
}
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
override fun narrationPriority() = NarratableEntry.NarrationPriority.HOVERED
class PokemonTile(
val x: Float,
val y: Float,
val pokemonDto: BattlePokemonDTO,
val widget: TeamPreviewWidget,
val reversed: Boolean
) {
companion object {
const val SELECT_WIDTH = 41
const val SELECT_HEIGHT = 39
const val SCALE = 0.5F
val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
val pokemonTile = cobblemonResource("textures/gui/battle/pokemon_tile.png")
val pokemonTileReversed = cobblemonResource("textures/gui/battle/pokemon_tile_reversed.png")
val pokemonTileDisabled = cobblemonResource("textures/gui/battle/pokemon_tile_disabled.png")
val pokemonTileDisabledReversed = cobblemonResource("textures/gui/battle/pokemon_tile_disabled_reversed.png")
val pokemonTileSelected = cobblemonResource("textures/gui/battle/pokemon_tile_selected.png")
val pokemonTileSelectedReversed = cobblemonResource("textures/gui/battle/pokemon_tile_selected_reversed.png")
val hoverGapSelected = cobblemonResource("textures/gui/battle/hover_gap_selected.png")
val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
private val decimalFormat = DecimalFormat("0.00").also {
it.roundingMode = RoundingMode.CEILING
}
}
val state = FloatingState()
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) {
val pokemon = pokemonDto.activeBattlePokemonDTO ?: return
val battlePokemon = ClientBattlePokemon(
uuid = pokemon.uuid,
properties = pokemon.properties,
aspects = pokemon.aspects,
displayName = pokemon.displayName,
hpValue = pokemon.hpValue,
maxHp = pokemon.maxHp,
isHpFlat = pokemon.isFlatHp,
status = pokemon.status,
statChanges = pokemon.statChanges,
)
state.currentAspects = pokemon.aspects
val matrixStack = context.pose()
val isFainted = pokemonDto.fainted
val isSelected = pokemonDto in widget.preview.selection
blitk(
matrixStack = matrixStack,
texture = cobblemonResource("textures/gui/battle/battle_info_underlay.png"),
x = x + 5 + (if (reversed) 3 else 0),
y = y + 4,
width = 28,
height = 28
)
val speciesToDisplay = battlePokemon.species
val stateToDisplay = battlePokemon.state
context.enableScissor(
(x + 5 + (if (reversed) 3 else 0)).toInt(),
(y + 5).toInt(),
(x + 5 + 28 + (if (reversed) 3 else 0)).toInt(),
(y + 5 + 28).toInt(),
)
matrixStack.pushPose()
matrixStack.translate(
x + (if (reversed) 3 else 0) + 5 + 28 / 2.0,
y.toDouble() + 5 - 5.0,
0.0
)
drawCustomPosablePortrait(
identifier = speciesToDisplay.resourceIdentifier,
matrixStack = matrixStack,
scale = 18f,
contextScale = speciesToDisplay.getForm(stateToDisplay.currentAspects).baseScale,
reversed = reversed,
doQuirks = false,
state = stateToDisplay,
partialTicks = if (Cobblemon.config.animateBattleTiles) deltaTicks else 0F
)
matrixStack.popPose()
context.disableScissor()
val tile = if (reversed) pokemonTileReversed else pokemonTile
val disabled = if (reversed) pokemonTileDisabledReversed else pokemonTileDisabled
val selected = if (reversed) pokemonTileSelectedReversed else pokemonTileSelected
if (isFainted) {
blitk(
matrixStack = matrixStack,
texture = disabled,
x = x,
y = y,
width = 41,
height = 39
)
}
else if (isSelected) {
blitk(
matrixStack = matrixStack,
texture = selected,
x = x,
y = y,
width = 41,
height = 39
)
val selectionIndex = widget.preview.selection.indexOf(pokemonDto)
if (selectionIndex != -1) {
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = "${selectionIndex + 1}".text().bold(),
x = x + 34,
y = y + 29,
scale = 1.25f,
opacity = 1f,
shadow = true
)
}
}
else {
blitk(
matrixStack = matrixStack,
texture = tile,
x = x,
y = y,
width = 41,
height = 39
)
}
if (isHovered(mouseX, mouseY)) {
renderExpandedInfo(context, isSelected, reversed, battlePokemon, pokemonDto)
}
}
private fun renderExpandedInfo(context: GuiGraphics, selected: Boolean, reversed: Boolean, pokemon: ClientBattlePokemon, dto: BattlePokemonDTO) {
context.pose().pushPose()
context.pose().translate(0.0, 0.0, 300.0)
val startX = if (reversed) x - 136 else x + 40
val startY = y
val species = pokemon.species
val form = species.getForm(pokemon.state.currentAspects)
val expandedInfoTexture = expandedPokemonInfo
if (selected) {
blitk(
matrixStack = context.pose(),
texture = hoverGapSelected,
x = startX - 3,
y = startY,
height = 30,
width = 4,
textureHeight = 30,
textureWidth = 4,
)
}
else {
val gap = if (reversed) hoverGapReversed else hoverGap
val xOffset = if (reversed) 136 else -7
blitk(
matrixStack = context.pose(),
texture = gap,
x = startX + xOffset,
y = startY,
height = 36,
width = 8,
textureHeight = 36,
textureWidth = 8,
)
}
blitk(
matrixStack = context.pose(),
texture = expandedInfoTexture,
x = startX,
y = startY,
height = 112,
width = 137,
textureHeight = 112,
textureWidth = 137,
)
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.pokemon_form".asTranslated().bold(),
x = startX + 36,
y = startY + 6.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = getFormText(species, form),
x = startX + 36,
y = startY + 13.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.buffs".asTranslated().bold(),
x = startX + 36,
y = startY + 23.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = "cobblemon.stat.attack.name".asTranslated(),
x = startX + 10,
y = startY + 31.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.ATTACK)?.let {
drawScaledText(
context = context,
text = getMultiplierText(it),
x = startX + 50,
y = startY + 31.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.defence.name".asTranslated(),
x = startX + 10,
y = startY + 39.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.DEFENCE)?.let {
drawScaledText(
context = context,
text = getMultiplierText(it),
x = startX + 50,
y = startY + 39.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.stats.special_attack".asTranslated(),
x = startX + 10,
y = startY + 47.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.SPECIAL_ATTACK)?.let {
drawScaledText(
context = context,
text = getMultiplierText(it),
x = startX + 50,
y = startY + 47.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.stats.special_defence".asTranslated(),
x = startX + 10,
y = startY + 55.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.SPECIAL_DEFENCE)?.let {
drawScaledText(
context = context,
text = getMultiplierText(it),
x = startX + 50,
y = startY + 55.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.speed.name".asTranslated(),
x = startX + 10,
y = startY + 63.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.SPEED)?.let {
drawScaledText(
context = context,
text = getMultiplierText(it),
x = startX + 50,
y = startY + 63.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.accuracy.name".asTranslated(),
x = startX + 10,
y = startY + 71.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.ACCURACY)?.let {
drawScaledText(
context = context,
text = getMultiplierText(it),
x = startX + 50,
y = startY + 71.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.evasion.name".asTranslated(),
x = startX + 10,
y = startY + 79.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto?.buffs?.get(Stats.EVASION)?.let {
drawScaledText(
context = context,
text = getMultiplierText(it),
x = startX + 50,
y = startY + 79.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.ability".asTranslated().bold(),
x = startX + 100,
y = startY + 6.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
val ability = dto.ability?.let { Abilities.get(it) }
val abilityName = ability?.displayName?.asTranslated() ?: "?".text()
drawScaledText(
context = context,
text = abilityName,
x = startX + 100,
y = startY + 13.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.moves".asTranslated().bold(),
x = startX + 100,
y = startY + 23.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
if (dto.moves.size != 4) {
for (i in 0 until 4) {
drawScaledText(
context = context,
text = "?".text(),
x = startX + 100,
y = startY + 31.5 + i * 8,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
}
}
else {
val questionMarkText = "?".text()
dto.moves.forEachIndexed { index, moveDTO ->
val move = moveDTO?.move ?: questionMarkText
drawScaledText(
context = context,
text = move.string.text(),
x = if (move == questionMarkText) startX + 100 else startX + 74,
y = startY + 31.5 + index * 8,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = move == questionMarkText
)
if (moveDTO != null) {
drawScaledTextJustifiedRight(
context = context,
text = moveDTO.timesUsed.toString().text(),
x = startX + 127.5,
y = startY + 31.5 + index * 8,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
)
}
}
}
if (dto.speed != null) {
val speed = dto.speed
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
val speedAfterBoost = (speed * speedBoost).toInt()
drawScaledText(
context = context,
text = "cobblemon.stat.speed.name".asTranslated(),
x = startX + 100,
y = startY + 65.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = speedAfterBoost.toString().text(),
x = startX + 100,
y = startY + 72.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
}
else {
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.speed_tier".asTranslated(),
x = startX + 100,
y = startY + 65.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
val speedTier = getSpeedRange(form, pokemon.level, speedBoost)
drawScaledText(
context = context,
text = speedTier,
x = startX + 100,
y = startY + 72.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.held_item".asTranslated(),
x = startX + 40,
y = startY + 99.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
TypeIcon(
x = startX + 100.5,
y = startY + 88,
type = form.primaryType,
secondaryType = form.secondaryType,
small = false,
centeredX = true
).render(context)
if (form.secondaryType != null) {
blitk(
matrixStack = context.pose(),
texture = cobblemonResource("textures/gui/battle/type_spacer_double.png"),
x = (startX + 69.0) / 0.45,
y = (startY + 91.5) / 0.45,
height = 24,
width = 140,
textureHeight = 24,
textureWidth = 140,
scale = 0.45f
)
}
else {
blitk(
matrixStack = context.pose(),
texture = cobblemonResource("textures/gui/summary/type_spacer.png"),
x = (startX + 70.5) / 0.45,
y = (startY + 91.5) / 0.45,
height = 24,
width = 132,
textureHeight = 24,
textureWidth = 132,
scale = 0.45f
)
}
val heldItem = dto.heldItem
if (heldItem != null && !heldItem.isEmpty()) {
context.pose().pushPose()
context.pose().scale(0.95F, 0.95F, 0.95F)
val itemX = (startX + 4) / 0.95
val itemY = (startY + 91) / 0.95
context.renderItem(heldItem, itemX.toInt(), itemY.toInt())
context.pose().popPose()
}
else {
blitk(
matrixStack = context.pose(),
texture = questionMarkIcon,
x = startX + 6,
y = startY + 92.5,
height = 11,
width = 10,
)
}
context.pose().popPose()
}
@JvmOverloads
fun drawCustomPosablePortrait(
identifier: ResourceLocation,
matrixStack: PoseStack,
scale: Float = 13F,
contextScale: Float = 1F,
reversed: Boolean = false,
state: PosableState,
partialTicks: Float,
limbSwing: Float = 0F,
limbSwingAmount: Float = 0F,
ageInTicks: Float = 0F,
headYaw: Float = 0F,
headPitch: Float = 0F,
r: Float = 1F,
g: Float = 1F,
b: Float = 1F,
a: Float = 1F,
doQuirks: Boolean = true
) {
RenderSystem.applyModelViewMatrix()
matrixStack.pushPose()
matrixStack.translate(0.0, PORTRAIT_DIAMETER.toDouble() + 2.0, 0.0)
matrixStack.scale(scale, scale, -scale)
matrixStack.translate(0.0, -PORTRAIT_DIAMETER / 18.0, 0.0)
val sprite = VaryingModelRepository.getSprite(identifier, state, SpriteType.PORTRAIT);
if (sprite == null) {
val model = VaryingModelRepository.getPoser(identifier, state)
state.currentModel = model
val texture = VaryingModelRepository.getTexture(identifier, state)
val context = RenderContext()
model.context = context
VaryingModelRepository.getTextureNoSubstitute(identifier, state).let { context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, contextScale)
context.put(RenderContext.SPECIES, identifier)
context.put(RenderContext.ASPECTS, state.currentAspects)
context.put(RenderContext.POSABLE_STATE, state)
context.put(RenderContext.DO_QUIRKS, doQuirks)
val renderType = RenderType.entityCutout(texture)
val quaternion1 = Axis.YP.rotationDegrees(-32F * if (reversed) -1F else 1F)
val quaternion2 = Axis.XP.rotationDegrees(5F)
val originalPose = state.currentPose
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
state.updatePartialTicks(partialTicks)
model.applyAnimations(null, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
originalPose?.let { state.setPose(it) }
matrixStack.translate(
model.portraitTranslation.x * if (reversed) -1F else 1F,
model.portraitTranslation.y + 1.5 * model.portraitScale,
model.portraitTranslation.z - 4
)
matrixStack.scale(model.portraitScale, model.portraitScale, 1 / model.portraitScale)
matrixStack.mulPose(quaternion1)
matrixStack.mulPose(quaternion2)
val light1 = Vector3f(0.2F, 1.0F, -1.0F)
val light2 = Vector3f(0.1F, 0.0F, 8.0F)
RenderSystem.setShaderLights(light1, light2)
quaternion1.conjugate()
val immediate = Minecraft.getInstance().renderBuffers().bufferSource()
val buffer = immediate.getBuffer(renderType)
val packedLight = LightTexture.pack(11, 7)
val colour = toHex(r, g, b, a)
model.withLayerContext(immediate, state, VaryingModelRepository.getLayers(identifier, state)) {
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, colour)
immediate.endBatch()
}
model.setDefault()
Lighting.setupFor3DItems()
} else {
renderSprite(matrixStack, sprite)
}
matrixStack.popPose()
}
private fun getSpeedRange(form: FormData, level: Int, speedBoost: Double): MutableComponent {
val base = form.baseStats[Stats.SPEED] ?: 0
val maxIV = 31
val maxEV = 252
val minNatureMod = 0.9
val maxNatureMod = 1.1
val minSpeed = getStat(base, level, 0, 0, minNatureMod) * speedBoost
val maxSpeed = getStat(base, level, maxIV, maxEV, maxNatureMod) * speedBoost
return "cobblemon.battle.ui.speed_tier".asTranslated(minSpeed.toInt(), maxSpeed.toInt())
}
private fun getStat(base: Int, level: Int, iv: Int, ev: Int, natureMod: Double): Int {
return floor((floor(((2.0 * base + iv + floor(ev / 4.0)) * level) / 100) + 5) * natureMod).toInt()
}
private fun getMultiplierText(multiplier: Double): MutableComponent {
val rounded = decimalFormat.format(multiplier)
val str = "x$rounded"
return when {
multiplier > 1.0 -> str.green()
multiplier < 1.0 -> str.red()
else -> str.text()
}
}
private fun getFormText(species: Species, form: FormData): MutableComponent {
val speciesName = species.name
val formName = form.name
return if (formName == "Normal") speciesName.text() else "$speciesName-$formName".text()
}
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
@@ -76,12 +74,6 @@ class BattleGeneralActionSelection(
battleGUI.changeActionSelection(ForfeitConfirmationSelection(battleGUI, request))
playDownSound(Minecraft.getInstance().soundManager)
}
if (battle.side2.actors.all { it.type == ActorType.PLAYER }) {
addOption(rank++, battleLang("ui.team_info.button"), cobblemonResource("textures/gui/battle/team_info.png")) {
battleGUI.changeActionSelection(BattleTeamInfoSelection(battleGUI, request))
playDownSound(Minecraft.getInstance().soundManager)
}
}
}
}
}
@@ -129,4 +121,4 @@ class BattleGeneralActionSelection(
}
override fun narrationPriority() = NarratableEntry.NarrationPriority.NONE
}
}
@@ -10,41 +10,27 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.gold
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.Effectiveness
import com.cobblemon.mod.common.client.battle.MoveEffectivenessCalculator
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.gui.summary.widgets.screens.moves.MovesWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.locale.Language
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth.floor
import java.math.RoundingMode
import java.text.DecimalFormat
class BattleMoveSelection(
battleGUI: BattleGUI,
@@ -65,21 +51,7 @@ class BattleMoveSelection(
const val MOVE_HORIZONTAL_SPACING = 13F
val moveTexture = cobblemonResource("textures/gui/battle/battle_move.png")
val moveOverlayTexture = cobblemonResource("textures/gui/battle/move_outline.png")
val moveTooltipIcon = cobblemonResource("textures/gui/battle/move_tooltip_icon.png")
val moveTooltip = cobblemonResource("textures/gui/battle/move_tooltip.png")
val moveTooltipForStatus = cobblemonResource("textures/gui/battle/move_tooltip_status.png")
val effectiveIcon = cobblemonResource("textures/gui/battle/effective_icon.png")
val extremelyEffectiveIcon = cobblemonResource("textures/gui/battle/extremely_effective_icon.png")
val immuneIcon = cobblemonResource("textures/gui/battle/immune_icon.png")
val mostlyIneffectiveIcon = cobblemonResource("textures/gui/battle/mostly_ineffective_icon.png")
val superEffectiveIcon = cobblemonResource("textures/gui/battle/super_effective_icon.png")
val notVeryEffectiveIcon = cobblemonResource("textures/gui/battle/not_very_effective_icon.png")
private val decimalFormat = DecimalFormat("#.##").also {
it.roundingMode = RoundingMode.CEILING
}
val moveOverlayTexture = cobblemonResource("textures/gui/battle/battle_move_overlay.png")
}
val moveSet = request.moveSet!!
@@ -102,7 +74,7 @@ class BattleMoveSelection(
BattleGimmickButton.create(gimmick, this, backButton.x + xOff, backButton.y)
}
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F )
open class MoveTile(
val moveSelection: BattleMoveSelection,
@@ -166,7 +138,7 @@ class BattleMoveSelection(
// Move Category
MoveCategoryIcon(
x = x + 41,
x = x + 48,
y = y + 14.5,
category = moveTemplate.damageCategory,
opacity = moveSelection.opacity
@@ -196,265 +168,14 @@ class BattleMoveSelection(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = movePPText,
x = x + 67,
x = x + 75,
y = y + 14,
opacity = moveSelection.opacity,
centered = true
)
blitk(
matrixStack = context.pose(),
texture = moveTooltipIcon,
x = x + 82,
y = y + 14,
width = 9,
height = 9,
textureHeight = 18,
textureWidth = 9,
alpha = moveSelection.opacity,
vOffset = if (isTooltipHovered(mouseX.toDouble(), mouseY.toDouble())) 9 else 0
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = "i".text().bold(),
x = x + 87.375,
y = y + 14,
opacity = moveSelection.opacity,
centered = true
)
if (isTooltipHovered(mouseX.toDouble(), mouseY.toDouble())) {
renderTooltip(context)
}
}
fun renderTooltip(context: GuiGraphics) {
val tooltipY = Minecraft.getInstance().window.guiScaledHeight - 84 - 59 - 4
val tooltipX = 20
val move = moveTemplate
val effectiveness = getMoveEffectiveness(move)
blitk(
matrixStack = context.pose(),
texture = if (effectiveness == null) moveTooltipForStatus else moveTooltip,
x = tooltipX,
y = tooltipY,
width = 195,
height = 56,
alpha = moveSelection.opacity
)
drawScaledText(
context = context,
text = lang("ui.power"),
x = tooltipX + 18,
y = tooltipY + 11,
opacity = moveSelection.opacity,
centered = false,
scale = 0.65f
)
blitk(
matrixStack = context.pose(),
texture = MovesWidget.movesPowerIconResource,
x = (tooltipX + 8.5) / 0.66f,
y = (tooltipY + 9.5) / 0.66f,
width = 10,
height = 10,
alpha = moveSelection.opacity,
scale = 0.66f
)
val powerStr = if (move.power <= 0) "—" else move.power.toInt().toString()
drawScaledTextJustifiedRight(
context = context,
text = powerStr.text(),
x = tooltipX + 73,
y = tooltipY + 11,
opacity = moveSelection.opacity,
scale = 0.65f
)
blitk(
matrixStack = context.pose(),
texture = MovesWidget.movesAccuracyIconResource,
x = (tooltipX + 8.5) / 0.66f,
y = (tooltipY + 26) / 0.66f,
width = 10,
height = 10,
alpha = moveSelection.opacity,
scale = 0.66f
)
drawScaledText(
context = context,
text = lang("ui.accuracy"),
x = tooltipX + 18,
y = tooltipY + 27,
opacity = moveSelection.opacity,
centered = false,
scale = 0.65f
)
val accuracyStr = format(move.accuracy).text()
drawScaledTextJustifiedRight(
context = context,
text = accuracyStr,
x = tooltipX + 73,
y = tooltipY + 27,
opacity = moveSelection.opacity,
scale = 0.65f
)
blitk(
matrixStack = context.pose(),
texture = MovesWidget.movesEffectIconResource,
x = (tooltipX + 8.5) / 0.66f,
y = (tooltipY + 42.5) / 0.66f,
width = 10,
height = 10,
alpha = moveSelection.opacity,
scale = 0.66f
)
drawScaledText(
context = context,
text = lang("ui.effect"),
x = tooltipX + 18,
y = tooltipY + 43.5,
opacity = moveSelection.opacity,
centered = false,
scale = 0.65f
)
val moveEffect = format(move.effectChances.firstOrNull() ?: 0.0).text()
drawScaledTextJustifiedRight(
context = context,
text = moveEffect,
x = tooltipX + 73,
y = tooltipY + 43.5,
opacity = moveSelection.opacity,
scale = 0.65f
)
val moveDescription = move.description
val lines = Minecraft.getInstance().font.splitter.splitLines(moveDescription, 200, moveDescription.style)
val orderedLines = Language.getInstance().getVisualOrder(lines)
val maxLines = if (effectiveness == null) 6 else 4
orderedLines.take(maxLines).forEachIndexed { index, line ->
drawScaledText(
context = context,
text = line,
x = tooltipX + 85,
y = tooltipY + 11 + (6 * index),
opacity = moveSelection.opacity,
scaleX = 0.5f,
scaleY = 0.5f,
centered = false
)
}
if (effectiveness == null) return
val effectivenessText = getEffectivenessText(effectiveness)
val effectivenessIcon = getEffectivenessIcon(effectiveness)
val effectivenessValue = getEffectivenessValue(effectiveness)
blitk(
matrixStack = context.pose(),
texture = effectivenessIcon,
x = (tooltipX + 84) / 0.5f,
y = (tooltipY + 43) / 0.5f,
width = 12,
height = 12,
alpha = moveSelection.opacity,
scale = 0.5f
)
drawScaledText(
context = context,
text = effectivenessText,
x = tooltipX + 94,
y = tooltipY + 43.5,
opacity = moveSelection.opacity,
centered = false,
scale = 0.65f
)
drawScaledText(
context = context,
text = effectivenessValue,
x = tooltipX + 168,
y = tooltipY + 43.5,
opacity = moveSelection.opacity,
centered = false,
scale = 0.65f
)
}
private fun getEffectivenessValue(effectiveness: Effectiveness): MutableComponent {
return when (effectiveness) {
Effectiveness.IMMUNE -> "0.00x".text()
Effectiveness.MOSTLY_INEFFECTIVE -> "0.25x".text()
Effectiveness.NOT_VERY_EFFECTIVE -> "0.50x".text()
Effectiveness.EFFECTIVE -> "1.00x".text()
Effectiveness.SUPER_EFFECTIVE -> "2.00x".text()
Effectiveness.EXTREMELY_EFFECTIVE -> "4.00x".text()
}
}
private fun getEffectivenessIcon(effectiveness: Effectiveness): ResourceLocation {
return when (effectiveness) {
Effectiveness.IMMUNE -> immuneIcon
Effectiveness.MOSTLY_INEFFECTIVE -> mostlyIneffectiveIcon
Effectiveness.NOT_VERY_EFFECTIVE -> notVeryEffectiveIcon
Effectiveness.EFFECTIVE -> effectiveIcon
Effectiveness.SUPER_EFFECTIVE -> superEffectiveIcon
Effectiveness.EXTREMELY_EFFECTIVE -> extremelyEffectiveIcon
}
}
private fun getEffectivenessText(effectiveness: Effectiveness): MutableComponent {
return when (effectiveness) {
Effectiveness.IMMUNE -> "cobblemon.battle.ui.effectiveness.has_no_effect".asTranslated()
Effectiveness.MOSTLY_INEFFECTIVE -> "cobblemon.battle.ui.effectiveness.mostly_ineffective".asTranslated()
Effectiveness.NOT_VERY_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.not_very_effective".asTranslated()
Effectiveness.EFFECTIVE -> "cobblemon.battle.ui.effectiveness.effective".asTranslated()
Effectiveness.SUPER_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.super_effective".asTranslated()
Effectiveness.EXTREMELY_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.extremely_effective".asTranslated()
}
}
private fun getMoveEffectiveness(move: MoveTemplate): Effectiveness? {
val battle = CobblemonClient.battle ?: return null
val opponents = battle.side2.activeClientBattlePokemon.toList()
if (opponents.size != 1) return null
val opponent = battle.side2.activeClientBattlePokemon.first().battlePokemon ?: return null
val aspects: Set<String> = opponent.state.currentAspects
val opponentForm = opponent.species.getForm(aspects)
if (move.damageCategory == DamageCategories.STATUS) return null
return MoveEffectivenessCalculator.getMoveEffectiveness(move.elementalType, opponentForm.primaryType, opponentForm.secondaryType)
}
fun format(input: Double): String {
if (input <= 0) return "—"
return "${decimalFormat.format(input)}%"
}
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
return !isTooltipHovered(mouseX, mouseY) && mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
}
fun isTooltipHovered(mouseX: Double, mouseY: Double): Boolean {
return mouseX >= x + 82 && mouseX <= x + MOVE_WIDTH && mouseY >= y + 14 && 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
@@ -483,15 +204,7 @@ class BattleMoveSelection(
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
move.onClick()
} else {
battleGUI.changeActionSelection(
BattleTargetSelection(
battleGUI,
request,
move.move,
move.response.gimmickID,
move.move.gimmickMove
)
)
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move, move.response.gimmickID, move.move.gimmickMove))
playDownSound(Minecraft.getInstance().soundManager)
}
return true
@@ -511,4 +224,4 @@ class BattleMoveSelection(
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
}
@@ -1,880 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.renderSprite
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ClientBattleActor
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.battle.BattlePokemonInfoPanel
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.PORTRAIT_DIAMETER
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.questionMarkIcon
import com.cobblemon.mod.common.client.gui.battle.preview.TeamPreviewWidget
import com.cobblemon.mod.common.client.render.SpriteType
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
import com.cobblemon.mod.common.util.toHex
import com.mojang.blaze3d.platform.Lighting
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.narration.NarratableEntry
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import org.joml.Quaternionf
import org.joml.Vector3f
import java.math.RoundingMode
import java.text.DecimalFormat
import kotlin.collections.get
import kotlin.math.ceil
import kotlin.math.floor
class BattleTeamInfoSelection(
battleGUI: BattleGUI,
request: SingleActionRequest
) : BattleActionSelection(
battleGUI,
request,
x = 0,
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
width = Minecraft.getInstance().window.guiScaledWidth,
height = Minecraft.getInstance().window.guiScaledHeight,
"cobblemon.battle.ui.team_info".asTranslated()
) {
companion object {
const val SLOT_HORIZONTAL_SPACING = 4F
const val SLOT_VERTICAL_SPACING = 2F
const val BACKGROUND_HEIGHT = 148
val underlayTexture = cobblemonResource("textures/gui/battle/selection_underlay.png")
val title = cobblemonResource("textures/gui/battle/turn_counter.png")
var visible = false
}
val teamTiles = mutableListOf<PokemonTile>()
val opponentTiles = mutableListOf<PokemonTile>()
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
init {
addTiles()
}
fun addTiles() {
val battle = CobblemonClient.battle ?: return
val actors = mutableListOf<ClientBattleActor>()
battle.sides.forEach { side ->
side.actors.forEach { actors.add(it) }
}
if (actors.size != 2) return
val displayOrder = if (actors.last().uuid == Minecraft.getInstance().player?.uuid) actors.reversed() else actors
val actorToPokemon = displayOrder.map { it to ClientBattleInformationRepository.actors[it.uuid] }
actorToPokemon.first().second?.forEachIndexed { index, pokemon ->
val (slotX, slotY) = getSlotPosition(index, 1)
teamTiles.add(PokemonTile(slotX, slotY, pokemon, this, false, actorToPokemon.first().first))
}
actorToPokemon.last().second?.forEachIndexed { index, pokemon ->
val (slotX, slotY) = getSlotPosition(index, 2)
opponentTiles.add(PokemonTile(slotX, slotY, pokemon, this, true, actorToPokemon.last().first))
}
}
fun getSlotPosition(index: Int, side: Int): Pair<Float, Float> {
val startX = if (side == 1) 10 else width - 10 - (41 + TeamPreviewWidget.Companion.SLOT_HORIZONTAL_SPACING) * 2
val startY = y + 15
val row = index / 2
val column = index % 2
val slotX = startX.toFloat() + column * (SLOT_HORIZONTAL_SPACING + 41)
val slotY = startY.toFloat() + row * (SLOT_VERTICAL_SPACING + 39)
return Pair(slotX, slotY)
}
private fun getHoveredTile(team: Boolean, mouseX: Int, mouseY: Int): Int {
val tiles = if (team) teamTiles else opponentTiles
return tiles.indexOfFirst { it.pokemonDto.activeBattlePokemonDTO != null && it.isHovered(mouseX.toDouble(), mouseY.toDouble()) }
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
if (opacity <= 0.05F) return
val matrixStack = context.pose()
val mc = Minecraft.getInstance()
blitk(
matrixStack = matrixStack,
texture = underlayTexture,
x = x,
y = y,
width = width,
height = BACKGROUND_HEIGHT
)
blitk(
matrixStack = matrixStack,
texture = title,
x = (mc.window.guiScaledWidth / 2) - (103 / 2),
y = 4,
width = 103,
height = 22
)
drawScaledText(
context = context,
text = "cobblemon.battle.ui.team_info.title".asTranslated().bold(),
x = (mc.window.guiScaledWidth / 2),
y = 6,
scale = 0.75f,
shadow = true,
centered = true
)
teamTiles.forEachIndexed { index, tile ->
val hoveredTile = getHoveredTile(true, mouseX, mouseY)
if (shouldHide(hoveredTile, index, false)) return@forEachIndexed
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
}
opponentTiles.forEachIndexed { index, tile ->
val hoveredTile = getHoveredTile(false, mouseX, mouseY)
if (shouldHide(hoveredTile, index, true)) return@forEachIndexed
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
}
backButton.render(context, mouseX, mouseY, delta)
}
private fun shouldHide(hoveredTile: Int, currentTile: Int, reversed: Boolean): Boolean {
if (hoveredTile != -1) {
val hoveredRow = hoveredTile / 2
val hoveredColumn = hoveredTile % 2
val currentRow = currentTile / 2
val currentColumn = currentTile % 2
if (!reversed && hoveredColumn < currentColumn && hoveredRow <= currentRow) return true
if (reversed && hoveredColumn > currentColumn && hoveredRow <= currentRow) return true
}
return false
}
override fun mousePrimaryClicked(mouseX: Double, mouseY: Double): Boolean {
if (backButton.isHovered(mouseX, mouseY)) {
battleGUI.changeActionSelection(null)
playDownSound(Minecraft.getInstance().soundManager)
Companion.visible = false
return true
}
return false
}
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {
}
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
override fun narrationPriority() = NarratableEntry.NarrationPriority.HOVERED
class PokemonTile(
val x: Float,
val y: Float,
private val initialPokemonDto: BattlePokemonDTO,
val widget: BattleTeamInfoSelection,
val reversed: Boolean,
val actor: ClientBattleActor
) {
companion object {
const val SELECT_WIDTH = 41
const val SELECT_HEIGHT = 39
const val SCALE = 0.5F
val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
val pokemonTile = cobblemonResource("textures/gui/battle/pokemon_tile.png")
val pokemonTileReversed = cobblemonResource("textures/gui/battle/pokemon_tile_reversed.png")
val pokemonTileDisabled = cobblemonResource("textures/gui/battle/pokemon_tile_disabled.png")
val pokemonTileDisabledReversed = cobblemonResource("textures/gui/battle/pokemon_tile_disabled_reversed.png")
val pokemonTileSelected = cobblemonResource("textures/gui/battle/pokemon_tile_selected.png")
val pokemonTileSelectedReversed = cobblemonResource("textures/gui/battle/pokemon_tile_selected_reversed.png")
val hoverGapSelected = cobblemonResource("textures/gui/battle/hover_gap_selected.png")
val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
private val decimalFormat = DecimalFormat("0.00").also {
it.roundingMode = RoundingMode.CEILING
}
}
val state = FloatingState()
val pokemonDto: BattlePokemonDTO
get() = ClientBattleInformationRepository.actors[actor.uuid]?.firstOrNull { it.uuid == initialPokemonDto.uuid } ?: initialPokemonDto
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) {
val pokemon = pokemonDto.activeBattlePokemonDTO
if (pokemon == null) {
BattlePokemonInfoPanel.renderUnknownTile(context, x, y, reversed)
return
}
val battlePokemon = ClientBattlePokemon(
uuid = pokemon.uuid,
properties = pokemon.properties,
aspects = pokemon.aspects,
displayName = pokemon.displayName,
hpValue = pokemon.hpValue,
maxHp = pokemon.maxHp,
isHpFlat = pokemon.isFlatHp,
status = pokemon.status,
statChanges = pokemon.statChanges,
)
state.currentAspects = pokemon.aspects
val matrixStack = context.pose()
val isFainted = pokemonDto.fainted
blitk(
matrixStack = matrixStack,
texture = cobblemonResource("textures/gui/battle/battle_info_underlay.png"),
x = x + 5 + (if (reversed) 3 else 0),
y = y + 4,
width = 28,
height = 28
)
val speciesToDisplay = battlePokemon.species
val stateToDisplay = battlePokemon.state
context.enableScissor(
(x + 5 + (if (reversed) 3 else 0)).toInt(),
(y + 5).toInt(),
(x + 5 + 28 + (if (reversed) 3 else 0)).toInt(),
(y + 5 + 28).toInt(),
)
matrixStack.pushPose()
matrixStack.translate(
x + (if (reversed) 3 else 0) + 5 + 28 / 2.0,
y.toDouble() + 5 - 5.0,
0.0
)
drawCustomPosablePortrait(
identifier = speciesToDisplay.resourceIdentifier,
matrixStack = matrixStack,
scale = 18f,
contextScale = speciesToDisplay.getForm(stateToDisplay.currentAspects).baseScale,
reversed = reversed,
doQuirks = false,
state = stateToDisplay,
partialTicks = if (Cobblemon.config.animateBattleTiles) deltaTicks else 0F
)
matrixStack.popPose()
context.disableScissor()
val tile = if (reversed) pokemonTileReversed else pokemonTile
val disabled = if (reversed) pokemonTileDisabledReversed else pokemonTileDisabled
val selected = if (reversed) pokemonTileSelectedReversed else pokemonTileSelected
if (isFainted) {
blitk(
matrixStack = matrixStack,
texture = disabled,
x = x,
y = y,
width = 41,
height = 39
)
}
else {
blitk(
matrixStack = matrixStack,
texture = tile,
x = x,
y = y,
width = 41,
height = 39
)
}
if (isHovered(mouseX, mouseY)) {
renderExpandedInfo(context, reversed, battlePokemon, pokemonDto)
}
}
private fun renderExpandedInfo(context: GuiGraphics, reversed: Boolean, pokemon: ClientBattlePokemon, dto: BattlePokemonDTO) {
context.pose().pushPose()
context.pose().translate(0.0, 0.0, 300.0)
val startX = if (reversed) x - 136 else x + 40
val startY = y
val species = pokemon.species
val form = species.getForm(pokemon.state.currentAspects)
val expandedInfoTexture = expandedPokemonInfo
val gap = if (reversed) hoverGapReversed else hoverGap
val xOffset = if (reversed) 136 else -7
blitk(
matrixStack = context.pose(),
texture = gap,
x = startX + xOffset,
y = startY,
height = 36,
width = 8,
textureHeight = 36,
textureWidth = 8,
)
blitk(
matrixStack = context.pose(),
texture = expandedInfoTexture,
x = startX,
y = startY,
height = 112,
width = 137,
textureHeight = 112,
textureWidth = 137,
)
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.pokemon_form".asTranslated().bold(),
x = startX + 36,
y = startY + 6.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = getFormText(species, form),
x = startX + 36,
y = startY + 13.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.buffs".asTranslated().bold(),
x = startX + 36,
y = startY + 23.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = "cobblemon.stat.attack.name".asTranslated(),
x = startX + 10,
y = startY + 31.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.ATTACK)?.let {
drawScaledText(
context = context,
text = BattlePokemonInfoPanel.getBoostText(Stats.ATTACK, it),
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
y = startY + 31.5,
scale = BattleOverlay.Companion.SCALE,
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.defence.name".asTranslated(),
x = startX + 10,
y = startY + 39.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.DEFENCE)?.let {
drawScaledText(
context = context,
text = BattlePokemonInfoPanel.getBoostText(Stats.DEFENCE, it),
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
y = startY + 39.5,
scale = BattleOverlay.Companion.SCALE,
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.stats.special_attack".asTranslated(),
x = startX + 10,
y = startY + 47.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.SPECIAL_ATTACK)?.let {
drawScaledText(
context = context,
text = BattlePokemonInfoPanel.getBoostText(Stats.SPECIAL_ATTACK, it),
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
y = startY + 47.5,
scale = BattleOverlay.Companion.SCALE,
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.stats.special_defence".asTranslated(),
x = startX + 10,
y = startY + 55.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.SPECIAL_DEFENCE)?.let {
drawScaledText(
context = context,
text = BattlePokemonInfoPanel.getBoostText(Stats.SPECIAL_DEFENCE, it),
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
y = startY + 55.5,
scale = BattleOverlay.Companion.SCALE,
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.speed.name".asTranslated(),
x = startX + 10,
y = startY + 63.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.SPEED)?.let {
drawScaledText(
context = context,
text = BattlePokemonInfoPanel.getBoostText(Stats.SPEED, it),
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
y = startY + 63.5,
scale = BattleOverlay.Companion.SCALE,
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.accuracy.name".asTranslated(),
x = startX + 10,
y = startY + 71.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto.buffs.get(Stats.ACCURACY)?.let {
drawScaledText(
context = context,
text = BattlePokemonInfoPanel.getBoostText(Stats.ACCURACY, it),
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
y = startY + 71.5,
scale = BattleOverlay.Companion.SCALE,
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.stat.evasion.name".asTranslated(),
x = startX + 10,
y = startY + 79.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = false
)
dto?.buffs?.get(Stats.EVASION)?.let {
drawScaledText(
context = context,
text = BattlePokemonInfoPanel.getBoostText(Stats.EVASION, it),
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
y = startY + 79.5,
scale = BattleOverlay.Companion.SCALE,
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.ability".asTranslated().bold(),
x = startX + 100,
y = startY + 6.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
val ability = dto.ability?.let { Abilities.get(it) }
val abilityName = ability?.displayName?.asTranslated() ?: "?".text()
drawScaledText(
context = context,
text = abilityName,
x = startX + 100,
y = startY + 13.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.moves".asTranslated().bold(),
x = startX + 100,
y = startY + 23.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
if (dto.moves.size != 4) {
for (i in 0 until 4) {
drawScaledText(
context = context,
text = "?".text(),
x = startX + 100,
y = startY + 31.5 + i * 8,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
}
}
else {
val questionMarkText = "?".text()
dto.moves.forEachIndexed { index, moveDTO ->
val move = moveDTO?.move ?: questionMarkText
drawScaledText(
context = context,
text = move.string.text(),
x = if (move == questionMarkText) startX + 100 else startX + 74,
y = startY + 31.5 + index * 8,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = move == questionMarkText
)
if (moveDTO != null) {
drawScaledTextJustifiedRight(
context = context,
text = moveDTO.timesUsed.toString().text(),
x = startX + 127.5,
y = startY + 31.5 + index * 8,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
)
}
}
}
if (dto.speed != null) {
val speed = dto.speed
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
val speedAfterBoost = (speed * speedBoost).toInt()
drawScaledText(
context = context,
text = "cobblemon.stat.speed.name".asTranslated(),
x = startX + 100,
y = startY + 65.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
drawScaledText(
context = context,
text = speedAfterBoost.toString().text(),
x = startX + 100,
y = startY + 72.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
}
else {
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.speed_tier".asTranslated(),
x = startX + 100,
y = startY + 65.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
val speedTier = getSpeedRange(form, pokemon.level, speedBoost)
drawScaledText(
context = context,
text = speedTier,
x = startX + 100,
y = startY + 72.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
}
drawScaledText(
context = context,
text = "cobblemon.battle.ui.label.held_item".asTranslated(),
x = startX + 40,
y = startY + 99.5,
scale = BattleOverlay.Companion.SCALE,
shadow = true,
centered = true
)
TypeIcon(
x = startX + 100.5,
y = startY + 88,
type = form.primaryType,
secondaryType = form.secondaryType,
small = false,
centeredX = true
).render(context)
if (form.secondaryType != null) {
blitk(
matrixStack = context.pose(),
texture = cobblemonResource("textures/gui/battle/type_spacer_double.png"),
x = (startX + 69.0) / 0.45,
y = (startY + 91.5) / 0.45,
height = 24,
width = 140,
textureHeight = 24,
textureWidth = 140,
scale = 0.45f
)
}
else {
blitk(
matrixStack = context.pose(),
texture = cobblemonResource("textures/gui/summary/type_spacer.png"),
x = (startX + 70.5) / 0.45,
y = (startY + 91.5) / 0.45,
height = 24,
width = 132,
textureHeight = 24,
textureWidth = 132,
scale = 0.45f
)
}
val heldItem = dto.heldItem
if (heldItem != null && !heldItem.isEmpty()) {
context.pose().pushPose()
context.pose().scale(0.95F, 0.95F, 0.95F)
val itemX = (startX + 4) / 0.95
val itemY = (startY + 91) / 0.95
context.renderItem(heldItem, itemX.toInt(), itemY.toInt())
context.pose().popPose()
}
else {
blitk(
matrixStack = context.pose(),
texture = questionMarkIcon,
x = startX + 6,
y = startY + 92.5,
height = 11,
width = 10,
)
}
context.pose().popPose()
}
@JvmOverloads
fun drawCustomPosablePortrait(
identifier: ResourceLocation,
matrixStack: PoseStack,
scale: Float = 13F,
contextScale: Float = 1F,
reversed: Boolean = false,
state: PosableState,
partialTicks: Float,
limbSwing: Float = 0F,
limbSwingAmount: Float = 0F,
ageInTicks: Float = 0F,
headYaw: Float = 0F,
headPitch: Float = 0F,
r: Float = 1F,
g: Float = 1F,
b: Float = 1F,
a: Float = 1F,
doQuirks: Boolean = true
) {
RenderSystem.applyModelViewMatrix()
matrixStack.pushPose()
matrixStack.translate(0.0, PORTRAIT_DIAMETER.toDouble() + 2.0, 0.0)
matrixStack.scale(scale, scale, -scale)
matrixStack.translate(0.0, -PORTRAIT_DIAMETER / 18.0, 0.0)
val sprite = VaryingModelRepository.getSprite(identifier, state, SpriteType.PORTRAIT);
if (sprite == null) {
val model = VaryingModelRepository.getPoser(identifier, state)
state.currentModel = model
val texture = VaryingModelRepository.getTexture(identifier, state)
val context = RenderContext()
model.context = context
VaryingModelRepository.getTextureNoSubstitute(identifier, state).let { context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, contextScale)
context.put(RenderContext.SPECIES, identifier)
context.put(RenderContext.ASPECTS, state.currentAspects)
context.put(RenderContext.POSABLE_STATE, state)
context.put(RenderContext.DO_QUIRKS, doQuirks)
val renderType = RenderType.entityCutout(texture)
val quaternion1 = Axis.YP.rotationDegrees(-32F * if (reversed) -1F else 1F)
val quaternion2 = Axis.XP.rotationDegrees(5F)
val originalPose = state.currentPose
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
state.updatePartialTicks(partialTicks)
model.applyAnimations(null, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
originalPose?.let { state.setPose(it) }
matrixStack.translate(
model.portraitTranslation.x * if (reversed) -1F else 1F,
model.portraitTranslation.y + 1.5 * model.portraitScale,
model.portraitTranslation.z - 4
)
matrixStack.scale(model.portraitScale, model.portraitScale, 1 / model.portraitScale)
matrixStack.mulPose(quaternion1)
matrixStack.mulPose(quaternion2)
val light1 = Vector3f(0.2F, 1.0F, -1.0F)
val light2 = Vector3f(0.1F, 0.0F, 8.0F)
RenderSystem.setShaderLights(light1, light2)
quaternion1.conjugate()
val immediate = Minecraft.getInstance().renderBuffers().bufferSource()
val buffer = immediate.getBuffer(renderType)
val packedLight = LightTexture.pack(11, 7)
val colour = toHex(r, g, b, a)
model.withLayerContext(immediate, state, VaryingModelRepository.getLayers(identifier, state)) {
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, colour)
immediate.endBatch()
}
model.setDefault()
Lighting.setupFor3DItems()
} else {
renderSprite(matrixStack, sprite)
}
matrixStack.popPose()
}
private fun getSpeedRange(form: FormData, level: Int, speedBoost: Double): MutableComponent {
val base = form.baseStats[Stats.SPEED] ?: 0
val maxIV = 31
val maxEV = 252
val minNatureMod = 0.9
val maxNatureMod = 1.1
val minSpeed = getStat(base, level, 0, 0, minNatureMod) * speedBoost
val maxSpeed = getStat(base, level, maxIV, maxEV, maxNatureMod) * speedBoost
return "cobblemon.battle.ui.speed_tier".asTranslated(minSpeed.toInt(), maxSpeed.toInt())
}
private fun getStat(base: Int, level: Int, iv: Int, ev: Int, natureMod: Double): Int {
return floor((floor(((2.0 * base + iv + floor(ev / 4.0)) * level) / 100) + 5) * natureMod).toInt()
}
private fun getMultiplierText(multiplier: Double): MutableComponent {
val rounded = decimalFormat.format(multiplier)
val str = "x$rounded"
return when {
multiplier > 1.0 -> str.green()
multiplier < 1.0 -> str.red()
else -> str.text()
}
}
private fun getFormText(species: Species, form: FormData): MutableComponent {
val speciesName = species.name
val formName = form.name
return if (formName == "Normal") speciesName.text() else "$speciesName-$formName".text()
}
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.client.gui.battle.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ClientBattleMessageQueue
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -18,15 +17,7 @@ import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.ObjectSelectionList
import net.minecraft.locale.Language
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.Style
import net.minecraft.network.chat.TextColor
import net.minecraft.network.chat.contents.TranslatableContents
import net.minecraft.util.FormattedCharSequence
import kotlin.math.max
import kotlin.math.min
import kotlin.math.round
/**
* Pane for seeing and interacting with battle messages.
@@ -45,80 +36,29 @@ class BattleMessagePane(
), CobblemonRenderable {
var opacity = 1F
private var scrolling = false
private var resizing = false
private var movingBox = false
// Offset from the mouse to the box's top-left when a move-drag begins, so the box tracks the cursor
// exactly (positioning absolutely avoids the GUI-scale mismatch that accumulating deltas causes).
private var dragOffsetX = 0.0
private var dragOffsetY = 0.0
val appropriateX: Int
get() = minecraft.window.guiScaledWidth - (FRAME_WIDTH + 12)
val appropriateY: Int
get() = minecraft.window.guiScaledHeight - (30 + (if (expanded) FRAME_EXPANDED_HEIGHT else FRAME_HEIGHT))
val battleMessages = mutableListOf<Component>()
private val unsubscribe: () -> Unit
fun dispose() = unsubscribe()
init {
correctSize()
//setRenderBackground(false)
scrollAmount = maxScroll.toDouble()
unsubscribe = messageQueue.subscribeComponents {
battleMessages.add(it)
correctBattleText()
}
}
private fun correctBattleText() {
val isFullyScrolled = maxScroll - scrollAmount < 10
clearEntries()
val textRenderer = Minecraft.getInstance().font
for (message in battleMessages) {
// Turn announcements ("It is now turn N") render as a centered "Turn N" divider header.
val contents = message.contents
if (contents is TranslatableContents && contents.key == "cobblemon.battle.turn") {
val turnNum = contents.args.firstOrNull()?.toString() ?: ""
addEntry(BattleMessageLine(this, null, Component.translatable("deltaclient.ui.battle.turn", turnNum).string))
continue
}
// Tint certain message categories by their translation key: weather/field/screen → teal,
// stat changes → purple, damage → blue. Other messages keep their existing styling.
var style = message.style.withBold(true).withFont(CobblemonResources.DEFAULT_LARGE)
val color = messageColor((contents as? TranslatableContents)?.key)
if (color != null) style = style.withColor(TextColor.fromRgb(color))
val line = message.copy().setStyle(style)
val wrappedLines = textRenderer.splitter.splitLines(line, battleLogWidth - 27, line.style)
val lines = Language.getInstance().getVisualOrder(wrappedLines)
for (finalLine in lines) {
addEntry(BattleMessageLine(this, finalLine))
messageQueue.subscribe {
val fullyScrolledDown = maxScroll - scrollAmount < 10
addEntry(BattleMessageLine(this, it))
if (fullyScrolledDown) {
scrollAmount = maxScroll.toDouble()
}
}
if (isFullyScrolled) {
scrollAmount = maxScroll.toDouble()
}
}
/** Colour for a battle message based on its translation key, or null to keep its existing styling. */
private fun messageColor(key: String?): Int? = when {
key == null -> null
key == "cobblemon.battle.damage_dealt" -> 0x4298ED // damage % → blue
key.startsWith("cobblemon.battle.weather.") -> 0x42EDAF // weather → teal
key.startsWith("cobblemon.battle.fieldstart.") -> 0x42EDAF // terrains, rooms, gravity, etc.
key.startsWith("cobblemon.battle.fieldend.") -> 0x42EDAF
key.startsWith("cobblemon.battle.fieldactivate.") -> 0x42EDAF
key.startsWith("cobblemon.battle.sidestart.") -> 0x42EDAF // Reflect, Light Screen, etc.
key.startsWith("cobblemon.battle.sideend.") -> 0x42EDAF
key.startsWith("cobblemon.battle.boost.") -> 0x8C42ED // stat rose → purple
key.startsWith("cobblemon.battle.unboost.") -> 0x8C42ED // stat fell → purple
else -> null
}
private fun correctSize() {
setRectangle(battleLogWidth - 11, battleLogHeight - 13, battleLogX.toInt(), battleLogY.toInt() + 8)
val textBoxHeight = if (expanded) TEXT_BOX_HEIGHT * 2 else TEXT_BOX_HEIGHT
setRectangle(TEXT_BOX_WIDTH, textBoxHeight, appropriateY + 6, appropriateY + 6)
this.x = appropriateX
}
companion object {
@@ -131,52 +71,10 @@ class BattleMessagePane(
const val TEXT_BOX_HEIGHT = 46
const val EXPAND_TOGGLE_SIZE = 5
private val battleMessagePaneFrameResource = cobblemonResource("textures/gui/battle/battle_log.png")
private val battleMessagePaneFrameExpandedResource = cobblemonResource("textures/gui/battle/battle_log_expanded.png")
private val battleMessageHighlight = cobblemonResource("textures/gui/battle/battle_log_row_selected_color.png")
val topTexture = cobblemonResource("textures/gui/battle/log/top.png")
val bottomTexture = cobblemonResource("textures/gui/battle/log/bottom.png")
val leftTexture = cobblemonResource("textures/gui/battle/log/left.png")
val rightTexture = cobblemonResource("textures/gui/battle/log/right.png")
val topLeftTexture = cobblemonResource("textures/gui/battle/log/top_left.png")
val topRightTexture = cobblemonResource("textures/gui/battle/log/top_right.png")
val bottomLeftTexture = cobblemonResource("textures/gui/battle/log/bottom_left.png")
val bottomRightTexture = cobblemonResource("textures/gui/battle/log/bottom_right.png")
val centerTexture = cobblemonResource("textures/gui/battle/log/center.png")
private var expanded = false
var battleLogWidth: Int = 153
set(value) {
val max = Minecraft.getInstance().window.guiScaledWidth - 20
if (battleLogX + value <= max) {
field = value
}
}
var battleLogHeight: Int = 46
set(value) {
val max = Minecraft.getInstance().window.guiScaledHeight - 20
if (battleLogY + value <= max) {
field = value
}
}
var battleLogX: Double = defaultLogX
get() {
val max = Minecraft.getInstance().window.guiScaledWidth - battleLogWidth
return max(min(field, max.toDouble() - 20), 4.0)
}
var battleLogY: Double = defaultLogY
get() {
val max = Minecraft.getInstance().window.guiScaledHeight - battleLogHeight
return max(min(field, max.toDouble() - 20), 4.0)
}
val defaultLogX: Double
get() = Minecraft.getInstance().window.guiScaledWidth - 181.0
val defaultLogY: Double
get() = Minecraft.getInstance().window.guiScaledHeight - 85.0
}
override fun addEntry(entry: BattleMessageLine): Int {
@@ -184,7 +82,7 @@ class BattleMessagePane(
}
override fun getRowLeft(): Int {
return x + 10
return super.getRowLeft() + 4
}
override fun renderSelection(guiGraphics: GuiGraphics, top: Int, width: Int, height: Int, outerColor: Int, innerColor: Int) {
@@ -204,7 +102,7 @@ class BattleMessagePane(
x = x + 6,
y = top - 2,
height = 1,
width = battleLogWidth - 18,
width = LINE_WIDTH,
alpha = opacity
)
@@ -214,14 +112,14 @@ class BattleMessagePane(
x = x + 6,
y = top + 7,
height = 1,
width = battleLogWidth - 18,
width = LINE_WIDTH,
alpha = opacity
)
blitk(
matrixStack = guiGraphics.pose(),
texture = battleMessageHighlight,
x = x + 6 + battleLogWidth - 19,
x = x + 6 + LINE_WIDTH - 1,
y = top - 2,
height = 10,
width = 1,
@@ -230,198 +128,62 @@ class BattleMessagePane(
}
override fun getRowWidth(): Int {
return battleLogWidth - 16
return LINE_WIDTH
}
override fun getScrollbarPosition(): Int {
return this.x + battleLogWidth - 16
return this.x + 154
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
correctSize()
val isFullyScrolled = opacity != 1f || maxScroll - scrollAmount < 2
if (isFullyScrolled) { scrollAmount = maxScroll.toDouble() }
blitk(
matrixStack = context.pose(),
texture = topLeftTexture,
x = battleLogX,
y = battleLogY,
height = 8,
width = 7,
textureHeight = 8,
textureWidth = 7,
alpha = opacity
)
blitk(
matrixStack = context.pose(),
texture = topRightTexture,
x = battleLogX + battleLogWidth - 13,
y = battleLogY,
height = 8,
width = 13,
textureHeight = 8,
textureWidth = 13,
alpha = opacity
)
blitk(
matrixStack = context.pose(),
texture = bottomLeftTexture,
x = battleLogX,
y = battleLogY + battleLogHeight - 5,
height = 5,
width = 7,
textureHeight = 5,
textureWidth = 7,
alpha = opacity
)
blitk(
matrixStack = context.pose(),
texture = bottomRightTexture,
x = battleLogX + battleLogWidth - 13,
y = battleLogY + battleLogHeight - 10,
height = 10,
width = 13,
textureHeight = 10,
textureWidth = 13,
alpha = opacity
)
blitk(
matrixStack = context.pose(),
texture = topTexture,
x = battleLogX + 7,
y = battleLogY,
height = 7,
width = battleLogWidth - 7 - 13,
textureHeight = 7,
textureWidth = 1,
alpha = opacity
)
blitk(
matrixStack = context.pose(),
texture = bottomTexture,
x = battleLogX + 7,
y = battleLogY + battleLogHeight - 4,
height = 4,
width = battleLogWidth - 7 - 12,
textureHeight = 4,
textureWidth = 1,
alpha = opacity
)
blitk(
matrixStack = context.pose(),
texture = leftTexture,
x = battleLogX,
y = battleLogY + 8,
height = battleLogHeight - 8 - 5,
width = 7,
textureHeight = 1,
textureWidth = 7,
alpha = opacity
)
blitk(
matrixStack = context.pose(),
texture = rightTexture,
x = battleLogX + battleLogWidth - 13,
y = battleLogY + 8,
height = battleLogHeight - 18,
width = 13,
textureHeight = 1,
textureWidth = 13,
alpha = opacity
)
blitk(
matrixStack = context.pose(),
texture = centerTexture,
x = battleLogX + 7,
y = battleLogY + 7,
height = battleLogHeight - 11,
width = battleLogWidth - 20,
textureHeight = battleLogHeight - 11,
textureWidth = battleLogWidth - 20,
texture = if (expanded) battleMessagePaneFrameExpandedResource else battleMessagePaneFrameResource,
x = this.x,
y = appropriateY,
height = if (expanded) FRAME_EXPANDED_HEIGHT else FRAME_HEIGHT,
width = FRAME_WIDTH,
alpha = opacity
)
val textBoxHeight = if (expanded) TEXT_BOX_HEIGHT * 2 else TEXT_BOX_HEIGHT
context.enableScissor(
x + 5,
round(battleLogY + 6).toInt(),
x + 6 + battleLogWidth,
round(battleLogY + 6 + battleLogHeight).toInt()
this.x + 5,
appropriateY + 6,
this.x + 5 + width,
appropriateY + 6 + textBoxHeight
)
super.renderWidget(context, mouseX, mouseY, partialTicks)
context.disableScissor()
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (button != 0) return false
// Intentionally NOT calling super.mouseClicked — that selects the clicked log line, which we no
// longer want. Instead the press grabs the scrollbar, the bottom-right resize handle, or (anywhere
// else on the box) moves the whole box.
updateScrollingState(mouseX, mouseY)
if (scrolling) return true
if (isOnResizeHandle(mouseX, mouseY)) {
resizing = true
return true
val toggleOffsetY = if (expanded) 92 else 46
if (mouseX > (this.x + 160) && mouseX < (this.x + 160 + EXPAND_TOGGLE_SIZE) && mouseY > (appropriateY + toggleOffsetY) && mouseY < (appropriateY + toggleOffsetY + EXPAND_TOGGLE_SIZE)) {
expanded = !expanded
}
if (isWithinBox(mouseX, mouseY)) {
movingBox = true
dragOffsetX = mouseX - battleLogX
dragOffsetY = mouseY - battleLogY
return true
}
return false
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
scrolling = false
resizing = false
movingBox = false
return super.mouseReleased(mouseX, mouseY, button)
updateScrollingState(mouseX, mouseY)
if (scrolling) {
focused = getEntryAtPosition(mouseX, mouseY)
isDragging = true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (scrolling) {
when {
mouseY < this.y -> scrollAmount = 0.0
mouseY > bottom -> scrollAmount = maxScroll.toDouble()
else -> scrollAmount += deltaY
if (mouseY < this.y) {
scrollAmount = 0.0
} else if (mouseY > bottom) {
scrollAmount = maxScroll.toDouble()
} else {
scrollAmount += deltaY
}
return true
}
if (resizing) {
battleLogHeight = max(mouseY.toInt() + 7 - y, TEXT_BOX_HEIGHT)
battleLogWidth = max(mouseX.toInt() - x, TEXT_BOX_WIDTH)
correctSize()
correctBattleText()
return true
}
if (movingBox) {
battleLogX = mouseX - dragOffsetX
battleLogY = mouseY - dragOffsetY
return true
}
return false
}
/** The whole message box (frame included) — grab it anywhere here to move it. */
private fun isWithinBox(mouseX: Double, mouseY: Double): Boolean {
return mouseX >= battleLogX && mouseX <= battleLogX + battleLogWidth &&
mouseY >= battleLogY && mouseY <= battleLogY + battleLogHeight
}
/** The resize handle in the bottom-right corner (drag to expand); takes priority over moving. */
private fun isOnResizeHandle(mouseX: Double, mouseY: Double): Boolean {
return mouseX >= battleLogX + battleLogWidth - 13 && mouseX <= battleLogX + battleLogWidth &&
mouseY >= battleLogY + battleLogHeight - 10 && mouseY <= battleLogY + battleLogHeight
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
@@ -431,7 +193,7 @@ class BattleMessagePane(
&& mouseY < bottom
}
class BattleMessageLine(val pane: BattleMessagePane, val line: FormattedCharSequence?, val headerText: String? = null) : Entry<BattleMessageLine>() {
class BattleMessageLine(val pane: BattleMessagePane, val line: FormattedCharSequence) : Entry<BattleMessageLine>() {
override fun getNarration() = "".text()
override fun render(
context: GuiGraphics,
@@ -445,54 +207,13 @@ class BattleMessagePane(
isHovered: Boolean,
partialTicks: Float
) {
if (headerText != null) {
// "Turn N" centered on the divider's true midpoint, with an equal-length line each side.
val comp = headerText.text().setStyle(Style.EMPTY.withBold(true).withFont(CobblemonResources.DEFAULT_LARGE))
val textWidth = Minecraft.getInstance().font.width(comp)
val dividerLeft = rowLeft - 3
val dividerRight = rowLeft + rowWidth - 7
val centerX = (dividerLeft + dividerRight) / 2
val lineY = rowTop + 2
val leftEnd = centerX - textWidth / 2 - 3
val rightStart = centerX + textWidth / 2 + 3
blitk(
matrixStack = context.pose(),
texture = CobblemonResources.WHITE,
x = dividerLeft,
y = lineY,
width = (leftEnd - dividerLeft).coerceAtLeast(0),
height = 1,
alpha = pane.opacity * 0.6f
)
blitk(
matrixStack = context.pose(),
texture = CobblemonResources.WHITE,
x = rightStart,
y = lineY,
width = (dividerRight - rightStart).coerceAtLeast(0),
height = 1,
alpha = pane.opacity * 0.6f
)
drawScaledText(
context = context,
text = comp,
x = centerX - textWidth / 2,
y = rowTop - 2,
opacity = pane.opacity
)
return
}
drawScaledText(
context,
line ?: return,
line,
rowLeft,
rowTop - 2,
opacity = pane.opacity
)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
return super.mouseClicked(mouseX, mouseY, button)
}
}
}
}
@@ -36,32 +36,33 @@ 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_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HEADER_BAR_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_ARROW_PADDING_AROUND
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_PADDING_AROUND
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ABILITIES
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DESCRIPTION
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DROPS
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_EVOLUTION_TREE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_SIZE
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.AbilitiesWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DescriptionWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DropsScrollingWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.EntriesScrollingWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.MovesLearnsetWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.PokemonInfoWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.SearchWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.SizeWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.StatsWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.*
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.EvolutionTreeWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.debugEvolutionRequirementRendering
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.isInventoryKeyPressed
import com.cobblemon.mod.common.util.lang
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.EditBox
@@ -94,6 +95,10 @@ class PokedexGUI private constructor(
private val categoryBarOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_bar_category.png")
// TODO: separate into smaller components / widgets?
private val backgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay.png")
private val expandedBackgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay_expanded.png")
private val tabSelectArrow = cobblemonResource("textures/gui/pokedex/select_arrow.png")
private val tabIcons = arrayOf(
cobblemonResource("textures/gui/pokedex/tab_info.png"),
@@ -101,7 +106,8 @@ class PokedexGUI private constructor(
cobblemonResource("textures/gui/pokedex/tab_size.png"),
cobblemonResource("textures/gui/pokedex/tab_stats.png"),
cobblemonResource("textures/gui/pokedex/tab_drops.png"),
cobblemonResource("textures/gui/pokedex/tab_moves.png")
cobblemonResource("textures/gui/pokedex/tab_moves.png"),
cobblemonResource("textures/gui/pokedex/tab_evolution_tree.png"),
)
val arrowUpIcon = cobblemonResource("textures/gui/pokedex/arrow_up.png")
@@ -114,6 +120,8 @@ class PokedexGUI private constructor(
TAB_MOVES
)
private val ICONS_PADDING_BETWEEN = (BASE_WIDTH / 2 - ICONS_PADDING_AROUND * 2) / tabIcons.size
/**
* Attempts to open this screen for a client.
*/
@@ -132,6 +140,7 @@ class PokedexGUI private constructor(
var oldDragPosX = 0.0
var canDragRender = false
private var activeDragableElement: GuiEventListener? = null
private var filteredPokedex: Collection<PokedexDef> = mutableListOf()
private var seenCount = "0000"
@@ -155,13 +164,29 @@ class PokedexGUI private constructor(
private lateinit var scrollScreen: EntriesScrollingWidget
private lateinit var pokemonInfoWidget: PokemonInfoWidget
private lateinit var searchWidget: SearchWidget
private lateinit var pokemonNameWidget: PokemonNameWidget
private var selectedSearchByType: SearchByType = SearchByType.SPECIES
private var selectedCategoryFilter: PokedexCategoryFilterType = PokedexCategoryFilterType.ALL
private val tabButtons: MutableList<ScaledButton> = mutableListOf()
var seenShinyStates = setOf<String>()
var shiny = false
var maleRatio = -1F
var gender = Gender.GENDERLESS
var aspects = mutableSetOf<String>()
lateinit var tabInfoElement: GuiEventListener
var tabInfoIndex = TAB_DESCRIPTION
var evolutionTreeExpanded = false
set(value) {
field = value
if (::pokemonInfoWidget.isInitialized) {
pokemonInfoWidget.active = !value
pokemonInfoWidget.visible = !value
}
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -180,7 +205,7 @@ class PokedexGUI private constructor(
//Info Widget
if (::pokemonInfoWidget.isInitialized) removeWidget(pokemonInfoWidget)
pokemonInfoWidget = PokemonInfoWidget(x + 180, y + 28) { newForm -> updateSelectedForm(newForm) }
pokemonInfoWidget = PokemonInfoWidget(this, x + 180, y + 28) { newForm -> updateSelectedForm(newForm) }
addRenderableWidget(pokemonInfoWidget)
setUpTabs()
@@ -192,6 +217,10 @@ class PokedexGUI private constructor(
searchWidget = SearchWidget(x + 26, y + 28, 120, HEADER_BAR_HEIGHT, update = ::updateFilters)
addRenderableWidget(searchWidget)
if (::pokemonNameWidget.isInitialized) removeWidget(pokemonNameWidget)
pokemonNameWidget = PokemonNameWidget(this, x + 180, y + 28)
addRenderableWidget(pokemonNameWidget)
if (::regionSelectWidgetUp.isInitialized) removeWidget(regionSelectWidgetUp)
regionSelectWidgetUp = ScaledButton(
buttonX = (x + 95).toFloat(),
@@ -367,6 +396,14 @@ class PokedexGUI private constructor(
shadow = true
)
blitk(
matrixStack = matrices,
texture = if (evolutionTreeExpanded) expandedBackgroundOverlay else backgroundOverlay,
x = pokemonInfoWidget.x, y = pokemonInfoWidget.y,
width = HALF_OVERLAY_WIDTH,
height = HALF_OVERLAY_HEIGHT
)
// Category filter
blitk(
matrixStack = matrices,
@@ -402,7 +439,7 @@ class PokedexGUI private constructor(
blitk(
matrixStack = matrices,
texture = tabSelectArrow,
x = (x + 191.5F + (22F * tabInfoIndex)) / SCALE,
x = (x + BASE_WIDTH / 2 + ICONS_ARROW_PADDING_AROUND + tabInfoIndex * ICONS_PADDING_BETWEEN) / SCALE,
y = (y + 177) / SCALE,
width = 12,
height = 6,
@@ -441,6 +478,7 @@ class PokedexGUI private constructor(
val canDisplayEntry = true //selectedForm?.unlockForms
if (::pokemonInfoWidget.isInitialized
&& pokemonInfoWidget.active
&& !pokemonInfoWidget.suppressViewport
&& pokemonInfoWidget.isWithinPortraitSpace(mouseX, mouseY)
&& canDisplayEntry == true
@@ -449,6 +487,12 @@ class PokedexGUI private constructor(
isDragging = true
oldDragPosX = mouseX
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
activeDragableElement = pokemonInfoWidget
} else if (tabInfoIndex == TAB_EVOLUTION_TREE &&
::tabInfoElement.isInitialized &&
(tabInfoElement as EvolutionTreeWidget).isHovered(mouseX, mouseY)
) {
activeDragableElement = tabInfoElement
}
return try {
super.mouseClicked(mouseX, mouseY, button)
@@ -460,14 +504,18 @@ class PokedexGUI private constructor(
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (canDragRender) canDragRender = false
if (isDragging) isDragging = false
activeDragableElement = null
return super.mouseReleased(pMouseX, pMouseY, pButton)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (isDragging && canDragRender) {
if (isDragging && canDragRender && activeDragableElement == pokemonInfoWidget) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
pokemonInfoWidget.rotationY = (((pokemonInfoWidget.rotationY + dragOffsetY) % 360 + 360) % 360)
} else if (activeDragableElement == tabInfoElement) {
tabInfoElement.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
oldDragPosX = mouseX
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
@@ -558,6 +606,8 @@ class PokedexGUI private constructor(
selectedForm = CobblemonClient.clientPokedexData.getEncounteredForms(newSelectedEntry).firstOrNull()
pokemonInfoWidget.setDexEntry(selectedEntry!!)
pokemonNameWidget.update(selectedEntry!!)
displaytabInfoElement(tabInfoIndex)
}
@@ -569,7 +619,7 @@ class PokedexGUI private constructor(
for (i in tabIcons.indices) {
tabButtons.add(ScaledButton(
x + 190.5F + (i * 22F),
x + BASE_WIDTH / 2 + ICONS_PADDING_AROUND + i * ICONS_PADDING_BETWEEN,
y + 181.5F,
TAB_ICON_SIZE,
TAB_ICON_SIZE,
@@ -582,6 +632,8 @@ class PokedexGUI private constructor(
}
fun displaytabInfoElement(tabIndex: Int, update: Boolean = true) {
if (tabIndex != TAB_EVOLUTION_TREE) evolutionTreeExpanded = false
val showActiveTab = selectedEntry?.let { selectedForm in getSufficientlyKnownForms(it, tabIndex) } == true
var statSubIndex = 0
@@ -633,6 +685,9 @@ class PokedexGUI private constructor(
TAB_MOVES -> {
tabInfoElement = MovesLearnsetWidget(x + 180, y + 28)
}
TAB_EVOLUTION_TREE -> {
tabInfoElement = EvolutionTreeWidget(this, x, y)
}
}
val element = tabInfoElement
if (element is Renderable && element is NarratableEntry) {
@@ -707,6 +762,9 @@ class PokedexGUI private constructor(
selectedForm = fallbackForm
) { next -> cycleSelectedForm(next) }
}
TAB_EVOLUTION_TREE -> {
(tabInfoElement as EvolutionTreeWidget).setup(selectedEntry, selectedForm)
}
}
} else {
if (tabInfoIndex != TAB_DESCRIPTION) displaytabInfoElement(TAB_DESCRIPTION)
@@ -770,6 +828,10 @@ class PokedexGUI private constructor(
}
}
if (tabInfoIndex == TAB_EVOLUTION_TREE && keyCode == InputConstants.KEY_SEMICOLON) {
debugEvolutionRequirementRendering()
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
@@ -16,6 +16,8 @@ object PokedexGUIConstants {
const val HEADER_BAR_HEIGHT = 11
const val POKEMON_DESCRIPTION_HEIGHT = 42
const val POKEMON_DESCRIPTION_PADDING = 9
const val ICONS_PADDING_AROUND = 18.5f
const val ICONS_ARROW_PADDING_AROUND = ICONS_PADDING_AROUND + 1
const val TAB_ICON_SIZE = 16
@@ -36,6 +38,7 @@ object PokedexGUIConstants {
const val TAB_STATS = 3
const val TAB_DROPS = 4
const val TAB_MOVES = 5
const val TAB_EVOLUTION_TREE = 6
// RowScrollingWidget
const val SCROLL_BASE_HEIGHT = 141
@@ -13,38 +13,101 @@ import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
private val tooltipEdge = cobblemonResource("textures/gui/pokedex/tooltip_edge.png")
private val tooltipBackground = cobblemonResource("textures/gui/pokedex/tooltip_background.png")
fun renderTooltip(context: GuiGraphics, text: MutableComponent, posX: Int, posY: Int, delta: Float, offsetY: Int = 0) {
private const val TOOLTIP_SLICE_SIZE = 11
fun renderTooltip(
context: GuiGraphics,
text: MutableComponent,
posX: Int,
posY: Int,
delta: Float,
offsetY: Int = 0,
) {
renderTooltip(
context,
listOf(text),
posX,
posY,
delta,
offsetY,
)
}
fun renderTooltip(
context: GuiGraphics,
text: List<MutableComponent>,
posX: Int,
posY: Int,
delta: Float,
offsetY: Int = 0,
) {
val poseStack = context.pose()
val textWidth = Minecraft.getInstance().font.width(text.font(CobblemonResources.DEFAULT_LARGE))
val tooltipWidth = textWidth + 6
val tooltipHeight = 11
val textWidth = text.maxOf { Minecraft.getInstance().font.width(it.font(CobblemonResources.DEFAULT_LARGE)) }
val tooltipPadding = 6
val tooltipWidth = textWidth + tooltipPadding
val tooltipTop = posY + offsetY
poseStack.pushPose()
poseStack.translate(0.0, 0.0, 1000.0)
Minecraft.getInstance().let {
it.mainRenderTarget.bindWrite(false)
context.enableScissor(
posX - (tooltipWidth / 2),
tooltipTop + 1,
(posX - (tooltipWidth / 2)) + tooltipWidth,
tooltipTop + 10
tooltipTop + TOOLTIP_SLICE_SIZE * text.size - 1
)
it.gameRenderer.processBlurEffect(delta)
context.disableScissor()
it.mainRenderTarget.bindWrite(true)
}
blitk(matrixStack = poseStack, texture = tooltipEdge, x = posX - (tooltipWidth / 2) - 1, y = tooltipTop, width = 1, height = tooltipHeight)
blitk(matrixStack = poseStack, texture = tooltipBackground, x = posX - (tooltipWidth / 2), y = tooltipTop, width = tooltipWidth, height = tooltipHeight)
blitk(matrixStack = poseStack, texture = tooltipEdge, x = posX + (tooltipWidth / 2), y = tooltipTop, width = 1, height = tooltipHeight)
drawScaledText(context = context, font = CobblemonResources.DEFAULT_LARGE, text = text, x = posX, y = tooltipTop + 1, shadow = true, centered = true)
val totalHeight = TOOLTIP_SLICE_SIZE * text.size
blitSlicedVertical(poseStack, tooltipEdge, posX - (tooltipWidth / 2) - 1, tooltipTop, 1, totalHeight)
blitSlicedVertical(poseStack, tooltipBackground, posX - (tooltipWidth / 2), tooltipTop, tooltipWidth, totalHeight)
blitSlicedVertical(poseStack, tooltipEdge, posX + (tooltipWidth / 2), tooltipTop, 1, totalHeight)
val centered = text.size == 1
text.forEachIndexed { index, it ->
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = it,
x = if (centered) posX else posX - tooltipWidth / 2 + tooltipPadding / 2,
y = tooltipTop + 1 + TOOLTIP_SLICE_SIZE * index,
shadow = true,
centered = centered
)
}
poseStack.popPose()
}
// Stretching the whole 11px image over an arbitrary height thickens the caps as more lines are added, so
// instead the caps are drawn 1:1 and only the flat fill row is stretched to cover the remaining height
private fun blitSlicedVertical(
poseStack: PoseStack,
texture: ResourceLocation,
x: Number,
y: Int,
width: Number,
totalHeight: Int,
) {
val middleHeight = totalHeight - 2
blitk(matrixStack = poseStack, texture = texture, x = x, y = y, width = width, height = 1, vOffset = 0, textureHeight = TOOLTIP_SLICE_SIZE)
blitk(matrixStack = poseStack, texture = texture, x = x, y = y + 1, width = width, height = middleHeight, vOffset = middleHeight, textureHeight = TOOLTIP_SLICE_SIZE * middleHeight)
blitk(matrixStack = poseStack, texture = texture, x = x, y = y + 1 + middleHeight, width = width, height = 1, vOffset = TOOLTIP_SLICE_SIZE - 1, textureHeight = TOOLTIP_SLICE_SIZE)
}
@@ -20,8 +20,8 @@ import net.minecraft.client.sounds.SoundManager
import net.minecraft.resources.ResourceLocation
class ScaledButton(
var buttonX: Float,
var buttonY: Float,
buttonX: Float,
buttonY: Float,
val buttonWidth: Number,
val buttonHeight: Number,
var resource: ResourceLocation? = null,
@@ -30,6 +30,18 @@ class ScaledButton(
val clickAction: Button.OnPress
): Button(buttonX.toInt(), buttonY.toInt(), buttonWidth.toInt(), buttonHeight.toInt(), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
var buttonX: Float = buttonX
set(value) {
field = value
this.x = value.toInt()
}
var buttonY: Float = buttonY
set(value) {
field = value
this.y = value.toInt()
}
var isWidgetActive = false
var isVisible = true
Loaded 100 of 1717 files, more files were not shown because too many files have changed in this diff. Show more