Compare commits

...
Author SHA1 Message Date
jackowes 3d54e51fb9 properly switch pose to walking when walk speed is super slow. 2026-10-05 15:38:25 -05:00
jackowes e3c1fff02a Update speedtuner with the ability to slow animations, +- buttons on the slider. and a working pause. 2026-10-05 15:19:48 -05:00
jackowes c672a4d690 Merge remote-tracking branch 'origin/main' into mon-smoothening 2026-10-04 18:44:12 -05:00
jackowes e64df48441 allow for the speed tuner to set walk speeds during the session 2026-10-04 18:34:03 -05:00
Polymeta 7511facebc Merge branch 'decorative-items-for-all' into 'main'
More Decorative Items

See merge request cable-mc/cobblemon!2430
2026-10-04 06:58:14 +00:00
dhi_holo 1486f5cfc2 Merge branch 'main' into decorative-items-for-all 2026-10-04 12:43:57 +10:00
dhi_holo 558e2d89ff Mipmap Fix
shhhh, I did it the cheap and nasty way rather than tweaking the models, it's for the greater good though
2026-10-04 12:01:29 +10:00
dhi_holo ea7b688655 Asset Updates 2026-10-04 11:35:22 +10:00
Polymeta fdafa3435c Merge branch 'flora-heals-an-aching-soul-' into 'main'
Flora Update

See merge request cable-mc/cobblemon!2412
2026-10-04 00:02:50 +00:00
dhi_holo f17c90f6ea Changelog
(also fixed some issues from the merge conflicts I missed)
2026-10-04 09:14:54 +10:00
dhi_holo 1dc45669bb Recipe Updates the Third 2026-10-04 09:14:12 +10:00
dhi_holo f3658614a4 Merge branch 'main' into decorative-items-for-all 2026-10-04 08:35:05 +10:00
Polymeta 422f60f587 Merge branch 'feat/tm-creative-tab' into 'main'
Add Technical Machines creative tab

See merge request cable-mc/cobblemon!2443
2026-10-03 22:31:41 +00:00
dhi_holo 8190ddf0e9 Merge branch 'main' into flora-heals-an-aching-soul- 2026-10-04 08:31:26 +10:00
jackowes acd375f533 - fix jitter in dynamic animations.
- Lookahead scales with turn distance
- Walk instead of sprint through narrow path sections
- Eased head look
- No looking around while travelling
- Pitch tilt applies to the whole model
- Slower floatier turns in water and air
- Large Pokémon slide onto close targets instead of spinning
2026-10-03 17:24:59 -05:00
dhi_holo af281d0bde Pa'uvillea 2026-10-04 07:57:39 +10:00
Gitoido 754e9bc158 chore: changelog skip_ci 2026-10-03 20:48:36 +03:00
Gitoido 2df8504139 feat: lore correct sorting 2026-10-03 20:35:44 +03:00
Gitoido 2838f97c56 feat: add Technical Machines dynamic creative tab 2026-10-03 19:55:32 +03:00
Gitoido 68c75deeeb feat: Technical Machines creative tab i18n 2026-10-03 19:55:04 +03:00
jackowes 9076c659de run script to ungate many riding overlay animations 2026-10-02 20:19:16 -05:00
jackowes 9292504f8c Move away from non minecrafty speed holding and redo how rotations are handled. move away from reynolds model. 2026-10-02 20:00:56 -05: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
dhi_holo e9bffd3ad7 Minty Fresh
Updated Mint Leaf Litter to use a texture closer to Vanilla to make the tiling nicer
2026-10-02 17:18:54 +10: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
dhi_holo 460c472c55 Asset Updates
Approved assets for all Terrain Plants, Mint Leaf Litter, and three of the nectar flowers (at my own fault). Potted plants already match approval
2026-10-02 16:09:11 +10:00
dhi_holo 69c3b6142d Merge branch 'main' into flora-heals-an-aching-soul- 2026-10-02 14:56:06 +10:00
jackowes ad484abf56 Merge remote-tracking branch 'origin/main' into mon-smoothening 2026-10-01 23:14:46 -05:00
jackowes fc1c7cf6ca allow for dynamic animations outside of rides. 2026-10-01 23:13:01 -05: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
Sterrezo 2aa4ab4b52 Added sounds to bright powder illuminating signs 2026-10-01 17:40:13 +02:00
Gitoido 113977990e feat: helper functions to register plants and pots 2026-10-01 17:22:09 +03: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
jackowes 33abca9b43 First draft of sprint and walk speed tuning tool 2026-09-29 21:40:04 -05:00
jackowes ea3c051772 Sprint now in blocks per second and default sprint speed is player sprint speed. 2026-09-29 20:44:00 -05:00
dhi_holo 3bd3ce5688 Delete wisteria_root_side.json 2026-09-30 10:58:27 +10: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
dhi_holo 881a87216b Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-30 10:11:19 +10:00
dhi_holo 1d473b82af Refactor
Renamed Voltconia to Voltaconia, and also fixed the models being in a temp folder for easier file editing because that's not needed anymore
2026-09-30 10:10:55 +10: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
jackowes 4cde730259 these are not the correct file 2026-09-27 21:53:02 -05:00
jackowes 2bcce06318 - fix sliding after being hit while walking on a path.
- make it so running starts when they mon is farther
- smaller mons get closer before stopping when traveling to the owner.
- make it so mons immediately sprint when the player is sprinting.
2026-09-27 21:45:35 -05:00
jackowes 930c3568bb add temp sprint speeds to all sprinters. add sprint to attacktargettask as well 2026-09-27 21:03:42 -05:00
jackowes ffe480b12f Smoother path following, steering move control, body rotation and AI sprinting 2026-09-27 20:20:54 -05: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
dhi_holo e0d0c38232 Merge branch 'main' into decorative-items-for-all 2026-09-25 15:02:09 +10: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
dhi_holo 217d4cda42 Update CHANGELOG.md 2026-09-22 10:44:12 +10:00
dhi_holo 08daa43aae Update blank_tm.json 2026-09-22 10:41:01 +10:00
dhi_holo 184d889831 TM Boost 2026-09-22 10:36:59 +10:00
dhi_holo c6e0f431ae Update never_melt_ice.json 2026-09-22 10:31:59 +10:00
dhi_holo 93daeab840 Merge branch 'main' into decorative-items-for-all 2026-09-22 10:16:29 +10:00
dhi_holo f132b297c6 Never-Melted
Recipe Update
2026-09-22 10:16:17 +10: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
dhi_holo c0a524a718 Jars and Generators and a forgotten Pouch
6 new items ready to decorate your worlds
2026-09-21 11:58:11 +10: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
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
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
Kenji_64 5d53fba2ce Fixed Rockruff line missing blinks 2026-09-14 20:47:40 +02:00
dhi_holo 92b0ef84e9 Yeah, I've Got Time
This'll hopefully be ready in time for 1.8.2 so I'm making the decision to treat it as if it is now, so it almost certainly will be
2026-09-14 16:37:09 +10:00
dhi_holo dc8d18384c Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-14 16:33:26 +10:00
dhi_holo 44c056ecd5 Update CHANGELOG-DecorativeWv2.md
Fixed that up for cleanup reasons
2026-09-14 11:34:16 +10:00
dhi_holo dcc707c20d Model Fix
Improved the visuals and log spam of the Wistensu blocks
2026-09-14 10:53:12 +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
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
Gertimoshka 07b1b22c2c Wistensu Flower Logic Implementation 2026-09-13 15:08:44 +02:00
dhi_holo 23078eccba Wistensu Root Models
Fixed a tag and added the models for the Wistensu Roots
2026-09-13 21:11:11 +10:00
ewwww 8b825d558f cacnea & cacturne remodels + animations 2026-09-13 06:28:57 -04:00
dhi_holo 8a82f35edf Seasonings 2026-09-13 19:59:19 +10:00
dhi_holo bec664c073 Merge Conflict Fixes 2026-09-13 13:01:20 +10: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
dhi_holo 3e61424d8f Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-13 12:28:17 +10:00
dhi_holo c1606c6fc9 Textures!
Terrain Plants now have proper textures, finishing them off beyond some data file work waiting on some conversations
2026-09-13 11:42:42 +10:00
dhi_holo e0108cc99f Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-13 10:47:37 +10:00
dhi_holo c0de8a2591 Update TerrainSeedBlock.kt
Actually fixed it this time
2026-09-12 15:19:09 +10:00
dhi_holo b8733eed52 Revert "Update TerrainSeedBlock.kt"
This reverts commit bd0511a9fb.
2026-09-12 14:31:19 +10:00
dhi_holo 4aa30efec7 Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-12 13:04:47 +10:00
Plastered_Crab 4354c3fb03 Fix mip map of TM shelf disc textures to be 16x16 2026-09-10 11:56:30 -07: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
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 7930ec52fa Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-07 15:20:47 +10:00
dhi_holo 57eb306e4f Update TODO List - Flora Heals An Aching Soul.md 2026-09-06 21:25:18 +10:00
dhi_holo bd0511a9fb Update TerrainSeedBlock.kt
Terrain crops now break properly after growing to be 2 blocks tall
2026-09-06 21:24:28 +10:00
dhi_holo c14f3292aa Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-06 19:57:15 +10: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
FrankTheFarmer 3c7637515c Placeholder idle animation for snover 2026-08-10 13:03:00 +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
FrankTheFarmer 20a35de3d8 What if we started implementing pokemon rn rn 2026-08-08 14:58:20 +10:00
dhi_holo 8fb2b1cf3d Merge branch 'main' into flora-heals-an-aching-soul- 2026-07-12 08:20:14 +10:00
dhi_holo 5c7d1cbe10 Fixed Case of the Missing Loot Table 2026-06-04 15:47:55 +10:00
dhi_holo 3348bfc362 Merge branch 'main' into flora-heals-an-aching-soul- 2026-06-04 14:35:35 +10:00
dhi_holo 8db9252434 Potted Berries for All 2026-05-25 18:09:49 +10:00
dhi_holo d9d4fe15cc Update TODO List - Flora Heals An Aching Soul.md 2026-05-25 08:40:11 +10:00
dhi_holo 8453e2d514 Milling Integration
with Create
2026-05-25 08:15:47 +10:00
dhi_holo 4d3070c0c2 Compost Levels 2026-05-25 08:03:54 +10:00
dhi_holo 15f273a9ef Bright Powder on Signs
It's so wonderful
2026-05-25 07:16:36 +10:00
dhi_holo 007c079cd6 WIP 2026-05-25 05:17:30 +10:00
dhi_holo 9851b1e8ad Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-25 04:22:38 +10:00
dhi_holo c65c2610cd Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-16 21:29:13 +10:00
dhi_holo 2be08a3fcc terminology corrections
also tag additions
2026-05-16 21:26:05 +10:00
dhi_holo 4cdf440eee More Tagging Work 2026-05-13 09:48:52 +10:00
dhi_holo 1545ee8fc2 Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-13 09:12:37 +10:00
dhi_holo a009fabfe4 Create TODO List - Flora Heals An Aching Soul.md 2026-05-12 22:51:33 +10:00
dhi_holo 46e4bfdd13 CONSUME 2026-05-12 22:27:07 +10:00
dhi_holo e24c82860d No Bone 2026-05-12 22:11:53 +10:00
dhi_holo 1291dee1e1 Data Files 2026-05-12 21:39:03 +10:00
dhi_holo 69732be09b Mistleaf's Fae Magics 2026-05-12 18:24:23 +10:00
dhi_holo 8ce4ed7dee Update TerrainFlowerBlock.kt
Removed unused imports
2026-05-12 14:59:52 +10:00
dhi_holo 33d3cc0104 Merge branch 'flora-heals-an-aching-soul-' of https://gitlab.com/cable-mc/cobblemon into flora-heals-an-aching-soul- 2026-05-12 14:52:36 +10:00
dhi_holo 33b6b519ed Merge branch 'flora-heals-an-aching-soul-' of https://gitlab.com/cable-mc/cobblemon into flora-heals-an-aching-soul- 2026-05-12 12:26:39 +10:00
dhi_holo 5ec849f4e3 Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-12 12:26:09 +10:00
Gertimoshka 6f4a2d2cd5 Made the flowers block entities so they can tick and adjust to weather changes 2026-05-11 21:35:48 +03:00
Gertimoshka 0bca89e77d Lucibell will now properly light up if any part of the block has an entity in it, by designating lower part as the 'driver' who controls the states 2026-05-11 17:21:22 +03:00
dhi_holo 8f9f162011 Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-11 09:56:11 +10:00
dhi_holo ac3bea3d5e Update TerrainFlowerBlock.kt
Compacted but it STILL DOESN'T WORK
2026-05-10 21:23:17 +10:00
dhi_holo 178d4f199b Update TerrainFlowerBlock.kt
I feel so close, but the weather checks don't work
2026-05-09 23:04:07 +10:00
dhi_holo 933819380d Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-09 21:15:27 +10:00
dhi_holo d68131e1b9 Update TerrainFlowerBlock.kt
It's starting to get there, probably. Not fully functional yet
2026-05-09 21:15:04 +10:00
dhi_holo ffbc379c0b Some More Work 2026-05-08 14:01:31 +10:00
dhi_holo 7053d12508 Update TerrainSeedBlock.kt
In theory this makes it grow slower on dry dirt, which would be pretty cool
2026-05-07 20:38:47 +10:00
dhi_holo d41442200f Models and Temp Textures and start of some code for glowing 2026-05-07 20:08:15 +10:00
dhi_holo d2564c343e Terrain Seed Beginnings 2026-05-04 22:43:54 +10:00
dhi_holo d1c19e8837 Visual FIxes 2026-05-04 14:30:31 +10:00
dhi_holo 0ec3bfabad Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-04 11:37:59 +10:00
dhi_holo 43ffb04621 FINALLY FUCKING FIXED IT 2026-05-04 11:34:04 +10:00
dhi_holo eeaedf76a9 progress 2026-05-04 09:43:59 +10:00
dhi_holo 120fb4829a Pa'uvillea complete
Maash's tweaks to the wall version, plus placement logic working as desired (although may need further tweaks depending on feedback)
2026-05-03 11:10:00 +10:00
dhi_holo 12b93e53a7 Revenge of the Pa'uvillea 2026-05-02 22:43:34 +10:00
dhi_holo 00b17b9764 Update CobblemonBlocks.kt 2026-05-02 18:55:21 +10:00
dhi_holo 06192f809d Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-02 18:54:04 +10:00
dhi_holo d09eb0d3ff Update LobailaFlowerBlock.kt 2026-04-23 08:05:13 +10:00
dhi_holo 416321679c Updated Textures 2026-04-22 20:29:02 +10:00
dhi_holo 72c4e6e54f Merge branch 'main' into flora-heals-an-aching-soul- 2026-04-19 16:45:10 +10:00
dhi_holo d3738e3424 Merge branch 'main' into flora-heals-an-aching-soul- 2025-12-13 18:45:37 +10:00
dhi_holo 2e183db903 Re-rename
Lobaila and Pa'uvillea changes
2025-12-02 12:33:55 +10:00
dhi_holo fa174b88dd OVERSIGHTS (may need to copy to another branch) 2025-12-01 17:09:00 +10:00
dhi_holo 0b4abe6d16 Texture Updates 2025-12-01 17:08:42 +10:00
dhi_holo 759fb0ecd8 Rename!
Lobalia, Pa'uvilla, and Pomohana are here!
2025-12-01 16:37:53 +10:00
dhi_holo 429e927c3f Potted Berries Part 1
All the data/asset stuff needed for Potted Berries.
Need to handle the codeside now, which will be trickier.
2025-11-30 22:27:29 +10:00
dhi_holo 643aed9133 Update HydrangeaFlowerBlock.kt
Comment
2025-11-30 12:02:26 +10:00
dhi_holo e701642de2 Hydrangea loot and tag starts 2025-11-30 12:00:14 +10:00
dhi_holo a3a03e3da6 Lobelia Polish and Hydrangea
Added start of Hydrangea flowers
2025-11-29 20:51:25 +10:00
dhi_holo 5dd3d5b552 Mint Rework
Totally reworked the Mint Leaf Litter blocks to not rely on Pink Petals due to the differences in behaviour, finally added Craspedia loot table, updated Craspedia texture, fixed Lobelia being shaded incorrectly
2025-11-29 16:20:45 +10:00
dhi_holo 1bae246e6c Gestdate
Ges updated textures
2025-11-29 14:39:30 +10:00
dhi_holo c94ef10189 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-29 14:24:30 +10:00
dhi_holo dfacf1f9df Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-27 18:33:35 +10:00
dhi_holo 06c5e34f48 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-23 20:33:34 +10:00
dhi_holo 1ab8b35e98 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-23 12:50:52 +10:00
dhi_holo 261b61ee6c Updated Assets and removed TODO 2025-11-22 15:31:12 +10:00
dhi_holo 5abf8e1bab Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-22 14:16:33 +10:00
dhi_holo 7c0b463637 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-21 18:14:42 +10:00
dhi_holo e156ab2432 Craspedia assets and Lobelia waterlogging
Still needs some more work done on both, but it's real nice now
2025-11-21 18:14:29 +10:00
dhi_holo 0b46a7163b Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-21 13:35:16 +10:00
dhi_holo 21b76c008a Craspedia start
Needs work, but it's fully functional
2025-11-21 13:30:19 +10:00
dhi_holo b7e9f8248d Oricorio Start
Lobelia (Red) flower partially set up.
Wanna look into letting it be waterlogged, and no dye crafting recipe is set up yet
2025-11-20 19:34:10 +10:00
dhi_holo 8b21e76a36 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-20 17:06:31 +10:00
dhi_holo 515471185f Create CHANGELOG-DecorativeWv2.md 2025-11-19 19:54:49 +10:00
dhi_holo 628e8f4e5e Mint Leaf Litter and Potted Mint
"It's not fucking weed you piece of shit stoner" -Kermit the Frog

Needs some cleanup when it comes to how I've stolen the Pink Petals (due to Leaf Litter being a future block), but what we got rn is a good starting point
2025-11-19 19:42:38 +10:00
1407 changed files with 100629 additions and 14657 deletions

No files matched your search

+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
+117 -9
View File
@@ -3,46 +3,154 @@
### Additions
- 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.
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Soft Sand, Electirizer, Magmarizer, 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, Terrain Extender, Medicinal Brew, Moomoo Milk, 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:
- 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.
### New rideable Pokémon
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots
- Added Oricorio Flowers
- Added Terrain Plants
- Berries can now be placed in Flower Pots
- 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
#### Regional Forms
#### 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
#### Added sounds to the following moves
- Treecko (New Shoulder Mount!)
- Bonsly (New Shoulder Mount!)
- Sudowoodo
- Cacnea
- Cacturne
- Ekans
- Arbok
- Oddish
- Gloom
- Vileplume
- Bellossom
### Changes
- Terrain Seeds can now be planted
- Terrain Seeds are now consumed during PvP and NPC battles
- Glow Item Frames can be crafted using Bright Powder as an alternative to Glow Ink Sacs
- Bright Powder can be used on Signs and Hanging Signs to make text glow, as an alternative to Glow Ink Sacs
- 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
- Updated the following recipes
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM
- Make ride rotations less jittery in multiplayer.
- Changes Disc Shelf TMs to use textures instead of tinting and overlays to line up with item display
- Blank TM can now be inserted into the Disc Shelf
- Added TM creative tab
- 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
- 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 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()}"
}
+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)
@@ -109,4 +109,21 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
"poke_snack",
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
)
@JvmField
val TERRAIN_PLANT: BlockEntityType<TerrainPlantBlockEntity> = this.create(
"terrain_plant",
BlockEntityType.Builder.of(::TerrainPlantBlockEntity,
CobblemonBlocks.VOLTACONIA,
CobblemonBlocks.LUCIBELL,
CobblemonBlocks.VERDROOT
).build(null)
)
@JvmField
val MISTLEAF_FLOWER: BlockEntityType<MistleafFlowerBlockEntity> = this.create(
"mistleaf",
BlockEntityType.Builder.of(::MistleafFlowerBlockEntity,
CobblemonBlocks.MISTLEAF
).build(null)
)
}
@@ -16,8 +16,7 @@ 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.decor.*
import com.cobblemon.mod.common.block.general.BaleBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
@@ -28,6 +27,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
import com.cobblemon.mod.common.block.general.MintLitterBlock
import com.cobblemon.mod.common.mixin.invoker.BlocksInvoker
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.FireBlockInvoker
@@ -653,15 +653,21 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
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 ELECTIRIZER = create("electirizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
@JvmField
val MAGMARIZER = create("magmarizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
@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(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
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(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
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(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
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 SOFT_SAND = create("soft_sand", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), 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))
@@ -691,7 +697,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
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
@@ -702,40 +708,46 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
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 TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
@JvmField
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val MEDICINAL_BREW = create("medicinal_brew", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
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))
@@ -755,21 +767,21 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
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(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
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 }))
@@ -857,6 +869,64 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
@JvmField
val RED_MINT_LEAF_LITTER = this.mintLitter("red_mint_leaf_litter", MapColor.COLOR_RED)
@JvmField
val POTTED_RED_MINT = this.pottedPlant("potted_red_mint", RED_MINT_LEAF_LITTER)
@JvmField
val BLUE_MINT_LEAF_LITTER = this.mintLitter("blue_mint_leaf_litter", MapColor.COLOR_BLUE)
@JvmField
val POTTED_BLUE_MINT = this.pottedPlant("potted_blue_mint", BLUE_MINT_LEAF_LITTER)
@JvmField
val CYAN_MINT_LEAF_LITTER = this.mintLitter("cyan_mint_leaf_litter", MapColor.COLOR_CYAN)
@JvmField
val POTTED_CYAN_MINT = this.pottedPlant("potted_cyan_mint", CYAN_MINT_LEAF_LITTER)
@JvmField
val PINK_MINT_LEAF_LITTER = this.mintLitter("pink_mint_leaf_litter", MapColor.COLOR_PINK)
@JvmField
val POTTED_PINK_MINT = this.pottedPlant("potted_pink_mint", PINK_MINT_LEAF_LITTER)
@JvmField
val GREEN_MINT_LEAF_LITTER = this.mintLitter("green_mint_leaf_litter", MapColor.COLOR_GREEN)
@JvmField
val POTTED_GREEN_MINT = this.pottedPlant("potted_green_mint", GREEN_MINT_LEAF_LITTER)
@JvmField
val WHITE_MINT_LEAF_LITTER = this.mintLitter("white_mint_leaf_litter", MapColor.SNOW)
@JvmField
val POTTED_WHITE_MINT = this.pottedPlant("potted_white_mint", WHITE_MINT_LEAF_LITTER)
@JvmField
val LOBAILA = this.create("lobaila", LobailaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val POMOHANA = this.create("pomohana", PomohanaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val PAUVILLEA = this.create("pauvillea",PauvilleaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val WISTENSU = this.create("wistensu", WistensuFlowerHeadBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY).lightLevel{11}))
@JvmField
val WISTENSU_VINE = this.create("wistensu_vine", WistensuFlowerVineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val ELECTRIC_SEED = this.terrainSeed("electric_seed", TerrainSeedBlock.TerrainSeedType.ELECTRIC)
@JvmField
val PSYCHIC_SEED = this.terrainSeed("psychic_seed", TerrainSeedBlock.TerrainSeedType.PSYCHIC)
@JvmField
val MISTY_SEED = this.terrainSeed("misty_seed", TerrainSeedBlock.TerrainSeedType.MISTY)
@JvmField
val GRASSY_SEED = this.terrainSeed("grassy_seed", TerrainSeedBlock.TerrainSeedType.GRASSY)
@JvmField
val VOLTACONIA = this.terrainPlant("voltaconia") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
@JvmField
val VERDROOT = this.terrainPlant("verdroot") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 5 else 0 }
@JvmField
val LUCIBELL = this.terrainPlant("lucibell") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
@JvmField
val MISTLEAF = this.create(
"mistleaf",
MistleafFlowerBlock {
if ((it.getValue(MistleafFlowerBlock.GLOWING) >= 1)) it.getValue(MistleafFlowerBlock.GLOWING) + 2 else 0
}
)
@JvmField
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(CobblemonSounds.HEARTY_GRAIN_BALE_SOUNDS)))
@JvmField
@@ -1033,6 +1103,147 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val YACHE_BERRY = this.berryBlock("yache")
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
@JvmField
val POTTED_AGUAV_BERRY = this.create("potted_aguav_berry", BlocksInvoker.createFlowerPotBlock(AGUAV_BERRY))
@JvmField
val POTTED_APICOT_BERRY = this.create("potted_apicot_berry", BlocksInvoker.createFlowerPotBlock(APICOT_BERRY))
@JvmField
val POTTED_ASPEAR_BERRY = this.create("potted_aspear_berry", BlocksInvoker.createFlowerPotBlock(ASPEAR_BERRY))
@JvmField
val POTTED_BABIRI_BERRY = this.create("potted_babiri_berry", BlocksInvoker.createFlowerPotBlock(BABIRI_BERRY))
@JvmField
val POTTED_BELUE_BERRY = this.create("potted_belue_berry", BlocksInvoker.createFlowerPotBlock(BELUE_BERRY))
@JvmField
val POTTED_BLUK_BERRY = this.create("potted_bluk_berry", BlocksInvoker.createFlowerPotBlock(BLUK_BERRY))
@JvmField
val POTTED_CHARTI_BERRY = this.create("potted_charti_berry", BlocksInvoker.createFlowerPotBlock(CHARTI_BERRY))
@JvmField
val POTTED_CHERI_BERRY = this.create("potted_cheri_berry", BlocksInvoker.createFlowerPotBlock(CHERI_BERRY))
@JvmField
val POTTED_CHESTO_BERRY = this.create("potted_chesto_berry", BlocksInvoker.createFlowerPotBlock(CHESTO_BERRY))
@JvmField
val POTTED_CHILAN_BERRY = this.create("potted_chilan_berry", BlocksInvoker.createFlowerPotBlock(CHILAN_BERRY))
@JvmField
val POTTED_CHOPLE_BERRY = this.create("potted_chople_berry", BlocksInvoker.createFlowerPotBlock(CHOPLE_BERRY))
@JvmField
val POTTED_COBA_BERRY = this.create("potted_coba_berry", BlocksInvoker.createFlowerPotBlock(COBA_BERRY))
@JvmField
val POTTED_COLBUR_BERRY = this.create("potted_colbur_berry", BlocksInvoker.createFlowerPotBlock(COLBUR_BERRY))
@JvmField
val POTTED_CORNN_BERRY = this.create("potted_cornn_berry", BlocksInvoker.createFlowerPotBlock(CORNN_BERRY))
@JvmField
val POTTED_CUSTAP_BERRY = this.create("potted_custap_berry", BlocksInvoker.createFlowerPotBlock(CUSTAP_BERRY))
@JvmField
val POTTED_DURIN_BERRY = this.create("potted_durin_berry", BlocksInvoker.createFlowerPotBlock(DURIN_BERRY))
@JvmField
val POTTED_EGGANT_BERRY = this.create("potted_eggant_berry", BlocksInvoker.createFlowerPotBlock(EGGANT_BERRY))
@JvmField
val POTTED_ENIGMA_BERRY = this.create("potted_enigma_berry", BlocksInvoker.createFlowerPotBlock(ENIGMA_BERRY))
@JvmField
val POTTED_FIGY_BERRY = this.create("potted_figy_berry", BlocksInvoker.createFlowerPotBlock(FIGY_BERRY))
@JvmField
val POTTED_GANLON_BERRY = this.create("potted_ganlon_berry", BlocksInvoker.createFlowerPotBlock(GANLON_BERRY))
@JvmField
val POTTED_GREPA_BERRY = this.create("potted_grepa_berry", BlocksInvoker.createFlowerPotBlock(GREPA_BERRY))
@JvmField
val POTTED_HABAN_BERRY = this.create("potted_haban_berry", BlocksInvoker.createFlowerPotBlock(HABAN_BERRY))
@JvmField
val POTTED_HONDEW_BERRY = this.create("potted_hondew_berry", BlocksInvoker.createFlowerPotBlock(HONDEW_BERRY))
@JvmField
val POTTED_HOPO_BERRY = this.create("potted_hopo_berry", BlocksInvoker.createFlowerPotBlock(HOPO_BERRY))
@JvmField
val POTTED_IAPAPA_BERRY = this.create("potted_iapapa_berry", BlocksInvoker.createFlowerPotBlock(IAPAPA_BERRY))
@JvmField
val POTTED_JABOCA_BERRY = this.create("potted_jaboca_berry", BlocksInvoker.createFlowerPotBlock(JABOCA_BERRY))
@JvmField
val POTTED_KASIB_BERRY = this.create("potted_kasib_berry", BlocksInvoker.createFlowerPotBlock(KASIB_BERRY))
@JvmField
val POTTED_KEBIA_BERRY = this.create("potted_kebia_berry", BlocksInvoker.createFlowerPotBlock(KEBIA_BERRY))
@JvmField
val POTTED_KEE_BERRY = this.create("potted_kee_berry", BlocksInvoker.createFlowerPotBlock(KEE_BERRY))
@JvmField
val POTTED_KELPSY_BERRY = this.create("potted_kelpsy_berry", BlocksInvoker.createFlowerPotBlock(KELPSY_BERRY))
@JvmField
val POTTED_LANSAT_BERRY = this.create("potted_lansat_berry", BlocksInvoker.createFlowerPotBlock(LANSAT_BERRY))
@JvmField
val POTTED_LEPPA_BERRY = this.create("potted_leppa_berry", BlocksInvoker.createFlowerPotBlock(LEPPA_BERRY))
@JvmField
val POTTED_LIECHI_BERRY = this.create("potted_liechi_berry", BlocksInvoker.createFlowerPotBlock(LIECHI_BERRY))
@JvmField
val POTTED_LUM_BERRY = this.create("potted_lum_berry", BlocksInvoker.createFlowerPotBlock(LUM_BERRY))
@JvmField
val POTTED_MAGO_BERRY = this.create("potted_mago_berry", BlocksInvoker.createFlowerPotBlock(MAGO_BERRY))
@JvmField
val POTTED_MAGOST_BERRY = this.create("potted_magost_berry", BlocksInvoker.createFlowerPotBlock(MAGOST_BERRY))
@JvmField
val POTTED_MARANGA_BERRY = this.create("potted_maranga_berry", BlocksInvoker.createFlowerPotBlock(MARANGA_BERRY))
@JvmField
val POTTED_MICLE_BERRY = this.create("potted_micle_berry", BlocksInvoker.createFlowerPotBlock(MICLE_BERRY))
@JvmField
val POTTED_NANAB_BERRY = this.create("potted_nanab_berry", BlocksInvoker.createFlowerPotBlock(NANAB_BERRY))
@JvmField
val POTTED_NOMEL_BERRY = this.create("potted_nomel_berry", BlocksInvoker.createFlowerPotBlock(NOMEL_BERRY))
@JvmField
val POTTED_OCCA_BERRY = this.create("potted_occa_berry", BlocksInvoker.createFlowerPotBlock(OCCA_BERRY))
@JvmField
val POTTED_ORAN_BERRY = this.create("potted_oran_berry", BlocksInvoker.createFlowerPotBlock(ORAN_BERRY))
@JvmField
val POTTED_PAMTRE_BERRY = this.create("potted_pamtre_berry", BlocksInvoker.createFlowerPotBlock(PAMTRE_BERRY))
@JvmField
val POTTED_PASSHO_BERRY = this.create("potted_passho_berry", BlocksInvoker.createFlowerPotBlock(PASSHO_BERRY))
@JvmField
val POTTED_PAYAPA_BERRY = this.create("potted_payapa_berry", BlocksInvoker.createFlowerPotBlock(PAYAPA_BERRY))
@JvmField
val POTTED_PECHA_BERRY = this.create("potted_pecha_berry", BlocksInvoker.createFlowerPotBlock(PECHA_BERRY))
@JvmField
val POTTED_PERSIM_BERRY = this.create("potted_persim_berry", BlocksInvoker.createFlowerPotBlock(PERSIM_BERRY))
@JvmField
val POTTED_PETAYA_BERRY = this.create("potted_petaya_berry", BlocksInvoker.createFlowerPotBlock(PETAYA_BERRY))
@JvmField
val POTTED_PINAP_BERRY = this.create("potted_pinap_berry", BlocksInvoker.createFlowerPotBlock(PINAP_BERRY))
@JvmField
val POTTED_POMEG_BERRY = this.create("potted_pomeg_berry", BlocksInvoker.createFlowerPotBlock(POMEG_BERRY))
@JvmField
val POTTED_QUALOT_BERRY = this.create("potted_qualot_berry", BlocksInvoker.createFlowerPotBlock(QUALOT_BERRY))
@JvmField
val POTTED_RABUTA_BERRY = this.create("potted_rabuta_berry", BlocksInvoker.createFlowerPotBlock(RABUTA_BERRY))
@JvmField
val POTTED_RAWST_BERRY = this.create("potted_rawst_berry", BlocksInvoker.createFlowerPotBlock(RAWST_BERRY))
@JvmField
val POTTED_RAZZ_BERRY = this.create("potted_razz_berry", BlocksInvoker.createFlowerPotBlock(RAZZ_BERRY))
@JvmField
val POTTED_RINDO_BERRY = this.create("potted_rindo_berry", BlocksInvoker.createFlowerPotBlock(RINDO_BERRY))
@JvmField
val POTTED_ROSELI_BERRY = this.create("potted_roseli_berry", BlocksInvoker.createFlowerPotBlock(ROSELI_BERRY))
@JvmField
val POTTED_ROWAP_BERRY = this.create("potted_rowap_berry", BlocksInvoker.createFlowerPotBlock(ROWAP_BERRY))
@JvmField
val POTTED_SALAC_BERRY = this.create("potted_salac_berry", BlocksInvoker.createFlowerPotBlock(SALAC_BERRY))
@JvmField
val POTTED_SHUCA_BERRY = this.create("potted_shuca_berry", BlocksInvoker.createFlowerPotBlock(SHUCA_BERRY))
@JvmField
val POTTED_SITRUS_BERRY = this.create("potted_sitrus_berry", BlocksInvoker.createFlowerPotBlock(SITRUS_BERRY))
@JvmField
val POTTED_SPELON_BERRY = this.create("potted_spelon_berry", BlocksInvoker.createFlowerPotBlock(SPELON_BERRY))
@JvmField
val POTTED_STARF_BERRY = this.create("potted_starf_berry", BlocksInvoker.createFlowerPotBlock(STARF_BERRY))
@JvmField
val POTTED_TAMATO_BERRY = this.create("potted_tamato_berry", BlocksInvoker.createFlowerPotBlock(TAMATO_BERRY))
@JvmField
val POTTED_TANGA_BERRY = this.create("potted_tanga_berry", BlocksInvoker.createFlowerPotBlock(TANGA_BERRY))
@JvmField
val POTTED_TOUGA_BERRY = this.create("potted_touga_berry", BlocksInvoker.createFlowerPotBlock(TOUGA_BERRY))
@JvmField
val POTTED_WACAN_BERRY = this.create("potted_wacan_berry", BlocksInvoker.createFlowerPotBlock(WACAN_BERRY))
@JvmField
val POTTED_WATMEL_BERRY = this.create("potted_watmel_berry", BlocksInvoker.createFlowerPotBlock(WATMEL_BERRY))
@JvmField
val POTTED_WEPEAR_BERRY = this.create("potted_wepear_berry", BlocksInvoker.createFlowerPotBlock(WEPEAR_BERRY))
@JvmField
val POTTED_WIKI_BERRY = this.create("potted_wiki_berry", BlocksInvoker.createFlowerPotBlock(WIKI_BERRY))
@JvmField
val POTTED_YACHE_BERRY = this.create("potted_yache_berry", BlocksInvoker.createFlowerPotBlock(YACHE_BERRY))
init {
/**
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
@@ -1134,4 +1345,48 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
}
fun typeGemClusters() = typeGemClusters.toMap()
private fun pottedPlant(name: String, flower: Block) = this.create(name, BlocksInvoker.createFlowerPotBlock(flower))
private fun mintLitter(name: String, color: MapColor) = this.create(
name,
MintLitterBlock(
BlockBehaviour.Properties.of()
.sound(CobblemonSounds.MINT_SOUNDS)
.mapColor(color)
.noOcclusion()
.noCollission()
.pushReaction(PushReaction.DESTROY)
)
)
private fun terrainSeed(name: String, type: TerrainSeedBlock.TerrainSeedType) = this.create(
name,
TerrainSeedBlock(
type,
BlockBehaviour.Properties.of()
.pushReaction(PushReaction.DESTROY)
.ignitedByLava()
.mapColor(MapColor.PLANT)
.offsetType(BlockBehaviour.OffsetType.XZ)
.noCollission()
.randomTicks()
.instabreak()
)
)
private fun terrainPlant(name: String, lightLevel: (BlockState) -> Int) = this.create(
name,
TerrainPlantBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.PLANT)
.noCollission()
.instabreak()
.sound(SoundType.GRASS)
.offsetType(BlockBehaviour.OffsetType.XZ)
.ignitedByLava()
.pushReaction(PushReaction.DESTROY)
.lightLevel(lightLevel)
)
)
}
@@ -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
*
@@ -477,8 +477,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@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")
@JvmField val MAGMARIZER = noSettingsItem("magmarizer")
@JvmField val ELECTIRIZER = itemNameBlockItem("electirizer", CobblemonBlocks.ELECTIRIZER)
@JvmField val MAGMARIZER = itemNameBlockItem("magmarizer", CobblemonBlocks.MAGMARIZER)
@JvmField val OVAL_STONE = noSettingsItem("oval_stone")
@JvmField val PROTECTOR = noSettingsItem("protector")
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
@@ -711,7 +711,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val PEP_UP_FLOWER = compostableBlockItem("pep_up_flower", CobblemonBlocks.PEP_UP_FLOWER)
@JvmField
val MEDICINAL_BREW = noSettingsItem("medicinal_brew")
val MEDICINAL_BREW = itemNameBlockItem("medicinal_brew", CobblemonBlocks.MEDICINAL_BREW)
@JvmField
val REMEDY = compostableItem("remedy", RemedyItem(RemedyItem.NORMAL), 0.65f)
@JvmField
@@ -719,7 +719,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 1f)
@JvmField
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16)), CobblemonBlocks.MOOMOO_MILK))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
@@ -748,27 +748,27 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
@JvmField
val RED_MINT_LEAF = mintLeaf("red", MintLeafItem(MintType.RED))
val RED_MINT_LEAF = compostableItemNameBlockItem("red_mint_leaf", CobblemonBlocks.RED_MINT_LEAF_LITTER, 0.50f)
@JvmField
val BLUE_MINT_SEEDS = mintSeed("blue", MintType.BLUE.getCropBlock())
@JvmField
val BLUE_MINT_LEAF = mintLeaf("blue", MintLeafItem(MintType.BLUE))
val BLUE_MINT_LEAF = compostableItemNameBlockItem("blue_mint_leaf", CobblemonBlocks.BLUE_MINT_LEAF_LITTER, 0.50f)
@JvmField
val CYAN_MINT_SEEDS = mintSeed("cyan", MintType.CYAN.getCropBlock())
@JvmField
val CYAN_MINT_LEAF = mintLeaf("cyan", MintLeafItem(MintType.CYAN))
val CYAN_MINT_LEAF = compostableItemNameBlockItem("cyan_mint_leaf", CobblemonBlocks.CYAN_MINT_LEAF_LITTER, 0.50f)
@JvmField
val PINK_MINT_SEEDS = mintSeed("pink", MintType.PINK.getCropBlock())
@JvmField
val PINK_MINT_LEAF = mintLeaf("pink", MintLeafItem(MintType.PINK))
val PINK_MINT_LEAF = compostableItemNameBlockItem("pink_mint_leaf", CobblemonBlocks.PINK_MINT_LEAF_LITTER, 0.50f)
@JvmField
val GREEN_MINT_SEEDS = mintSeed("green", MintType.GREEN.getCropBlock())
@JvmField
val GREEN_MINT_LEAF = mintLeaf("green", MintLeafItem(MintType.GREEN))
val GREEN_MINT_LEAF = compostableItemNameBlockItem("green_mint_leaf", CobblemonBlocks.GREEN_MINT_LEAF_LITTER, 0.50f)
@JvmField
val WHITE_MINT_SEEDS = mintSeed("white", MintType.WHITE.getCropBlock())
@JvmField
val WHITE_MINT_LEAF = mintLeaf("white", MintLeafItem(MintType.WHITE))
val WHITE_MINT_LEAF = compostableItemNameBlockItem("white_mint_leaf", CobblemonBlocks.WHITE_MINT_LEAF_LITTER, 0.50f)
val mints = mutableMapOf<String, MintItem>()
@@ -969,7 +969,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val EJECT_PACK = heldItem("eject_pack")
@JvmField
val ELECTRIC_SEED = heldItem("electric_seed")
val ELECTRIC_SEED = itemNameBlockItem("electric_seed", CobblemonBlocks.ELECTRIC_SEED)
@JvmField
val EVERSTONE = heldItem("everstone")
@JvmField
@@ -985,7 +985,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val FOCUS_SASH = heldItem("focus_sash")
@JvmField
val GRASSY_SEED = heldItem("grassy_seed")
val GRASSY_SEED = itemNameBlockItem("grassy_seed", CobblemonBlocks.GRASSY_SEED)
@JvmField
val GRIP_CLAW = heldItem("grip_claw")
@JvmField
@@ -1033,7 +1033,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POWER_WEIGHT = heldItem("power_weight")
@JvmField
val PSYCHIC_SEED = heldItem("psychic_seed")
val PSYCHIC_SEED = itemNameBlockItem("psychic_seed", CobblemonBlocks.PSYCHIC_SEED)
@JvmField
val PROTECTIVE_PADS = heldItem("protective_pads")
@JvmField
@@ -1057,7 +1057,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SILVER_POWDER = heldItem("silver_powder")
@JvmField
val SOFT_SAND = heldItem("soft_sand")
val SOFT_SAND = itemNameBlockItem("soft_sand", CobblemonBlocks.SOFT_SAND)
@JvmField
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
@JvmField
@@ -1065,7 +1065,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val STICKY_BARB = heldItem("sticky_barb")
@JvmField
val TERRAIN_EXTENDER = heldItem("terrain_extender")
val TERRAIN_EXTENDER = itemNameBlockItem("terrain_extender", CobblemonBlocks.TERRAIN_EXTENDER)
@JvmField
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
@JvmField
@@ -1085,13 +1085,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MIRROR_HERB = compostableHeldItem("mirror_herb", null, 0.85F)
@JvmField
val MISTY_SEED = heldItem("misty_seed")
val MISTY_SEED = itemNameBlockItem("misty_seed", CobblemonBlocks.MISTY_SEED)
@JvmField
val POWER_HERB = compostableHeldItem("power_herb", null, 0.85F)
@JvmField
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
@JvmField
val BRIGHT_POWDER = itemNameBlockItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
val BRIGHT_POWDER = glowItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
@JvmField
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
@JvmField
@@ -1103,7 +1103,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
@JvmField
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
val ICY_ROCK = heldItem("icy_rock")
// Mulch
@JvmField
@@ -1558,6 +1558,25 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
// Plants, Flora, and Flowers
@JvmField
val LOBAILA = compostableBlockItem("lobaila", CobblemonBlocks.LOBAILA, 0.65f)
@JvmField
val POMOHANA = compostableBlockItem("pomohana", CobblemonBlocks.POMOHANA, 0.3f)
@JvmField
val PAUVILLEA = compostableBlockItem("pauvillea", CobblemonBlocks.PAUVILLEA, 0.65f)
@JvmField
val WISTENSU = compostableBlockItem("wistensu", CobblemonBlocks.WISTENSU, 0.5f)
@JvmField
val VOLTACONIA = compostableBlockItem("voltaconia", CobblemonBlocks.VOLTACONIA, 0.85f)
@JvmField
val VERDROOT = compostableBlockItem("verdroot", CobblemonBlocks.VERDROOT, 0.85f)
@JvmField
val MISTLEAF = compostableBlockItem("mistleaf", CobblemonBlocks.MISTLEAF, 0.85f)
@JvmField
val LUCIBELL = compostableBlockItem("lucibell", CobblemonBlocks.LUCIBELL, 0.85f)
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
private fun itemNameBlockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): ItemNameBlockItem = this.create(name, ItemNameBlockItem(block, Item.Properties().rarity(rarity)))
@@ -1760,4 +1779,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
}
private fun glowItem(name: String, block: Block): GlowItem = this.create(name, GlowItem(block, Item.Properties()))
}
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
import com.cobblemon.mod.common.client.net.debug.OpenSpeedTunerHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
@@ -83,6 +84,7 @@ import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallba
import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.debug.OpenSpeedTunerPacket
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
@@ -173,6 +175,9 @@ import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCa
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyMoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyPokemonMoveSelectedPacket
import com.cobblemon.mod.common.net.messages.server.debug.OpenRidingStatsDebugGUIPacket
import com.cobblemon.mod.common.net.messages.server.debug.RequestOpenSpeedTunerPacket
import com.cobblemon.mod.common.net.messages.server.debug.SetSpeedTunerSprintSpeedPacket
import com.cobblemon.mod.common.net.messages.server.debug.SetSpeedTunerWalkSpeedPacket
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingSettingsPacket
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatRangePacket
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatsPacket
@@ -231,6 +236,9 @@ import com.cobblemon.mod.common.net.serverhandling.callback.party.PartySelectCan
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyMoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyPokemonMoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.debug.RequestOpenRidingStatsDebugGUIHandler
import com.cobblemon.mod.common.net.serverhandling.debug.RequestOpenSpeedTunerHandler
import com.cobblemon.mod.common.net.serverhandling.debug.SetSpeedTunerSprintSpeedHandler
import com.cobblemon.mod.common.net.serverhandling.debug.SetSpeedTunerWalkSpeedHandler
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingSettingsHandler
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatRangeHandler
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatsHandler
@@ -425,6 +433,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()))
@@ -516,6 +525,7 @@ object CobblemonNetwork {
// Debug
list.add(PacketRegisterInfo(OpenRidingStatsDebugGUIPacket.ID, OpenRidingStatsDebugGUIPacket::decode, OpenRidingStatsDebugGUIHandler))
list.add(PacketRegisterInfo(OpenSpeedTunerPacket.ID, OpenSpeedTunerPacket::decode, OpenSpeedTunerHandler))
return list
@@ -648,6 +658,9 @@ object CobblemonNetwork {
// Debug / cheats
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
list.add(PacketRegisterInfo(RequestOpenSpeedTunerPacket.ID, RequestOpenSpeedTunerPacket::decode, RequestOpenSpeedTunerHandler))
list.add(PacketRegisterInfo(SetSpeedTunerSprintSpeedPacket.ID, SetSpeedTunerSprintSpeedPacket::decode, SetSpeedTunerSprintSpeedHandler))
list.add(PacketRegisterInfo(SetSpeedTunerWalkSpeedPacket.ID, SetSpeedTunerWalkSpeedPacket::decode, SetSpeedTunerWalkSpeedHandler))
return list
@@ -148,6 +148,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val MONITOR_GLITCHING = this.create("block.monitor.glitching")
@JvmField
val BRIGHT_POWDER_USE = this.create("block.bright_powder.use")
@JvmField
val FOSSIL_MACHINE_ACTIVATE = this.create("block.fossil_machine.activate")
@JvmField
@@ -657,5 +660,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)))
}
@@ -22,17 +22,22 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType
class MoveToOwnerTaskConfig : SingleTaskConfig {
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
var completionRange: ExpressionOrEntityVariable = Either.left("4.0".asExpression())
// Gap between the pokemon's hitbox and the owner's where it stops
var completionRange: ExpressionOrEntityVariable = Either.left("2.0".asExpression())
var speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
var teleportDistance: ExpressionOrEntityVariable = Either.left("24.0".asExpression())
var maxDistance: ExpressionOrEntityVariable = Either.left("14.0".asExpression())
// Gap between the pokemon's hitbox and the owner's before it sprints to catch up. Past maxDistance so short
// catch ups are walked
var sprintDistance: ExpressionOrEntityVariable = Either.left("14.0".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition,
completionRange,
speedMultiplier,
teleportDistance,
maxDistance
maxDistance,
sprintDistance
).asVariables()
override fun createTask(
@@ -49,7 +54,8 @@ class MoveToOwnerTaskConfig : SingleTaskConfig {
completionRange = completionRange.asExpression(),
speedMultiplier = speedMultiplier.asExpression(),
teleportDistance = teleportDistance.asExpression(),
maxDistance = maxDistance.asExpression()
maxDistance = maxDistance.asExpression(),
sprintDistance = sprintDistance.asExpression()
),
PokemonEntity::class.java
)
@@ -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)
}
}
}
@@ -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()) }
@@ -54,10 +54,15 @@ object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity
}
}
functions["is_sprinting"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["sprinting"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
// Ridden pokemon sprint from the ride state, otherwise from the AI
if (!pokemonEntity.hasControllingPassenger()) {
DoubleValue(pokemonEntity.isPokemonSprinting)
} else {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["sprinting"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
}
functions["is_drifting"] = {
@@ -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
}
@@ -151,6 +151,7 @@ object CobblemonPermissions {
@JvmStatic
val USE_RIDING_STATS_DEBUG = this.create("useridingstatsdebug", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val USE_SPEED_TUNER = this.create("usespeedtuner", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
fun all(): Iterable<Permission> = this.permissions
@@ -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)
}
@@ -18,50 +18,49 @@ import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.sign
import kotlin.math.sin
/**
* Class to handle the updating and containment of ride data used in animation.
* Class to handle the updating and holding of motion data used in animation. Motion values accumulate for every
* pokemon on the client, some only while ridden.
*
* @author Jackowes
* @since April 7th, 2025
*/
class RidingAnimationData(val ride: PokemonEntity)
class DynamicAnimationData(val ride: PokemonEntity)
{
companion object {
// Blend time for motion values, they catch up within 1.5x this. Longer is smoother but lags more
const val MOTION_BLEND_SECONDS = 0.8F
// Rider input comes straight from key presses so it catches up quicker
const val INPUT_BLEND_SECONDS = 0.3F
}
var prevOrientation: Matrix3f = Matrix3f()
var prevRot: Vec3 = Vec3.ZERO
var velocitySpring: Vec3Spring = Vec3Spring()
var rotSpring: Vec3Spring = Vec3Spring()
var rotDeltaSpring: Vec3Spring = Vec3Spring()
var localVelocitySpring: Vec3Spring = Vec3Spring()
var driverInputSpring: Vec3Spring = Vec3Spring()
var diveSpring: Vec3Spring = Vec3Spring()
var velocitySpring = Vec3Ease(MOTION_BLEND_SECONDS)
var rotSpring = Vec3Ease(MOTION_BLEND_SECONDS, isAngle = true)
var rotDeltaSpring = Vec3Ease(MOTION_BLEND_SECONDS)
var localVelocitySpring = Vec3Ease(MOTION_BLEND_SECONDS)
var driverInputSpring = Vec3Ease(INPUT_BLEND_SECONDS)
var diveSpring = Vec3Ease(MOTION_BLEND_SECONDS)
private var initialized = false
private var dismounted = false
/** Called on dismount. Motion keeps easing on its own, only the ride orientation needs to start fresh. */
fun clear() {
prevOrientation = Matrix3f()
prevRot = Vec3.ZERO
velocitySpring = Vec3Spring()
rotSpring = Vec3Spring()
rotDeltaSpring = Vec3Spring()
localVelocitySpring = Vec3Spring()
driverInputSpring = Vec3Spring()
diveSpring = Vec3Spring()
dismounted = true
}
fun update() {
val activeRide = ride.hasControllingPassenger()
if (!activeRide) return
var currRot = Vec3.ZERO
var angDelta = Vec3.ZERO
val stiffness = 90.0
val damping = 18.0
// Update velocity first
val currentVelocity = Vec3(
ride.x - ride.xOld,
@@ -69,7 +68,7 @@ class RidingAnimationData(val ride: PokemonEntity)
ride.z - ride.zOld
)
val r = ride.ifRidingAvailableSupply(false) { behaviour, settings, state ->
val r = activeRide && ride.ifRidingAvailableSupply(false) { behaviour, settings, state ->
behaviour.shouldRoll(settings, state, ride);
}
if( r ) {
@@ -118,22 +117,39 @@ class RidingAnimationData(val ride: PokemonEntity)
val localVelocityF = Matrix3f(orientation).transpose().transform(velocityWorldF)
val localVelocity = Vec3(localVelocityF.x.toDouble(), localVelocityF.y.toDouble(), localVelocityF.z.toDouble())
localVelocitySpring.update(localVelocity, stiffness, damping)
localVelocitySpring.update(localVelocity)
// Update previous values
prevOrientation.set(currOrientation)
} else {
val currPitch = ride.xRot
// Pitch only means something while ridden
val currPitch = if (activeRide) ride.xRot else 0.0f
val currYaw = ride.yRot
val currRoll = 0.0f
currRot = Vec3(currPitch.toDouble(), currYaw.toDouble(), currRoll.toDouble())
// Start from where the pokemon already is instead of turning in from zero
if (!initialized) {
prevRot = currRot
rotSpring.snap(currRot)
initialized = true
}
// measure turning from here rather than as one big turn
if (dismounted) {
prevRot = currRot
dismounted = false
}
//Scale rotation delta by 20 to represent degree per second instead of tick
angDelta = Vec3(
(currRot.x - prevRot.x),
Mth.degreesDifference(currRot.y.toFloat(), prevRot.y.toFloat()).toDouble(),
Mth.degreesDifference(currRot.z.toFloat(), prevRot.z.toFloat()).toDouble()
).scale(20.0)
// Unridden turning comes straight from the heading ease, it's already smooth
if (!activeRide) {
angDelta = Vec3(angDelta.x, ride.heading.yaw.velocity().toDouble() * -0.5, angDelta.z)
}
/******************************************************
* Calculate ride local velocity
@@ -147,7 +163,7 @@ class RidingAnimationData(val ride: PokemonEntity)
val localVelocityF = Matrix3f(orientationNonRollable).transpose().transform(velocityWorldF)
val localVelocity = Vec3(localVelocityF.x.toDouble(), localVelocityF.y.toDouble(), localVelocityF.z.toDouble())
localVelocitySpring.update(localVelocity, stiffness, damping)
localVelocitySpring.update(localVelocity)
}
/******************************************************
@@ -159,14 +175,14 @@ class RidingAnimationData(val ride: PokemonEntity)
driver != null && driver.isShiftKeyDown -> -1.0
else -> 0.0
}
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
driverInputSpring.update(input, stiffness * 2, damping)
rotDeltaSpring.update(angDelta, stiffness, damping)
velocitySpring.update(currentVelocity, stiffness, damping)
rotSpring.update(currRot, stiffness, damping)
val input = if(driver != null && activeRide) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
driverInputSpring.update(input)
if (activeRide) rotDeltaSpring.update(angDelta) else rotDeltaSpring.follow(angDelta)
velocitySpring.update(currentVelocity)
rotSpring.update(currRot)
val diving = abs(max(sin(currRot.x.toRadians()),0f))
diveSpring.update(Vec3(0.0,diving.toDouble(),0.0), stiffness, damping)
diveSpring.update(Vec3(0.0,diving.toDouble(),0.0))
prevRot = currRot
}
}
@@ -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)
}
@@ -0,0 +1,109 @@
/*
* 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 com.cobblemon.mod.common.util.math.InertialVals
import com.cobblemon.mod.common.util.math.smoothstep
import com.cobblemon.mod.common.util.math.smoothstepDt
import com.cobblemon.mod.common.util.math.smoothstepSolve
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
/**
* Eases a Vec3 towards a target with inertial easing, see SpringUtils and https://theorangeduck.com/page/inertial-easing
* When the target changes each component keeps its current velocity
*
* With [isAngle] the components are treated as degrees and always ease the short way round.
*/
class Vec3Ease(
private val blendTime: Float,
private val isAngle: Boolean = false
) {
companion object {
const val SECONDS_PER_TICK = 0.05F
}
var value: Vec3 = Vec3.ZERO
private set
private val axes = Array(3) { InertialVals() }
// Last tick's value while following a value that's already smooth, null while easing
private var followedFrom: Vec3? = null
// How far along the curve one tick is
private val step = SECONDS_PER_TICK / blendTime
private var target = Vec3.ZERO
/** Moves one tick towards [newTarget] */
fun update(newTarget: Vec3) {
followedFrom = null
val current = doubleArrayOf(value.x, value.y, value.z)
val oldGoal = doubleArrayOf(target.x, target.y, target.z)
val goal = doubleArrayOf(newTarget.x, newTarget.y, newTarget.z)
for (i in 0..2) {
val vals = axes[i]
if (goal[i] != oldGoal[i]) {
val difference = if (isAngle) Mth.wrapDegrees(goal[i] - current[i]) else goal[i] - current[i]
// Velocity along the current curve, carried into the new one so there's no sudden change
smoothstepSolve(vals, difference.toFloat(), smoothstepDt(vals.t, vals.s))
vals.offset = current[i].toFloat() - smoothstep(vals.t, vals.s)
}
vals.t += step
current[i] = (vals.offset + smoothstep(vals.t, vals.s)).toDouble()
if (isAngle) {
// Keep turning angles in range so they don't grow forever
val turns = current[i] - Mth.wrapDegrees(current[i])
if (turns != 0.0) {
vals.offset -= turns.toFloat()
current[i] -= turns
}
}
}
target = newTarget
value = Vec3(current[0], current[1], current[2])
}
/** Takes [to] as is for something already smooth. Still blends from last tick's value between ticks */
fun follow(to: Vec3) {
val from = value
snap(to)
followedFrom = from
}
/** Jumps straight to [to] with no easing */
fun snap(to: Vec3) {
followedFrom = null
val components = floatArrayOf(to.x.toFloat(), to.y.toFloat(), to.z.toFloat())
for (i in 0..2) {
axes[i].t = 1F
axes[i].s = 0F
axes[i].offset = components[i]
}
target = to
value = to
}
/** Value between last tick and this one read straight off the curve. [lookBackTick] isn't used anymore */
fun getInterpolated(partialTick: Double, @Suppress("UNUSED_PARAMETER") lookBackTick: Int = 0): Vec3 {
followedFrom?.let { from ->
if (!isAngle) return from.add(value.subtract(from).scale(partialTick))
// Short way around for angles
return Vec3(
Mth.wrapDegrees(from.x + Mth.wrapDegrees(value.x - from.x) * partialTick),
Mth.wrapDegrees(from.y + Mth.wrapDegrees(value.y - from.y) * partialTick),
Mth.wrapDegrees(from.z + Mth.wrapDegrees(value.z - from.z) * partialTick)
)
}
val back = step * (1 - partialTick.toFloat())
val x = axes[0].let { it.offset + smoothstep(it.t - back, it.s) }.toDouble()
val y = axes[1].let { it.offset + smoothstep(it.t - back, it.s) }.toDouble()
val z = axes[2].let { it.offset + smoothstep(it.t - back, it.s) }.toDouble()
// Can land just past ±180 partway through a tick
return if (isAngle) Vec3(Mth.wrapDegrees(x), Mth.wrapDegrees(y), Mth.wrapDegrees(z)) else Vec3(x, y, z)
}
}
@@ -1,68 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.world.phys.Vec3
import kotlin.math.min
/**
* A spring-based smoother for Vec3 values, designed for applying damped spring motion to riding data.
* Stores the previous and current values, along with a history buffer for lookback-based interpolation.
*
* @author Jackowes
* @since March 27th, 2025
*/
class Vec3Spring(
var value: Vec3 = Vec3.ZERO,
var velocity: Vec3 = Vec3.ZERO
) {
val history = ArrayDeque<Vec3>()
//Hold up to a seconds worth of values
val lookBackSize = 20
fun update(target: Vec3, stiffness: Double, damping: Double) {
//Hooke's Law f = kx where:
//f is force
//k is the spring constant or stiffness of this spring
//x is the distance from rest and in this case the difference between the most recent
//rotation delta ( the target ) and the springs current value for delta rot
val force = target.subtract(value).scale(stiffness)
//Now calculate acceleration and apply damping
val accel = force.subtract(velocity.scale(damping))
velocity = velocity.add(accel.scale(1.0/20.0))
value = value.add(velocity.scale(1.0/20.0))
history.addFirst(value)
//Adding one since if we want to check max look back point it needs something to interp with.
if( history.size > lookBackSize + 1) {
history.removeLast()
}
}
fun getInterpolated(partialTick: Double, lookBackTick: Int): Vec3 {
if (history.size < 2) {
return value // or Vec3.ZERO if you prefer
}
val tick = min(lookBackTick, history.size - 2)
var ret = Vec3.ZERO
for( i in 0..tick) {
val older = history.get(i + 1).scale(1.0)
val newer = history.get(i).scale(1.0)
ret = ret.add(older.add(newer.subtract(older).scale(partialTick)))
}
return ret.scale(1.0/(tick+1))
}
}
@@ -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()}")
}
}
@@ -34,6 +34,7 @@ class ElementalType(
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), ""),
val typeGem: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, showdownId + "_gem"),
val order: Int = 1000
) : ShowdownIdentifiable {
override fun showdownId(): String {
@@ -25,7 +25,8 @@ object ElementalTypes {
hue = 0xE8E8DA,
textureXMultiplier = 0,
primaryColor = 0x8FA689,
secondaryColor = 0xFFFBE2
secondaryColor = 0xFFFBE2,
order = 0
)
@JvmField
@@ -35,7 +36,8 @@ object ElementalTypes {
hue = 0xFF6E21,
textureXMultiplier = 1,
primaryColor = 0xF92D2D,
secondaryColor = 0xF99B1D
secondaryColor = 0xF99B1D,
order = 1
)
@JvmField
@@ -45,7 +47,8 @@ object ElementalTypes {
hue = 0x3FA5FF,
textureXMultiplier = 2,
primaryColor = 0x377AF6,
secondaryColor = 0x50DDF9
secondaryColor = 0x50DDF9,
order = 2
)
@JvmField
@@ -55,7 +58,8 @@ object ElementalTypes {
hue = 0x62D14F,
textureXMultiplier = 3,
primaryColor = 0x0EAE95,
secondaryColor = 0xC6F554
secondaryColor = 0xC6F554,
order = 3
)
@JvmField
@@ -65,7 +69,8 @@ object ElementalTypes {
hue = 0xFFD314,
textureXMultiplier = 4,
primaryColor = 0xF7AB19,
secondaryColor = 0xFFF257
secondaryColor = 0xFFF257,
order = 4
)
@JvmField
@@ -75,7 +80,8 @@ object ElementalTypes {
hue = 0x54F2F2,
textureXMultiplier = 5,
primaryColor = 0x41BAED,
secondaryColor = 0x91F4F9
secondaryColor = 0x91F4F9,
order = 5
)
@JvmField
@@ -85,7 +91,8 @@ object ElementalTypes {
hue = 0xEF565D,
textureXMultiplier = 6,
primaryColor = 0xC8356B,
secondaryColor = 0xF36A3F
secondaryColor = 0xF36A3F,
order = 6
)
@JvmField
@@ -95,7 +102,8 @@ object ElementalTypes {
hue = 0xD651FF,
textureXMultiplier = 7,
primaryColor = 0x7B41DD,
secondaryColor = 0xE568F4
secondaryColor = 0xE568F4,
order = 7
)
@JvmField
@@ -105,7 +113,8 @@ object ElementalTypes {
hue = 0xF4A453,
textureXMultiplier = 8,
primaryColor = 0xE08835,
secondaryColor = 0xFFD057
secondaryColor = 0xFFD057,
order = 8
)
@JvmField
@@ -115,7 +124,8 @@ object ElementalTypes {
hue = 0xB8B2FF,
textureXMultiplier = 9,
primaryColor = 0x6083F6,
secondaryColor = 0xD5CCFA
secondaryColor = 0xD5CCFA,
order = 9
)
@JvmField
@@ -125,7 +135,8 @@ object ElementalTypes {
hue = 0xFF5E9E,
textureXMultiplier = 10,
primaryColor = 0xE553CE,
secondaryColor = 0xF69DD5
secondaryColor = 0xF69DD5,
order = 10
)
@JvmField
@@ -135,7 +146,8 @@ object ElementalTypes {
hue = 0xD3D319,
textureXMultiplier = 11,
primaryColor = 0x9FBE1B,
secondaryColor = 0xE9FB5B
secondaryColor = 0xE9FB5B,
order = 11
)
@JvmField
@@ -145,7 +157,8 @@ object ElementalTypes {
hue = 0xB7A16E,
textureXMultiplier = 12,
primaryColor = 0x9F814A,
secondaryColor = 0xEECB7A
secondaryColor = 0xEECB7A,
order = 12
)
@JvmField
@@ -155,7 +168,8 @@ object ElementalTypes {
hue = 0x9C80F7,
textureXMultiplier = 13,
primaryColor = 0x4F57DD,
secondaryColor = 0xC182F1
secondaryColor = 0xC182F1,
order = 13
)
@JvmField
@@ -165,7 +179,8 @@ object ElementalTypes {
hue = 0x7580FF,
textureXMultiplier = 14,
primaryColor = 0x4E68E2,
secondaryColor = 0x47C7F7
secondaryColor = 0x47C7F7,
order = 14
)
@JvmField
@@ -175,7 +190,8 @@ object ElementalTypes {
hue = 0x587DA0,
textureXMultiplier = 15,
primaryColor = 0x465B99,
secondaryColor = 0x689EBF
secondaryColor = 0x689EBF,
order = 15
)
@JvmField
@@ -185,7 +201,8 @@ object ElementalTypes {
hue = 0xABD1F4,
textureXMultiplier = 16,
primaryColor = 0x628CB9,
secondaryColor = 0xD6F7F3
secondaryColor = 0xD6F7F3,
order = 16
)
@JvmField
@@ -195,18 +212,21 @@ object ElementalTypes {
hue = 0xFF7FE5,
textureXMultiplier = 17,
primaryColor = 0xE2678C,
secondaryColor = 0xF5A7B7
secondaryColor = 0xF5A7B7,
order = 17
)
@JvmStatic
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int): ElementalType {
@JvmOverloads
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int, order: Int = 1000): ElementalType {
return ElementalType(
name = name,
displayName = displayName,
hue = hue,
textureXMultiplier = textureXMultiplier,
primaryColor = primaryColor,
secondaryColor = secondaryColor
secondaryColor = secondaryColor,
order = order
).also {
allTypes.add(it)
}
@@ -48,7 +48,8 @@ class DecorativeItemBlock(settings: Properties, val type: DecorativeItemBlockTyp
NEVERMELT,
SCOPE,
WIDE,
ZOOM;
ZOOM,
IZER;
override fun getSerializedName(): String = this.name.lowercase()
@@ -76,6 +77,7 @@ class DecorativeItemBlock(settings: Properties, val type: DecorativeItemBlockTyp
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)
val IZER_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.375, 0.75)
fun getShape(type: DecorativeItemBlockType): VoxelShape {
return when (type) {
@@ -87,6 +89,7 @@ class DecorativeItemBlock(settings: Properties, val type: DecorativeItemBlockTyp
DecorativeItemBlockType.SCOPE -> SCOPE_HITBOX
DecorativeItemBlockType.WIDE -> WIDE_HITBOX
DecorativeItemBlockType.ZOOM -> ZOOM_HITBOX
DecorativeItemBlockType.IZER -> IZER_HITBOX
DecorativeItemBlockType.FLATTENED_10x4 -> SMOOSHED4_HITBOX
DecorativeItemBlockType.FLATTENED_8x4 -> FLAT4_HITBOX
DecorativeItemBlockType.FLATTENED_8x6 -> FLAT6_HITBOX
@@ -103,15 +103,15 @@ class MintBlock(private val mintType: MintType, settings: Properties) : CropBloc
GREEN -> CobblemonItems.GREEN_MINT_SEEDS
WHITE -> CobblemonItems.WHITE_MINT_SEEDS
}
fun getLeaf(): MintLeafItem = when (this) {
RED -> CobblemonItems.RED_MINT_LEAF
BLUE -> CobblemonItems.BLUE_MINT_LEAF
CYAN -> CobblemonItems.CYAN_MINT_LEAF
PINK -> CobblemonItems.PINK_MINT_LEAF
GREEN -> CobblemonItems.GREEN_MINT_LEAF
WHITE -> CobblemonItems.WHITE_MINT_LEAF
}
// These don't seem to get called to anywhere besides the direct item, and since I'm trying to do funny stuff there, just commenting this out
// fun getLeaf(): MintLeafItem = when (this) {
// RED -> CobblemonItems.RED_MINT_LEAF
// BLUE -> CobblemonItems.BLUE_MINT_LEAF
// CYAN -> CobblemonItems.CYAN_MINT_LEAF
// PINK -> CobblemonItems.PINK_MINT_LEAF
// GREEN -> CobblemonItems.GREEN_MINT_LEAF
// WHITE -> CobblemonItems.WHITE_MINT_LEAF
// }
fun getCropBlock(): MintBlock = when (this) {
RED -> CobblemonBlocks.RED_MINT
@@ -45,7 +45,9 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
IRON_BALL,
THROAT_SPRAY,
UPGRADE,
DICE;
DICE,
GLASS_BOTTLE,
EXTENDER;
override fun getSerializedName(): String = this.name.lowercase()
@@ -133,6 +135,18 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
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)
),
StackableItemBlockType.GLASS_BOTTLE to listOf(
Shapes.box(0.375, 0.0, 0.375, 0.625, 0.375, 0.625),
Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.375, 0.6875),
Shapes.box(0.125, 0.0, 0.125, 0.8125, 0.375, 0.875),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875)
),
StackableItemBlockType.EXTENDER to listOf(
Shapes.box(0.25, 0.0, 0.375, 0.625, 0.5625, 0.625),
Shapes.box(0.125, 0.0, 0.25, 0.875, 0.5625, 0.75),
Shapes.box(0.0625, 0.0, 0.0625, 0.875, 0.5625, 0.875),
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.5625, 0.9375)
)
)
@@ -0,0 +1,59 @@
/*
* 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 net.minecraft.core.BlockPos
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.TallFlowerBlock
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.WATERLOGGED
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
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 LobailaFlowerBlock(properties: Properties?) : TallFlowerBlock(properties), SimpleWaterloggedBlock {
init {
registerDefaultState(stateDefinition.any()
.setValue(HALF, DoubleBlockHalf.LOWER)
.setValue(WATERLOGGED, false))
}
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(HALF, WATERLOGGED)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
return this.defaultBlockState()
.setValue(
WATERLOGGED,
fluidState.type === Fluids.WATER
)
}
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
companion object {
private val AABB = Shapes.box(0.125, 0.0, 0.125, 0.875, 1.0, 0.875)
}
}
@@ -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.decor
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.block.entity.MistleafFlowerBlockEntity
import net.minecraft.core.BlockPos
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.TallFlowerBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class MistleafFlowerBlock(lightLevel: (BlockState) -> Int) :
EntityBlock,
TallFlowerBlock(
Properties.of()
.mapColor(MapColor.PLANT)
.noCollission()
.instabreak()
.sound(SoundType.GRASS)
.offsetType(OffsetType.XZ)
.ignitedByLava()
.pushReaction(PushReaction.DESTROY)
.lightLevel(lightLevel)
) {
init {
registerDefaultState(stateDefinition.any()
.setValue(HALF, DoubleBlockHalf.LOWER)
.setValue(GLOWING, 0))
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HALF, GLOWING)
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
return false
}
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? {
return MistleafFlowerBlockEntity(pos, state)
}
override fun <T : BlockEntity?> getTicker(
level: Level,
state: BlockState,
blockEntityType: BlockEntityType<T?>
): BlockEntityTicker<T?>? {
return if (level.isClientSide) null else BaseEntityBlock.createTickerHelper(blockEntityType as BlockEntityType<*>,
CobblemonBlockEntities.MISTLEAF_FLOWER, MistleafFlowerBlockEntity::serverTick) as BlockEntityTicker<T?>?
}
companion object {
private val AABB = Shapes.box(0.125, 0.0, 0.125, 0.875, 1.0, 0.875)
val GLOWING = IntegerProperty.create("glowing", 0, 8)
}
override fun hasAnalogOutputSignal(state: BlockState): Boolean { return true }
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
return state.getValue(GLOWING) as Int
}
}
@@ -0,0 +1,104 @@
/*
* 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.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.item.ItemStack
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.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.BonemealableBlock
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class PauvilleaFlowerBlock(properties: Properties?) : DirectionalBlock(properties), BonemealableBlock {
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.DOWN))
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
}
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
var blockState = defaultBlockState()
val worldView = ctx.level
val blockPos = ctx.clickedPos
blockState = blockState.setValue(FACING, ctx.clickedFace) as BlockState
if (blockState.canSurvive(worldView, blockPos)) {
return blockState
}
return null
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, level: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
val facing = state.getValue(FACING)
if (direction == facing.opposite && !canSurvive(state, level, pos)) {
return Blocks.AIR.defaultBlockState()
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
val direction = state.getValue(FACING) as Direction
val blockState = level.getBlockState(pos.relative(direction.opposite))
return blockState.isCollisionShapeFullBlock(level, pos) or blockState.canOcclude()
}
override fun codec(): MapCodec<PauvilleaFlowerBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
return when (state.getValue(FACING)) {
Direction.DOWN -> CEILING
Direction.NORTH -> WALL_NORTH
Direction.EAST -> WALL_EAST
Direction.SOUTH -> WALL_SOUTH
Direction.WEST -> WALL_WEST
Direction.UP -> FLOOR
}
}
companion object {
val CODEC: MapCodec<PauvilleaFlowerBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::PauvilleaFlowerBlock)
}
val FLOOR = Shapes.box(0.075, 0.0, 0.075, 0.925, 0.4375, 0.925)
val WALL_NORTH = Shapes.box(0.0, 0.0, 0.8125, 1.0, 1.0, 1.0)
val WALL_EAST = Shapes.box(0.0, 0.0, 0.0, 0.1875, 1.0, 1.0)
val WALL_SOUTH = Shapes.box(0.0, 0.0, 0.0, 1.0, 1.0, 0.1875)
val WALL_WEST = Shapes.box(0.8125, 0.0, 0.0, 1.0, 1.0, 1.0)
val CEILING = Shapes.box(0.075, 0.0, 0.075, 0.925, 1.0, 0.925)
}
override fun isValidBonemealTarget(level: LevelReader?, pos: BlockPos?, state: BlockState?): Boolean {
return true
}
override fun isBonemealSuccess(level: Level?, random: RandomSource?, pos: BlockPos?, state: BlockState?): Boolean {
return true
}
override fun performBonemeal(level: ServerLevel, random: RandomSource?, pos: BlockPos, state: BlockState?) {
popResource(level, pos, ItemStack(this))
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.decor
import net.minecraft.core.BlockPos
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.PinkPetalsBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class PomohanaFlowerBlock(properties: Properties?) : PinkPetalsBlock(properties) {
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
companion object {
private val AABB = Shapes.box(0.125, 0.0, 0.125, 0.875, 1.0, 0.875)
}
}
@@ -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.block.decor
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.TerrainPlantBlockEntity
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.TallFlowerBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class TerrainPlantBlock(properties: Properties) : TallFlowerBlock(properties), EntityBlock {
init {
registerDefaultState(stateDefinition.any()
.setValue(HALF, DoubleBlockHalf.LOWER)
.setValue(GLOWING, false))
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HALF, GLOWING)
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
return false
}
override fun tick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
if (state.`is`(CobblemonBlocks.LUCIBELL)) { this.checkInside(level, pos, state) }
}
private fun checkInside(level: Level, pos: BlockPos, state: BlockState) {
// Only lower will handle this
val upperState = level.getBlockState(pos.above()).takeIf { it.`is`(CobblemonBlocks.LUCIBELL) && it.getValue(HALF) == DoubleBlockHalf.UPPER }
val entityInside = level.getEntities(null, state.getShape(level, pos).bounds().move(pos)).isNotEmpty()
|| upperState?.let { level.getEntities(null, it.getShape(level, pos.above()).bounds().move(pos.above())).isNotEmpty() } ?: false
val glowingState = state.getValue(GLOWING)
if (glowingState != entityInside) {
level.setBlock(pos, state.setValue(GLOWING, entityInside), UPDATE_CLIENTS)
upperState?.let { level.setBlock(pos.above(), it.setValue(GLOWING, entityInside), UPDATE_CLIENTS) }
}
if (entityInside) level.scheduleTick(pos, this, 30) // tick occasionally to check if player inside
}
override fun entityInside(state: BlockState, level: Level, pos: BlockPos, entity: Entity) {
// Lucibell lower block will be our 'driver'
if (!level.isClientSide && !state.getValue(GLOWING) && state.`is`(CobblemonBlocks.LUCIBELL)) {
val halfState = state.getValue(HALF)
if (halfState == DoubleBlockHalf.UPPER) {
val lowerState = level.getBlockState(pos.below())
.takeIf { it.`is`(CobblemonBlocks.LUCIBELL) && it.getValue(HALF) == DoubleBlockHalf.LOWER }
?: return
(lowerState.block as TerrainPlantBlock).checkInside(level, pos.below(), lowerState)
} else {
checkInside(level, pos, state)
}
}
}
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return TerrainPlantBlockEntity(pos, state)
}
override fun <T : BlockEntity> getTicker(
level: Level,
state: BlockState,
blockEntityType: BlockEntityType<T?>
): BlockEntityTicker<T?>? {
return if (level.isClientSide) null else BaseEntityBlock.createTickerHelper(blockEntityType as BlockEntityType<*>,
CobblemonBlockEntities.TERRAIN_PLANT, TerrainPlantBlockEntity::serverTick) as BlockEntityTicker<T?>?
}
companion object {
private val AABB = Shapes.box(0.125, 0.0, 0.125, 0.875, 1.0, 0.875)
val GLOWING = BooleanProperty.create("glowing")
}
}
@@ -0,0 +1,273 @@
/*
* 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.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.util.StringRepresentable
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.*
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.entity.BlockEntity
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.DoubleBlockHalf
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
@Suppress("OVERRIDE_DEPRECATION")
class TerrainSeedBlock(private val terrainSeedType: TerrainSeedType, settings: Properties) : CropBlock(settings) {
companion object {
val CODEC: MapCodec<TerrainSeedBlock> = RecordCodecBuilder.mapCodec { it.group(
TerrainSeedType.CODEC.fieldOf("terrainSeedType").forGetter(TerrainSeedBlock::terrainSeedType),
propertiesCodec()
).apply(it, ::TerrainSeedBlock) }
const val MATURE_AGE = 4
val AGE: IntegerProperty = IntegerProperty.create("age", 0, MATURE_AGE)
val HALF: EnumProperty<DoubleBlockHalf> = BlockStateProperties.DOUBLE_BLOCK_HALF
val AGE_TO_SHAPE = arrayOf(
box(2.0, 0.0, 2.0, 14.0, 6.0, 14.0), // Stage 0
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 1
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 2
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 3
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 4
)
val AGE_TO_SHAPE_TOP = arrayOf(
box(2.0, 0.0, 2.0, 14.0, 1.0, 14.0), // Stage 0
box(2.0, 0.0, 2.0, 14.0, 1.0, 14.0), // Stage 1
box(2.0, 0.0, 2.0, 14.0, 8.0, 14.0), // Stage 2
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 3
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 4
)
}
override fun codec(): MapCodec<out CropBlock> {
return CODEC
}
init {
this.registerDefaultState(
this.stateDefinition.any()
.setValue(AGE, 0)
.setValue(HALF, DoubleBlockHalf.LOWER)
)
}
override fun getAgeProperty(): IntegerProperty = AGE
override fun getMaxAge(): Int = MATURE_AGE
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(AGE, HALF)
}
override fun getBaseSeedId(): ItemLike = this.terrainSeedType.getSeed()
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape =
if (state.getValue(HALF) == DoubleBlockHalf.UPPER) AGE_TO_SHAPE_TOP[getAge(state)] else AGE_TO_SHAPE[this.getAge(state)]
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
if (state.getValue(HALF) == DoubleBlockHalf.UPPER || !canGrow(world, pos, state)) return
if (hasSufficientLight(world, pos) && random.nextInt(32) == 0) {
grow(world, pos, state, 1)
}
}
private fun canGrow(world: LevelReader, pos: BlockPos, state: BlockState): Boolean {
return !this.isMaxAge(state) && canSurvive(state, world, pos) && !(!canGrowInto(world, pos.above()) && state.getValue(AGE) >= 4)
}
private fun canGrowInto(world: LevelReader, pos: BlockPos): Boolean {
val blockState = world.getBlockState(pos)
return blockState.isAir || blockState.`is`(this.terrainSeedType.getCropBlock())
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
return if (state.getValue(AGE) >= 2) {
super.updateShape(state, direction, neighborState, world, pos, neighborPos)
} else {
if (state.canSurvive(world, pos)) state else Blocks.AIR.defaultBlockState()
}
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
val posAndState = getLowerHalf(level, pos, state)
return if (posAndState == null) false else this.canGrow(
level,
posAndState.first,
posAndState.second
)
}
override fun performBonemeal(level: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
val posAndState = getLowerHalf(level, pos, state)
if (posAndState != null) {
grow(level, posAndState.first, posAndState.second, getBonemealAgeIncrease(level))
}
}
fun grow(world: Level, pos: BlockPos, state: BlockState, increment: Int) {
if (!canGrow(world, pos, state)) return
val newAge = (this.getAge(state) + increment).coerceAtMost(this.maxAge)
world.setBlock(pos, state.setValue(this.ageProperty, newAge), UPDATE_CLIENTS)
val abovePos = pos.above()
val stateAbove = world.getBlockState(abovePos)
// Set top half depending on age
if (newAge >= (2)) {
if (stateAbove.isAir) {
world.setBlock(
abovePos,
this.defaultBlockState().setValue(AGE, newAge).setValue(HALF, DoubleBlockHalf.UPPER),
UPDATE_CLIENTS
)
}
else if (stateAbove.hasProperty(HALF) && stateAbove.getValue(HALF) == DoubleBlockHalf.UPPER) {
world.setBlock(
abovePos,
stateAbove.setValue(AGE, newAge),
UPDATE_CLIENTS
)
}
}
// Swaps blocks to the actual Flower when fully grown
if (newAge == this.maxAge) {
val nextStateLower = this.terrainSeedType.getFlowerBlock().defaultBlockState().setValue(HALF, DoubleBlockHalf.LOWER)
val nextStateUpper = this.terrainSeedType.getFlowerBlock().defaultBlockState().setValue(HALF, DoubleBlockHalf.UPPER)
world.setBlock(abovePos, nextStateUpper, UPDATE_CLIENTS)
world.setBlock(pos, nextStateLower, UPDATE_CLIENTS)
}
}
override fun getBonemealAgeIncrease(level: Level): Int {
return 1
}
override fun canSurvive(state: BlockState, world: LevelReader, pos: BlockPos): Boolean {
return when (state.getValue(HALF)) {
DoubleBlockHalf.LOWER -> {
val floorBlock = pos.below()
val floor = world.getBlockState(floorBlock)
floor.`is`(Blocks.FARMLAND)
}
DoubleBlockHalf.UPPER -> {
getLowerHalf(world, pos, state) != null
// Upper part survives only if the lower part is valid
}
}
}
private fun getLowerHalf(level: LevelReader, pos: BlockPos, state: BlockState): Pair<BlockPos, BlockState>? {
if (isLower(state)) {
return Pair(pos, state)
} else {
val blockPos = pos.below()
val blockState = level.getBlockState(blockPos)
return if (isLower(blockState)) Pair(
blockPos,
blockState
) else null
}
}
private fun isLower(state: BlockState): Boolean =
state.`is`(this.terrainSeedType.getCropBlock()) && state.getValue(HALF) == DoubleBlockHalf.LOWER
override fun getSoundType(state: BlockState): SoundType {
return CobblemonSounds.HEARTY_GRAINS_SOUNDS
}
@Suppress("unused")
enum class TerrainSeedType : StringRepresentable {
ELECTRIC,
PSYCHIC,
MISTY,
GRASSY;
fun getSeed(): Item = when (this) {
ELECTRIC -> CobblemonItems.ELECTRIC_SEED
PSYCHIC -> CobblemonItems.PSYCHIC_SEED
MISTY -> CobblemonItems.MISTY_SEED
GRASSY -> CobblemonItems.GRASSY_SEED
}
fun getCropBlock(): TerrainSeedBlock = when (this) {
ELECTRIC -> CobblemonBlocks.ELECTRIC_SEED
PSYCHIC -> CobblemonBlocks.PSYCHIC_SEED
MISTY -> CobblemonBlocks.MISTY_SEED
GRASSY -> CobblemonBlocks.GRASSY_SEED
}
fun getFlowerBlock(): Block = when (this) {
ELECTRIC -> CobblemonBlocks.VOLTACONIA
PSYCHIC -> CobblemonBlocks.LUCIBELL
MISTY -> CobblemonBlocks.MISTLEAF
GRASSY -> CobblemonBlocks.VERDROOT
}
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
override fun playerWillDestroy(level: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
if (!level.isClientSide) {
if (player.isCreative()) {
preventDropFromBottomPart(level, pos, state, player)
} else {
dropResources(state, level, pos, null as BlockEntity?, player, player.getMainHandItem())
}
}
return super.playerWillDestroy(level, pos, state, player)
}
override fun playerDestroy(
level: Level,
player: Player,
pos: BlockPos,
state: BlockState,
blockEntity: BlockEntity?,
tool: ItemStack
) {
super.playerDestroy(level, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool)
}
fun preventDropFromBottomPart(level: Level, pos: BlockPos, state: BlockState, player: Player?) {
val doubleblockhalf = state.getValue<DoubleBlockHalf?>(DoublePlantBlock.HALF) as DoubleBlockHalf
if (doubleblockhalf == DoubleBlockHalf.UPPER) {
val blockpos = pos.below()
val blockstate = level.getBlockState(blockpos)
if (blockstate.`is`(state.getBlock()) && blockstate.getValue<DoubleBlockHalf?>(DoublePlantBlock.HALF) == DoubleBlockHalf.LOWER) {
val blockstate1 = if (blockstate.getFluidState()
.`is`(Fluids.WATER)
) Blocks.WATER.defaultBlockState() else Blocks.AIR.defaultBlockState()
level.setBlock(blockpos, blockstate1, 35)
level.levelEvent(player, 2001, blockpos, getId(blockstate))
}
}
}
}
@@ -0,0 +1,80 @@
/*
* 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.CobblemonBlocks
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.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.BooleanProperty
interface WistensuFlowerBlock {
fun additionalBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(BlockStateProperties.FACING)
builder.add(ROOT)
}
fun adjustStateForPlacement(context: BlockPlaceContext, defaultState: BlockState): BlockState {
val upperBlock = context.level.getBlockState(context.clickedPos.relative(Direction.UP))
if (upperBlock.`is`(CobblemonBlocks.WISTENSU) || upperBlock.`is`(CobblemonBlocks.WISTENSU_VINE)) {
return defaultState
.setValue(BlockStateProperties.FACING, Direction.DOWN)
.setValue(ROOT, false)
} else {
var placeDirection = context.clickedFace
if (placeDirection == Direction.UP) placeDirection = Direction.DOWN
return defaultState
.setValue(BlockStateProperties.FACING, placeDirection)
.setValue(ROOT, true)
}
}
fun copyStateValues(target: BlockState, source: BlockState): BlockState {
return target
.setValue(ROOT, source.getValue(ROOT))
.setValue(BlockStateProperties.FACING, source.getValue(BlockStateProperties.FACING))
}
fun wisternCanSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
if (state.getValue(ROOT)) {
val facing = state.getValue(BlockStateProperties.FACING)
val targetPos = pos.relative(facing.opposite)
val directionState = level.getBlockState(targetPos)
return directionState.isFaceSturdy(level, targetPos, facing)
} else {
val topState = level.getBlockState(pos.relative(Direction.UP))
return topState.`is`(CobblemonBlocks.WISTENSU) || topState.`is`(CobblemonBlocks.WISTENSU_VINE)
}
}
fun wisternUpdateShape(state: BlockState, direction: Direction, neighborState: BlockState, level: LevelAccessor, pos: BlockPos, neighborPos: BlockPos, block: Block): BlockState? {
if (!wisternCanSurvive(state, level, pos)) {
level.scheduleTick(pos, block, 1)
}
if (direction == Direction.UP &&
(neighborState.`is`(CobblemonBlocks.WISTENSU) || neighborState.`is`(CobblemonBlocks.WISTENSU_VINE))) {
return state
.setValue(ROOT, false)
.setValue(BlockStateProperties.FACING, Direction.DOWN)
}
return null
}
companion object {
val ROOT = BooleanProperty.create("root")
}
}
@@ -0,0 +1,114 @@
/*
* 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.CobblemonBlocks
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
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.GrowingPlantHeadBlock
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.phys.shapes.VoxelShape
import kotlin.math.min
//TODO: Root/connection shit
class WistensuFlowerHeadBlock(properties: Properties?) : GrowingPlantHeadBlock(properties, Direction.DOWN, SHAPE, false, 0.1), WistensuFlowerBlock {
companion object {
val CODEC: MapCodec<WistensuFlowerHeadBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::WistensuFlowerHeadBlock)
}
val SHAPE: VoxelShape = box(1.0, 2.0, 1.0, 15.0, 16.0, 15.0)
}
override fun codec(): MapCodec<WistensuFlowerHeadBlock> = CODEC
override fun getBlocksToGrowWhenBonemealed(random: RandomSource): Int {
var d = 1.0
var i = 0
while (random.nextDouble() < d) {
d *= 0.826
++i
}
return i
}
override fun canGrowInto(state: BlockState): Boolean {
return state.isAir
}
override fun getBodyBlock(): Block {
return CobblemonBlocks.WISTENSU_VINE
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
additionalBlockStateDefinition(builder)
}
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
level: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
val updateState = wisternUpdateShape(state, direction, neighborState, level, pos, neighborPos, this)
if (updateState != null) return updateState
if (direction == Direction.DOWN &&
(neighborState.`is`(CobblemonBlocks.WISTENSU) || neighborState.`is`(CobblemonBlocks.WISTENSU_VINE))) {
return updateBodyAfterConvertedFromHead(state, bodyBlock.defaultBlockState())
}
return state
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState {
val defaultState = super.getStateForPlacement(context)!!
return adjustStateForPlacement(context, defaultState)
}
override fun getGrowIntoState(state: BlockState, random: RandomSource): BlockState {
return super.getGrowIntoState(state, random)
.setValue(WistensuFlowerBlock.ROOT, false)
.setValue(BlockStateProperties.FACING, Direction.DOWN)
}
override fun updateBodyAfterConvertedFromHead(head: BlockState, body: BlockState): BlockState {
return copyStateValues(body, head)
}
override fun performBonemeal(level: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
var blockPos = pos.relative(growthDirection)
var i = min(state.getValue(AGE), MAX_AGE)
val j = getBlocksToGrowWhenBonemealed(random)
var currentState = state
for (k in 0..<j) {
if (!canGrowInto(level.getBlockState(blockPos))) break
level.setBlockAndUpdate(blockPos, getGrowIntoState(currentState, random))
currentState = level.getBlockState(blockPos)
blockPos = blockPos.relative(growthDirection)
i = min(i + 1, MAX_AGE)
}
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return wisternCanSurvive(state, level, pos)
}
}
@@ -0,0 +1,77 @@
/*
* 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.CobblemonBlocks
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.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.GrowingPlantBodyBlock
import net.minecraft.world.level.block.GrowingPlantHeadBlock
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.phys.shapes.VoxelShape
//TODO: Root/connection shit
class WistensuFlowerVineBlock(properties: Properties?) : GrowingPlantBodyBlock(properties, Direction.DOWN, SHAPE, false), WistensuFlowerBlock
{
companion object {
val CODEC: MapCodec<WistensuFlowerVineBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::WistensuFlowerVineBlock)
}
val SHAPE: VoxelShape = box(1.0, 0.0, 1.0, 15.0, 16.0, 15.0)
}
override fun codec(): MapCodec<WistensuFlowerVineBlock> = CODEC
override fun getHeadBlock(): GrowingPlantHeadBlock {
return CobblemonBlocks.WISTENSU
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
additionalBlockStateDefinition(builder)
}
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
level: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
val updateState = wisternUpdateShape(state, direction, neighborState, level, pos, neighborPos, this)
if (updateState != null) return updateState
if (direction == Direction.DOWN &&
!neighborState.`is`(CobblemonBlocks.WISTENSU) && !neighborState.`is`(CobblemonBlocks.WISTENSU_VINE)) {
return updateHeadAfterConvertedFromBody(state, headBlock.getStateForPlacement(level))
}
return state
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState {
val defaultState = super.getStateForPlacement(context)!!
return adjustStateForPlacement(context, defaultState)
}
override fun updateHeadAfterConvertedFromBody(head: BlockState, body: BlockState): BlockState {
return copyStateValues(body, head)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return wisternCanSurvive(state, level, pos)
}
}
@@ -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)
@@ -0,0 +1,46 @@
/*
* 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.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.decor.MistleafFlowerBlock
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class MistleafFlowerBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(CobblemonBlockEntities.MISTLEAF_FLOWER, pos, state) {
companion object {
fun serverTick(level: Level, pos: BlockPos, state: BlockState, mistleafFlowerBlockEntity: MistleafFlowerBlockEntity) {
if (level.isClientSide) return
if (state.block !is MistleafFlowerBlock) return
val glowing = state.getValue(MistleafFlowerBlock.GLOWING)
val wantedState = when (state.`is`(CobblemonBlocks.MISTLEAF)) {
//The light level and comparator output increases on odds from New to Full, and decreases on the evens from Full to New
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 4) -> 1 //New Moon
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 5) -> 3 //Waxing Crescent
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 6) -> 5 //First Quarter
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 7) -> 7 //Waxing Gibbous
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 0) -> 8 //Full Moon
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 1) -> 6 //Waning Gibbous
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 2) -> 4 // Third Quarter
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 3) -> 2 //Waning Crescent
else -> 0 //Daytime, and likely dimensions with no concept of day/night cycle or moon phase
}
if (glowing != wantedState) {
level.setBlock(pos, state.setValue(MistleafFlowerBlock.GLOWING, wantedState), Block.UPDATE_ALL)
}
}
}
}
@@ -0,0 +1,37 @@
/*
* 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.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.decor.TerrainPlantBlock
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class TerrainPlantBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(CobblemonBlockEntities.TERRAIN_PLANT, pos, state) {
companion object {
fun serverTick(level: Level, pos: BlockPos, state: BlockState, terrainPlantBlockEntity: TerrainPlantBlockEntity) {
if (level.isClientSide) return
if (state.block !is TerrainPlantBlock) return
val glowing = state.getValue(TerrainPlantBlock.GLOWING)
val wantedState = if (state.`is`(CobblemonBlocks.VOLTACONIA)) level.isThundering
else if (state.`is`(CobblemonBlocks.VERDROOT)) level.isRaining && !level.isThundering
else null // We dont control glowing here
if (wantedState != null && glowing != wantedState) {
level.setBlock(pos, state.setValue(TerrainPlantBlock.GLOWING, wantedState), Block.UPDATE_CLIENTS)
}
}
}
}
@@ -0,0 +1,133 @@
/*
* 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.general
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import kotlin.math.min
class MintLitterBlock(properties: Properties?) : Block(properties) {
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(AMOUNT, 1))
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val blockState = context.getLevel().getBlockState(context.getClickedPos())
return if (blockState.`is`(this)) blockState.setValue<Int?, Int?>(
AMOUNT,
min(4, blockState.getValue<Int?>(AMOUNT) as Int + 1)
) as BlockState else this.defaultBlockState().setValue<Direction?, Direction?>(
FACING,
context.getHorizontalDirection().getOpposite()
) as BlockState
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
builder.add(AMOUNT)
}
override fun updateShape(
state: BlockState,
direction: Direction?,
neighborState: BlockState?,
level: LevelAccessor?,
pos: BlockPos?,
neighborPos: BlockPos?
): BlockState {
return if (!state.canSurvive(level, pos)) Blocks.AIR.defaultBlockState() else super.updateShape(
state,
direction,
neighborState,
level,
pos,
neighborPos
)
}
override fun canSurvive(state: BlockState?, level: LevelReader, pos: BlockPos): Boolean {
val posBelow = pos.below()
return level.getBlockState(posBelow).isFaceSturdy(level, posBelow, Direction.UP)
}
override fun propagatesSkylightDown(state: BlockState, level: BlockGetter?, pos: BlockPos?): Boolean {
return state.getFluidState().isEmpty()
}
public override fun canBeReplaced(state: BlockState, useContext: BlockPlaceContext): Boolean {
return if (!useContext.isSecondaryUseActive && useContext.itemInHand.`is`(this.asItem()) && state.getValue(
AMOUNT
) <= MAX_AMOUNT
) true else super.canBeReplaced(state, useContext)
}
@Deprecated("Deprecated in Java")
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
return state.setValue(HORIZONTAL_FACING, rotation.rotate(state.getValue(HORIZONTAL_FACING)))
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape? {
val litterBoxes = when (state.getValue(FACING)) {
Direction.SOUTH -> SOUTH_SHAPES
Direction.WEST -> WEST_SHAPES
Direction.EAST -> EAST_SHAPES
else -> NORTH_SHAPES
}
var litterBox = litterBoxes[state.getValue(AMOUNT)]
return litterBox
}
companion object {
val CODEC: MapCodec<MintLitterBlock> = RecordCodecBuilder.mapCodec {
it.group(
propertiesCodec()
).apply(it, ::MintLitterBlock) }
val AMOUNT = IntegerProperty.create("amount", 1, 4)
const val MAX_AMOUNT: Int = 4
private val NORTH_SHAPES = listOf(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.125, 1.0),
Shapes.box(0.0, 0.0, 0.0, 0.5, 0.0625, 0.5),
Shapes.box(0.0, 0.0, 0.0, 0.5, 0.0625, 1.0),
Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 0.5, 0.0625, 1.0),
Shapes.box(0.5, 0.0, 0.5, 1.0, 0.0625, 1.0)
),
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.0625, 1.0)
)
private val SOUTH_SHAPES = createShapes(Direction.SOUTH)
private val WEST_SHAPES = createShapes(Direction.WEST)
private val EAST_SHAPES = createShapes(Direction.EAST)
private fun createShapes(to: Direction): List<VoxelShape> {
return NORTH_SHAPES.map { rotateShape(Direction.NORTH, to, it) }
}
}
}
@@ -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 {
@@ -45,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
@@ -390,7 +391,7 @@ 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,
@@ -414,7 +415,108 @@ object CobblemonClient {
CobblemonBlocks.NEVER_MELT_ICE,
CobblemonBlocks.WIDE_LENS,
CobblemonBlocks.ZOOM_LENS,
CobblemonBlocks.SCOPE_LENS
CobblemonBlocks.SCOPE_LENS,
CobblemonBlocks.MOOMOO_MILK,
CobblemonBlocks.MEDICINAL_BREW,
CobblemonBlocks.TERRAIN_EXTENDER,
CobblemonBlocks.RED_MINT_LEAF_LITTER,
CobblemonBlocks.POTTED_RED_MINT,
CobblemonBlocks.BLUE_MINT_LEAF_LITTER,
CobblemonBlocks.POTTED_BLUE_MINT,
CobblemonBlocks.CYAN_MINT_LEAF_LITTER,
CobblemonBlocks.POTTED_CYAN_MINT,
CobblemonBlocks.PINK_MINT_LEAF_LITTER,
CobblemonBlocks.POTTED_PINK_MINT,
CobblemonBlocks.GREEN_MINT_LEAF_LITTER,
CobblemonBlocks.POTTED_GREEN_MINT,
CobblemonBlocks.WHITE_MINT_LEAF_LITTER,
CobblemonBlocks.POTTED_WHITE_MINT,
CobblemonBlocks.LOBAILA,
CobblemonBlocks.POMOHANA,
CobblemonBlocks.PAUVILLEA,
CobblemonBlocks.WISTENSU,
CobblemonBlocks.WISTENSU_VINE,
CobblemonBlocks.ELECTRIC_SEED,
CobblemonBlocks.VOLTACONIA,
CobblemonBlocks.GRASSY_SEED,
CobblemonBlocks.VERDROOT,
CobblemonBlocks.MISTY_SEED,
CobblemonBlocks.MISTLEAF,
CobblemonBlocks.PSYCHIC_SEED,
CobblemonBlocks.LUCIBELL,
CobblemonBlocks.POTTED_AGUAV_BERRY,
CobblemonBlocks.POTTED_APICOT_BERRY,
CobblemonBlocks.POTTED_ASPEAR_BERRY,
CobblemonBlocks.POTTED_BABIRI_BERRY,
CobblemonBlocks.POTTED_BELUE_BERRY,
CobblemonBlocks.POTTED_BLUK_BERRY,
CobblemonBlocks.POTTED_CHARTI_BERRY,
CobblemonBlocks.POTTED_CHERI_BERRY,
CobblemonBlocks.POTTED_CHESTO_BERRY,
CobblemonBlocks.POTTED_CHILAN_BERRY,
CobblemonBlocks.POTTED_CHOPLE_BERRY,
CobblemonBlocks.POTTED_COBA_BERRY,
CobblemonBlocks.POTTED_COLBUR_BERRY,
CobblemonBlocks.POTTED_CORNN_BERRY,
CobblemonBlocks.POTTED_CUSTAP_BERRY,
CobblemonBlocks.POTTED_DURIN_BERRY,
CobblemonBlocks.POTTED_EGGANT_BERRY,
CobblemonBlocks.POTTED_ENIGMA_BERRY,
CobblemonBlocks.POTTED_FIGY_BERRY,
CobblemonBlocks.POTTED_GANLON_BERRY,
CobblemonBlocks.POTTED_GREPA_BERRY,
CobblemonBlocks.POTTED_HABAN_BERRY,
CobblemonBlocks.POTTED_HONDEW_BERRY,
CobblemonBlocks.POTTED_HOPO_BERRY,
CobblemonBlocks.POTTED_IAPAPA_BERRY,
CobblemonBlocks.POTTED_JABOCA_BERRY,
CobblemonBlocks.POTTED_KASIB_BERRY,
CobblemonBlocks.POTTED_KEBIA_BERRY,
CobblemonBlocks.POTTED_KEE_BERRY,
CobblemonBlocks.POTTED_KELPSY_BERRY,
CobblemonBlocks.POTTED_LANSAT_BERRY,
CobblemonBlocks.POTTED_LEPPA_BERRY,
CobblemonBlocks.POTTED_LIECHI_BERRY,
CobblemonBlocks.POTTED_LUM_BERRY,
CobblemonBlocks.POTTED_MAGOST_BERRY,
CobblemonBlocks.POTTED_MAGO_BERRY,
CobblemonBlocks.POTTED_MARANGA_BERRY,
CobblemonBlocks.POTTED_MICLE_BERRY,
CobblemonBlocks.POTTED_NANAB_BERRY,
CobblemonBlocks.POTTED_NOMEL_BERRY,
CobblemonBlocks.POTTED_OCCA_BERRY,
CobblemonBlocks.POTTED_ORAN_BERRY,
CobblemonBlocks.POTTED_PAMTRE_BERRY,
CobblemonBlocks.POTTED_PASSHO_BERRY,
CobblemonBlocks.POTTED_PAYAPA_BERRY,
CobblemonBlocks.POTTED_PECHA_BERRY,
CobblemonBlocks.POTTED_PERSIM_BERRY,
CobblemonBlocks.POTTED_PETAYA_BERRY,
CobblemonBlocks.POTTED_PINAP_BERRY,
CobblemonBlocks.POTTED_POMEG_BERRY,
CobblemonBlocks.POTTED_QUALOT_BERRY,
CobblemonBlocks.POTTED_RABUTA_BERRY,
CobblemonBlocks.POTTED_RAWST_BERRY,
CobblemonBlocks.POTTED_RAZZ_BERRY,
CobblemonBlocks.POTTED_RINDO_BERRY,
CobblemonBlocks.POTTED_ROSELI_BERRY,
CobblemonBlocks.POTTED_ROWAP_BERRY,
CobblemonBlocks.POTTED_SALAC_BERRY,
CobblemonBlocks.POTTED_SHUCA_BERRY,
CobblemonBlocks.POTTED_SITRUS_BERRY,
CobblemonBlocks.POTTED_SPELON_BERRY,
CobblemonBlocks.POTTED_STARF_BERRY,
CobblemonBlocks.POTTED_TAMATO_BERRY,
CobblemonBlocks.POTTED_TANGA_BERRY,
CobblemonBlocks.POTTED_TOUGA_BERRY,
CobblemonBlocks.POTTED_WACAN_BERRY,
CobblemonBlocks.POTTED_WATMEL_BERRY,
CobblemonBlocks.POTTED_WEPEAR_BERRY,
CobblemonBlocks.POTTED_WIKI_BERRY,
CobblemonBlocks.POTTED_YACHE_BERRY
)
this.createBoatModelLayers()
@@ -436,8 +538,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() {
@@ -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
@@ -0,0 +1,466 @@
/*
* 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.debug.speed
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.net.messages.client.debug.SpeedTunerEntry
import com.cobblemon.mod.common.net.messages.server.debug.SetSpeedTunerSprintSpeedPacket
import com.cobblemon.mod.common.net.messages.server.debug.SetSpeedTunerWalkSpeedPacket
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.platform.events.RenderEvent
import com.cobblemon.mod.common.pokemon.Pokemon
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import java.nio.file.Files
import java.nio.file.Path
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.floor
import kotlin.math.sin
import kotlin.math.sqrt
import net.minecraft.client.Minecraft
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.client.renderer.LevelRenderer
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.ClickEvent
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.HoverEvent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.phys.Vec3
/**
* Client side session for tuning species walk and sprint speeds. A display only pokemon runs a circle or a line at
* exactly the speed being tuned, and edits are kept in blocks per second and saved to a file for applying to the
* species data later.
*/
object SpeedTuner {
enum class Gait(val jsonKey: String) { WALK("walkSpeed"), SPRINT("sprintSpeed") }
enum class Track { CIRCLE, LINE }
// Vanilla player top speeds in blocks per second, shown and run alongside for comparison
const val PLAYER_WALK_SPEED = 4.317
const val PLAYER_SPRINT_SPEED = 5.612
// Sprint speed shown for species without one yet, matches the data script default
const val DEFAULT_SPRINT_SPEED = 5.6
// Walk speed values are multiplied by this in PokemonMoveControl
const val WALK_SPEED_SCALE = 2.5
// Friction of ordinary blocks, walk estimates assume flat normal ground
const val VANILLA_BLOCK_FRICTION = 0.6
const val TICKS_PER_SECOND = 20.0
const val DEFAULT_CIRCLE_RADIUS = 6.0
const val DEFAULT_LINE_LENGTH = 16.0
// How often the track gets outlined, and the gap between outline markers in blocks
const val TRACK_MARKER_INTERVAL_TICKS = 10
const val TRACK_MARKER_SPACING = 1.0
// Markers sit this far above the start height so they aren't inside the ground
const val MARKER_HEIGHT = 0.1
// Edits made while dragging a slider are written out at most this often
const val AUTOSAVE_INTERVAL_TICKS = 40
private const val UNITS = "blocks per second"
private val gson = GsonBuilder().setPrettyPrinting().create()
private val sessionFile: Path
get() = Minecraft.getInstance().gameDirectory.toPath().resolve("cobblemon-dev").resolve("speed_tuning_session.json")
var active = false
private set
var entries: List<SpeedTunerEntry> = emptyList()
private set
var selected: SpeedTunerEntry? = null
private set
var gait = Gait.SPRINT
set(value) {
field = value
ensureSelectionVisible()
restartRun()
}
var track = Track.CIRCLE
set(value) {
field = value
restartRun()
}
var radius = DEFAULT_CIRCLE_RADIUS
var lineLength = DEFAULT_LINE_LENGTH
var paused = false
// How fast the display pokemon plays, movement and animation together. 1 is real time
var playbackSpeed = 1.0
// Species id to edited speeds by gait json key, all in blocks per second
private val edits = mutableMapOf<ResourceLocation, MutableMap<String, Double>>()
private val reviewed = mutableSetOf<ResourceLocation>()
private var dirty = false
private var subscribed = false
private var origin = Vec3.ZERO
private var lineDirection = Vec3(0.0, 0.0, 1.0)
private var distance = 0.0
private var referenceDistance = 0.0
private var ticks = 0L
// The display pokemon's own clock in ticks, so it can run slower than the game or stand still
private var displayTime = 0.0
// Game ticks since the display pokemon last stepped, for how far between its steps it is
private var gameTicksSinceStep = 0
// Partial tick it was drawn at last, held while paused so it freezes exactly where it was
private var lastPartialTick = 0F
private var puppet: PokemonEntity? = null
fun open(entries: List<SpeedTunerEntry>) {
val player = Minecraft.getInstance().player ?: return
this.entries = entries.sortedBy { it.dex }
if (!active) {
origin = player.position()
val look = Vec3(player.lookAngle.x, 0.0, player.lookAngle.z)
lineDirection = if (look.lengthSqr() > 0) look.normalize() else Vec3(0.0, 0.0, 1.0)
load()
active = true
// The server starts from the species data, so bring it up to date with the loaded session
edits.forEach { (id, speeds) ->
speeds[Gait.SPRINT.jsonKey]?.let { sendSprintSpeed(id, it.toFloat()) }
speeds[Gait.WALK.jsonKey]?.let { sendWalkSpeed(id, walkSpeedFor(it)) }
}
}
subscribe()
ensureSelectionVisible()
Minecraft.getInstance().setScreen(SpeedTunerScreen())
}
fun stop() {
save()
active = false
puppet = null
selected = null
}
/** Species that make sense for the current gait. Only species with a sprint speed can sprint. */
fun visibleEntries(): List<SpeedTunerEntry> {
return if (gait == Gait.SPRINT) entries.filter { it.sprintSpeed != null } else entries
}
fun select(entry: SpeedTunerEntry) {
selected = entry
puppet = null
restartRun()
}
fun step(offset: Int) {
val visible = visibleEntries().ifEmpty { return }
val index = visible.indexOf(selected).coerceAtLeast(0)
select(visible[Math.floorMod(index + offset, visible.size)])
}
/** Finds a species by dex number or by name, preferring names that start with the search. */
fun search(text: String): Boolean {
val query = text.trim().lowercase().ifEmpty { return false }
val visible = visibleEntries()
val match = query.toIntOrNull()?.let { dex -> visible.firstOrNull { it.dex == dex } }
?: visible.firstOrNull { it.speciesId.path.startsWith(query) }
?: visible.firstOrNull { it.speciesId.path.contains(query) }
?: return false
select(match)
return true
}
fun speed(entry: SpeedTunerEntry, gait: Gait = this.gait): Double {
return edits[entry.speciesId]?.get(gait.jsonKey) ?: originalSpeed(entry, gait)
}
fun originalSpeed(entry: SpeedTunerEntry, gait: Gait = this.gait): Double {
return when (gait) {
Gait.SPRINT -> entry.sprintSpeed?.toDouble() ?: DEFAULT_SPRINT_SPEED
Gait.WALK -> estimatedWalkSpeed(entry)
}
}
/**
* Walk speed is still relative in the species data, so this works out what it comes to in blocks per second on
* normal ground. Walkers always use the same speed modifier so this is exact.
*/
fun estimatedWalkSpeed(entry: SpeedTunerEntry): Double {
val input = walkInputPerWalkSpeed() * entry.walkSpeed
return PokemonMoveControl.vanillaGroundTopSpeed(input, VANILLA_BLOCK_FRICTION) * TICKS_PER_SECOND
}
/** The other way round, walkSpeed that comes out at [blocksPerSecond]. Top speed grows with the square of the input */
fun walkSpeedFor(blocksPerSecond: Double): Float {
val topSpeedPerInputSquared = PokemonMoveControl.vanillaGroundTopSpeed(1.0, VANILLA_BLOCK_FRICTION)
val input = sqrt(blocksPerSecond / TICKS_PER_SECOND / topSpeedPerInputSquared)
return (input / walkInputPerWalkSpeed()).toFloat()
}
// Move control input for each unit of walkSpeed
private fun walkInputPerWalkSpeed(): Double {
return Attributes.MOVEMENT_SPEED.value().defaultValue * PokemonMoveControl.GROUND_SPEED_MODIFIER * WALK_SPEED_SCALE
}
fun referenceSpeed() = if (gait == Gait.SPRINT) PLAYER_SPRINT_SPEED else PLAYER_WALK_SPEED
fun setSpeed(value: Double) {
val entry = selected ?: return
edits.getOrPut(entry.speciesId) { mutableMapOf() }[gait.jsonKey] = value
dirty = true
when (gait) {
Gait.SPRINT -> sendSprintSpeed(entry.speciesId, value.toFloat())
Gait.WALK -> sendWalkSpeed(entry.speciesId, walkSpeedFor(value))
}
}
fun resetSpeed() {
val entry = selected ?: return
edits[entry.speciesId]?.let {
it.remove(gait.jsonKey)
if (it.isEmpty()) edits.remove(entry.speciesId)
}
dirty = true
when (gait) {
Gait.SPRINT -> sendSprintSpeed(entry.speciesId, entry.sprintSpeed)
Gait.WALK -> sendWalkSpeed(entry.speciesId, entry.walkSpeed)
}
}
private fun sendSprintSpeed(speciesId: ResourceLocation, sprintSpeed: Float?) {
SetSpeedTunerSprintSpeedPacket(speciesId, sprintSpeed).sendToServer()
}
/** Walk speeds go over in the species data's own units, converted from the tuner's blocks per second */
private fun sendWalkSpeed(speciesId: ResourceLocation, walkSpeed: Float) {
SetSpeedTunerWalkSpeedPacket(speciesId, walkSpeed).sendToServer()
}
fun isEdited(entry: SpeedTunerEntry) = edits[entry.speciesId]?.containsKey(gait.jsonKey) == true
fun isReviewed(entry: SpeedTunerEntry) = entry.speciesId in reviewed
fun toggleReviewed() {
val entry = selected ?: return
if (!reviewed.remove(entry.speciesId)) reviewed.add(entry.speciesId)
dirty = true
}
fun editedCount() = edits.size
fun reviewedCount() = reviewed.size
fun restartRun() {
distance = 0.0
referenceDistance = 0.0
}
private fun ensureSelectionVisible() {
val visible = visibleEntries()
if (selected !in visible) {
visible.firstOrNull()?.let { select(it) } ?: run { selected = null }
}
}
private fun subscribe() {
if (subscribed) return
subscribed = true
PlatformEvents.CLIENT_TICK_POST.subscribe { tick() }
PlatformEvents.RENDER.subscribe { render(it) }
PlatformEvents.CLIENT_PLAYER_LOGOUT.subscribe { if (active) stop() }
}
private fun tick() {
if (!active) return
ticks++
if (dirty && ticks % AUTOSAVE_INTERVAL_TICKS == 0L) save()
val level = Minecraft.getInstance().level ?: return
val entry = selected ?: return
val display = puppet?.takeIf { it.level() === level } ?: createPuppet(level, entry)?.also { puppet = it } ?: return
val rate = if (paused) 0.0 else playbackSpeed
val before = displayTime
displayTime += rate
// One display tick each time its clock passes a whole tick, so slow motion moves and animates less often
val steps = (floor(displayTime) - floor(before)).toInt()
repeat(steps) { stepDisplay(display, entry) }
// Hold while paused so it picks up from the same spot when resumed
if (steps > 0) gameTicksSinceStep = 0 else if (!paused) gameTicksSinceStep++
display.isPokemonSprinting = gait == Gait.SPRINT
display.entityData.set(PokemonEntity.POSE_TYPE, PoseType.WALK)
display.entityData.set(PokemonEntity.MOVING, !paused)
val reference = trackPosition(referenceDistance)
level.addParticle(ParticleTypes.ELECTRIC_SPARK, reference.x, reference.y + MARKER_HEIGHT, reference.z, 0.0, 0.0, 0.0)
if (ticks % TRACK_MARKER_INTERVAL_TICKS == 0L) outlineTrack(level)
}
/** One tick of the display pokemon's own clock, moving it along the track and ticking its animations */
private fun stepDisplay(display: PokemonEntity, entry: SpeedTunerEntry) {
distance += speed(entry) / TICKS_PER_SECOND
referenceDistance += referenceSpeed() / TICKS_PER_SECOND
display.tickCount++
val lapLength = lapLength()
// Line runs start over from the beginning, so jump without smoothing between the two ends
val wrapped = distance >= lapLength
distance %= lapLength
referenceDistance %= lapLength
display.setOldPosAndRot()
display.yBodyRotO = display.yBodyRot
display.yHeadRotO = display.yHeadRot
val position = trackPosition(distance)
// Keep yaw continuous with last tick so nothing lerping it spins round at the wrap
val yaw = display.yBodyRot + Mth.wrapDegrees(trackYaw(distance) - display.yBodyRot)
display.setPos(position.x, position.y, position.z)
display.yRot = yaw
display.yBodyRot = yaw
display.yHeadRot = yaw
if (wrapped && track == Track.LINE) {
display.setOldPosAndRot()
display.yBodyRotO = yaw
display.yHeadRotO = yaw
}
// Animation age and pose changes advance in the client delegate's tick, which normally runs from the
// entity's own tick. The display pokemon is never ticked so do it here
display.delegate.tick(display)
}
private fun render(event: RenderEvent) {
if (!active) return
val puppet = puppet ?: return
val level = Minecraft.getInstance().level ?: return
if (puppet.level() !== level) return
val camera = event.camera.position
// How far between its own steps the display pokemon is, so slow motion stays smooth and paused really stops
val partialTick = if (paused) {
lastPartialTick
} else {
val gamePartialTick = event.tickCounter.getGameTimeDeltaPartialTick(false)
((gameTicksSinceStep + gamePartialTick) * playbackSpeed).coerceAtMost(1.0).toFloat()
}
lastPartialTick = partialTick
val x = Mth.lerp(partialTick.toDouble(), puppet.xo, puppet.x) - camera.x
val y = Mth.lerp(partialTick.toDouble(), puppet.yo, puppet.y) - camera.y
val z = Mth.lerp(partialTick.toDouble(), puppet.zo, puppet.z) - camera.z
val light = LevelRenderer.getLightColor(level, puppet.blockPosition())
val buffers = Minecraft.getInstance().renderBuffers().bufferSource()
Minecraft.getInstance().entityRenderDispatcher.render(puppet, x, y, z, puppet.yRot, partialTick, event.poseStack, buffers, light)
buffers.endBatch()
}
/** Same setup as HabitatBlockRenderer, a pokemon that only exists to be drawn. */
private fun createPuppet(level: ClientLevel, entry: SpeedTunerEntry): PokemonEntity? {
val species = PokemonSpecies.getByIdentifier(entry.speciesId) ?: return null
val pokemon = Pokemon().apply {
isClient = true
this.species = species
initialize()
}
return PokemonEntity(level, pokemon).apply {
noPhysics = true
setNoGravity(true)
val start = trackPosition(distance)
setPos(start.x, start.y, start.z)
setOldPosAndRot()
}
}
private fun lapLength() = if (track == Track.CIRCLE) 2 * PI * radius else lineLength
private fun trackPosition(along: Double): Vec3 {
return when (track) {
Track.CIRCLE -> {
val angle = along / radius
origin.add(cos(angle) * radius, 0.0, sin(angle) * radius)
}
Track.LINE -> origin.add(lineDirection.scale(along))
}
}
private fun trackYaw(along: Double): Float {
val direction = when (track) {
// Tangent of the circle in the direction of travel
Track.CIRCLE -> (along / radius).let { Vec3(-sin(it), 0.0, cos(it)) }
Track.LINE -> lineDirection
}
return (Mth.atan2(direction.z, direction.x) * Mth.RAD_TO_DEG).toFloat() - 90F
}
private fun outlineTrack(level: ClientLevel) {
val markers = (lapLength() / TRACK_MARKER_SPACING).toInt()
for (i in 0..markers) {
val marker = trackPosition(i * TRACK_MARKER_SPACING)
level.addParticle(ParticleTypes.COMPOSTER, marker.x, marker.y + MARKER_HEIGHT, marker.z, 0.0, 0.0, 0.0)
}
}
private fun toJson(): JsonObject {
val species = JsonObject()
edits.toSortedMap(compareBy { it.toString() }).forEach { (id, speeds) ->
species.add(id.toString(), JsonObject().also { speedsJson -> speeds.forEach { (key, value) -> speedsJson.addProperty(key, value) } })
}
return JsonObject().apply {
addProperty("units", UNITS)
add("species", species)
add("reviewed", JsonArray().also { array -> reviewed.map { it.toString() }.sorted().forEach { array.add(it) } })
}
}
fun save() {
runCatching {
Files.createDirectories(sessionFile.parent)
Files.writeString(sessionFile, gson.toJson(toJson()))
dirty = false
}.onFailure { Cobblemon.LOGGER.error("Couldn't save the speed tuning session", it) }
}
/** Writes a timestamped copy of the session next to it and posts links to it in chat. */
fun export() {
save()
val stamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"))
val file = sessionFile.resolveSibling("speed_tuning_$stamp.json").toAbsolutePath()
runCatching {
Files.writeString(file, gson.toJson(toJson()))
val message = Component.literal("Speed tuning exported: ")
.append(chatLink("[open file]", ClickEvent.Action.OPEN_FILE, file.toString()))
.append(" ")
.append(chatLink("[open folder]", ClickEvent.Action.OPEN_FILE, file.parent.toString()))
.append(" ")
.append(chatLink("[copy path]", ClickEvent.Action.COPY_TO_CLIPBOARD, file.toString()))
Minecraft.getInstance().player?.displayClientMessage(message, false)
}.onFailure { Cobblemon.LOGGER.error("Couldn't export the speed tuning session", it) }
}
private fun chatLink(label: String, action: ClickEvent.Action, value: String): Component {
return Component.literal(label).withStyle { style ->
style.withColor(ChatFormatting.AQUA)
.withUnderlined(true)
.withClickEvent(ClickEvent(action, value))
.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(value)))
}
}
private fun load() {
edits.clear()
reviewed.clear()
if (!Files.exists(sessionFile)) return
runCatching {
val json = JsonParser.parseString(Files.readString(sessionFile)).asJsonObject
json.getAsJsonObject("species")?.entrySet()?.forEach { (id, speeds) ->
val map = edits.getOrPut(ResourceLocation.parse(id)) { mutableMapOf() }
speeds.asJsonObject.entrySet().forEach { (key, value) -> map[key] = value.asDouble }
}
json.getAsJsonArray("reviewed")?.forEach { reviewed.add(ResourceLocation.parse(it.asString)) }
}.onFailure { Cobblemon.LOGGER.error("Couldn't load the speed tuning session", it) }
}
}
@@ -0,0 +1,240 @@
/*
* 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.debug.speed
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.text.text
import java.awt.Color
import java.util.Locale
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractSliderButton
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import net.minecraft.util.Mth
import org.lwjgl.glfw.GLFW
/**
* Panel for [SpeedTuner] down the left side of the screen. Doesn't pause and leaves the world visible so the display
* pokemon can be watched from anywhere. Closing it leaves the session running, the debug keybind brings it back.
*/
class SpeedTunerScreen : Screen("Speed Tuner".text()) {
companion object {
const val PANEL_WIDTH = 180
const val MARGIN = 6
const val ROW_HEIGHT = 20
const val ROW_GAP = 4
const val TEXT_LINE_HEIGHT = 11
// Typed speeds can go up to this, the slider only covers the usual range
const val MAX_SPEED = 20.0
const val SLIDER_MAX_SPEED = 8.0
// Blocks per second the - and + buttons change the speed by
const val SPEED_STEP = 0.05
val PLAYBACK_SPEEDS = listOf(1.0, 0.5, 0.25, 0.1)
const val MIN_RADIUS = 2.0
const val MAX_RADIUS = 24.0
const val MIN_LINE_LENGTH = 4.0
const val MAX_LINE_LENGTH = 48.0
val PANEL_COLOUR = Color(0, 0, 0, 150).rgb
val EDITED_COLOUR = Color(255, 200, 80).rgb
val TEXT_COLOUR = Color(255, 255, 255).rgb
}
private lateinit var searchBox: EditBox
private lateinit var speedBox: EditBox
private lateinit var speedSlider: DecimalSlider
private var infoTop = 0
override fun isPauseScreen() = false
override fun init() {
super.init()
val fullWidth = PANEL_WIDTH - MARGIN * 2
val halfWidth = (fullWidth - ROW_GAP) / 2
var y = MARGIN
fun nextRow(): Int = y.also { y += ROW_HEIGHT + ROW_GAP }
nextRow().let { row ->
addRenderableWidget(Button.builder("<".text()) { step(-1) }.bounds(MARGIN, row, ROW_HEIGHT, ROW_HEIGHT).build())
addRenderableWidget(Button.builder(">".text()) { step(1) }.bounds(PANEL_WIDTH - MARGIN - ROW_HEIGHT, row, ROW_HEIGHT, ROW_HEIGHT).build())
}
searchBox = addRenderableWidget(EditBox(font, MARGIN, nextRow(), fullWidth, ROW_HEIGHT, "Search".text())).apply {
setHint("Dex number or name, Enter".text())
}
nextRow().let { row ->
addRenderableWidget(Button.builder("Gait: ${SpeedTuner.gait.name.lowercase()}".text()) {
SpeedTuner.gait = if (SpeedTuner.gait == SpeedTuner.Gait.SPRINT) SpeedTuner.Gait.WALK else SpeedTuner.Gait.SPRINT
rebuildWidgets()
}.bounds(MARGIN, row, halfWidth, ROW_HEIGHT).build())
addRenderableWidget(Button.builder("Track: ${SpeedTuner.track.name.lowercase()}".text()) {
SpeedTuner.track = if (SpeedTuner.track == SpeedTuner.Track.CIRCLE) SpeedTuner.Track.LINE else SpeedTuner.Track.CIRCLE
rebuildWidgets()
}.bounds(MARGIN + halfWidth + ROW_GAP, row, halfWidth, ROW_HEIGHT).build())
}
val entry = SpeedTuner.selected
nextRow().let { row ->
addRenderableWidget(Button.builder("-".text()) { nudgeSpeed(-SPEED_STEP) }.bounds(MARGIN, row, ROW_HEIGHT, ROW_HEIGHT).build())
speedSlider = addRenderableWidget(
DecimalSlider(MARGIN + ROW_HEIGHT + ROW_GAP, row, fullWidth - 2 * (ROW_HEIGHT + ROW_GAP), "Speed", 0.0, SLIDER_MAX_SPEED, entry?.let { SpeedTuner.speed(it) } ?: 0.0) {
SpeedTuner.setSpeed(it)
speedBox.value = format(it)
}
)
addRenderableWidget(Button.builder("+".text()) { nudgeSpeed(SPEED_STEP) }.bounds(PANEL_WIDTH - MARGIN - ROW_HEIGHT, row, ROW_HEIGHT, ROW_HEIGHT).build())
}
nextRow().let { row ->
speedBox = addRenderableWidget(EditBox(font, MARGIN, row, halfWidth, ROW_HEIGHT, "Speed".text())).apply {
value = entry?.let { format(SpeedTuner.speed(it)) } ?: ""
}
addRenderableWidget(Button.builder("Set".text()) { applyTypedSpeed() }.bounds(MARGIN + halfWidth + ROW_GAP, row, halfWidth, ROW_HEIGHT).build())
}
if (SpeedTuner.track == SpeedTuner.Track.CIRCLE) {
addRenderableWidget(DecimalSlider(MARGIN, nextRow(), fullWidth, "Radius", MIN_RADIUS, MAX_RADIUS, SpeedTuner.radius) {
SpeedTuner.radius = it
})
} else {
addRenderableWidget(DecimalSlider(MARGIN, nextRow(), fullWidth, "Length", MIN_LINE_LENGTH, MAX_LINE_LENGTH, SpeedTuner.lineLength) {
SpeedTuner.lineLength = it
})
}
nextRow().let { row ->
addRenderableWidget(Button.builder((if (SpeedTuner.paused) "Resume" else "Pause").text()) {
SpeedTuner.paused = !SpeedTuner.paused
rebuildWidgets()
}.bounds(MARGIN, row, halfWidth, ROW_HEIGHT).build())
addRenderableWidget(Button.builder("Reset speed".text()) {
SpeedTuner.resetSpeed()
rebuildWidgets()
}.bounds(MARGIN + halfWidth + ROW_GAP, row, halfWidth, ROW_HEIGHT).build())
}
addRenderableWidget(Button.builder("Playback: ${format(SpeedTuner.playbackSpeed)}x".text()) {
val next = (PLAYBACK_SPEEDS.indexOf(SpeedTuner.playbackSpeed) + 1) % PLAYBACK_SPEEDS.size
SpeedTuner.playbackSpeed = PLAYBACK_SPEEDS[next]
rebuildWidgets()
}.bounds(MARGIN, nextRow(), fullWidth, ROW_HEIGHT).build())
nextRow().let { row ->
val reviewed = entry?.let { SpeedTuner.isReviewed(it) } == true
addRenderableWidget(Button.builder((if (reviewed) "Reviewed ✔" else "Mark reviewed").text()) {
SpeedTuner.toggleReviewed()
rebuildWidgets()
}.bounds(MARGIN, row, halfWidth, ROW_HEIGHT).build())
addRenderableWidget(Button.builder("Export".text()) { SpeedTuner.export() }
.bounds(MARGIN + halfWidth + ROW_GAP, row, halfWidth, ROW_HEIGHT).build())
}
addRenderableWidget(Button.builder("End session".text()) {
SpeedTuner.stop()
onClose()
}.bounds(MARGIN, nextRow(), fullWidth, ROW_HEIGHT).build())
infoTop = y
}
private fun step(offset: Int) {
SpeedTuner.step(offset)
rebuildWidgets()
}
private fun nudgeSpeed(change: Double) {
val entry = SpeedTuner.selected ?: return
SpeedTuner.setSpeed((SpeedTuner.speed(entry) + change).coerceIn(0.0, MAX_SPEED))
rebuildWidgets()
}
private fun applyTypedSpeed() {
val typed = speedBox.value.toDoubleOrNull() ?: return
SpeedTuner.setSpeed(typed.coerceIn(0.0, MAX_SPEED))
rebuildWidgets()
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) {
if (searchBox.isFocused) {
if (SpeedTuner.search(searchBox.value)) rebuildWidgets()
return true
}
if (speedBox.isFocused) {
applyTypedSpeed()
return true
}
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
override fun onClose() {
SpeedTuner.save()
super.onClose()
}
// Only darken the panel so the world stays visible
override fun renderBackground(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
guiGraphics.fill(0, 0, PANEL_WIDTH, height, PANEL_COLOUR)
}
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
super.render(guiGraphics, mouseX, mouseY, partialTick)
val entry = SpeedTuner.selected
val title = entry?.let { selected ->
val name = PokemonSpecies.getByIdentifier(selected.speciesId)?.translatedName ?: selected.speciesId.path.text()
Component.literal("#${selected.dex} ").append(name)
} ?: "No species".text()
guiGraphics.drawCenteredString(font, title, PANEL_WIDTH / 2, MARGIN + (ROW_HEIGHT - font.lineHeight) / 2, TEXT_COLOUR)
var y = infoTop
fun line(text: String, colour: Int = TEXT_COLOUR) {
guiGraphics.drawString(font, text, MARGIN, y, colour)
y += TEXT_LINE_HEIGHT
}
if (entry != null) {
val speed = SpeedTuner.speed(entry)
line("Speed: ${format(speed)} blocks/s", if (SpeedTuner.isEdited(entry)) EDITED_COLOUR else TEXT_COLOUR)
line("Original: ${format(SpeedTuner.originalSpeed(entry))} blocks/s")
if (SpeedTuner.gait == SpeedTuner.Gait.WALK) {
line("(estimated, raw walkSpeed ${format(entry.walkSpeed.toDouble())})")
}
}
line("Sparks: player ${SpeedTuner.gait.name.lowercase()} ${format(SpeedTuner.referenceSpeed())} blocks/s")
line("Edited: ${SpeedTuner.editedCount()} Reviewed: ${SpeedTuner.reviewedCount()}")
line("Species: ${SpeedTuner.visibleEntries().size}")
}
private fun format(value: Double) = String.format(Locale.ROOT, "%.2f", value)
/** Slider over a decimal range that reports every change. */
class DecimalSlider(
x: Int,
y: Int,
width: Int,
private val label: String,
private val min: Double,
private val max: Double,
initial: Double,
private val onChange: (Double) -> Unit
) : AbstractSliderButton(x, y, width, ROW_HEIGHT, Component.empty(), 0.0) {
private val current: Double
get() = Mth.lerp(value, min, max)
init {
value = ((initial - min) / (max - min)).coerceIn(0.0, 1.0)
updateMessage()
}
override fun updateMessage() {
message = "$label: ${String.format(Locale.ROOT, "%.2f", current)}".text()
}
override fun applyValue() {
onChange(current)
}
}
}
@@ -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
@@ -12,14 +12,13 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_BAR_WIDTH
@@ -27,19 +26,14 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_BA
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SPACING
import com.cobblemon.mod.common.client.gui.pokedex.widgets.EntriesScrollingWidget.PokemonScrollSlotRow
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.util.Mth
import org.joml.Quaternionf
import org.joml.Vector3f
class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (PokedexEntry) -> (Unit)): ScrollingWidget<PokemonScrollSlotRow>(
width = PokedexGUIConstants.HALF_OVERLAY_WIDTH,
@@ -135,13 +129,6 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
val discoveryLevelList: MutableList<PokedexEntryProgress>,
val setPokedexEntry : (PokedexEntry) -> (Unit)
): Slot<PokemonScrollSlotRow>() {
companion object {
private val slotResource = cobblemonResource("textures/gui/pokedex/pokedex_slot.png")
private val slotHighlight = cobblemonResource("textures/gui/pokedex/slot_select.png")
private val caughtIcon = cobblemonResource("textures/gui/pokedex/caught_icon_small.png")
private val unknownIcon = cobblemonResource("textures/gui/pokedex/pokedex_slot_unknown.png")
private val unimplementedIcon = cobblemonResource("textures/gui/pokedex/pokedex_slot_unimplemented.png")
}
val runtime = MoLangRuntime().setupClient().setup().also {
it.environment.query.addFunction("get_pokedex") { CobblemonClient.clientPokedexData.struct }
}
@@ -161,128 +148,42 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
hovered: Boolean,
tickDelta: Float
) {
dexDataList.forEachIndexed { index, dexData ->
val state = FloatingState()
val species = PokemonSpecies.getByIdentifier(dexData.speciesId)
//FIXME: This may not work properly when accounting for custom pokemon with the same dex number
val pokemonNumber = (species?.nationalPokedexNumber?.toString() ?: "0").padStart(4, '0')
val speciesNumber = pokemonNumber.text()
dexDataList.forEachIndexed { index, pokedexEntry ->
val species = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId)
val discoveryLevel = discoveryLevelList[index]
val firstVisibleForm = CobblemonClient.clientPokedexData.getEncounteredForms(dexData).firstOrNull()
val shouldDrawMon = firstVisibleForm != null
val firstVisibleForm = CobblemonClient.clientPokedexData.getEncounteredForms(pokedexEntry).firstOrNull()
if (species == null) {
return@forEachIndexed
}
val matrices = context.pose()
val renderablePokemon =
if (firstVisibleForm != null) {
val firstVisibleGender = CobblemonClient.clientPokedexData.getSeenGenders(pokedexEntry, firstVisibleForm).firstOrNull()
val shinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(pokedexEntry, firstVisibleForm)
val firstVisibleShiny = shinyStates.count() == 1 && shinyStates.first() == "shiny"
val formAspects = species.forms.firstOrNull { it.name.equals(firstVisibleForm.displayForm, ignoreCase = true) }?.aspects ?: species.standardForm.aspects
val seenAspects = CobblemonClient.clientPokedexData.getSeenAspects(pokedexEntry)
val variationAspects = pokedexEntry.variations.mapNotNull { it.aspects.firstOrNull { it in seenAspects } }
val aspectsToDraw = (pokedexEntry.displayAspects + variationAspects + formAspects + (firstVisibleGender ?: Gender.GENDERLESS).name.lowercase()).toMutableSet()
if (firstVisibleShiny) {
aspectsToDraw.add("shiny")
}
val startPosX = x + ((SCROLL_SLOT_SPACING + SCROLL_SLOT_SIZE) * index)
val startPosY = y + SCROLL_SLOT_SPACING + 1
blitk(
matrixStack = matrices,
texture = slotResource,
x = startPosX,
y = startPosY,
width = SCROLL_SLOT_SIZE,
height = SCROLL_SLOT_SIZE
)
if (getHoveredSlotIndex(mouseX, mouseY) == index) {
blitk(
matrixStack = matrices,
texture = slotHighlight,
x = startPosX,
y = startPosY,
width = SCROLL_SLOT_SIZE,
height = SCROLL_SLOT_SIZE,
vOffset = SCROLL_SLOT_SIZE,
textureHeight = SCROLL_SLOT_SIZE * 2
)
}
if (shouldDrawMon) {
val firstVisibleGender = CobblemonClient.clientPokedexData.getSeenGenders(dexData, firstVisibleForm).firstOrNull()
val shinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(dexData, firstVisibleForm)
val firstVisibleShiny = shinyStates.count() == 1 && shinyStates.first() == "shiny"
val formAspects = species.forms.firstOrNull { it.name.equals(firstVisibleForm.displayForm, ignoreCase = true) }?.aspects ?: species.standardForm.aspects
val seenAspects = CobblemonClient.clientPokedexData.getSeenAspects(dexData)
val variationAspects = dexData.variations.mapNotNull { it.aspects.firstOrNull { it in seenAspects } }
context.enableScissor(
startPosX + 1,
startPosY + 1,
startPosX + SCROLL_SLOT_SIZE - 1,
startPosY + SCROLL_SLOT_SIZE - 2
)
val aspectsToDraw = (dexData.displayAspects + variationAspects + formAspects + (firstVisibleGender ?: Gender.GENDERLESS).name.lowercase()).toMutableSet()
if (firstVisibleShiny) {
aspectsToDraw.add("shiny")
RenderablePokemon(species, aspectsToDraw)
} else {
null
}
state.currentAspects = aspectsToDraw
matrices.pushPose()
matrices.translate(startPosX + (SCROLL_SLOT_SIZE / 2.0), startPosY + 1.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
drawProfilePokemon(
renderablePokemon = RenderablePokemon(species, aspectsToDraw),
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
partialTicks = 0F,
scale = 4.5F
)
matrices.popPose()
context.disableScissor()
} else {
blitk(
matrixStack = matrices,
texture = unknownIcon,
x = startPosX + 8.5,
y = startPosY + 9,
width = 8,
height = 10
)
if (!species.implemented) {
blitk(
matrixStack = matrices,
texture = unimplementedIcon,
x = (startPosX + 14) / SCALE,
y = (startPosY + 15.5) / SCALE,
width = 7,
height = 7,
scale = SCALE
)
}
}
// Ensure elements are not hidden behind Pokémon render
matrices.pushPose()
matrices.translate(0.0, 0.0, 100.0)
drawScaledText(
context = context,
text = speciesNumber,
x = startPosX + 1.5,//2,
y = startPosY + 2.5,//2
shadow = true,
scale = SCALE
PokemonSlotWidget.render(
context,
x = x + (SCROLL_SLOT_SPACING + SCROLL_SLOT_SIZE) * index,
y = y + SCROLL_SLOT_SPACING + 1,
getHoveredSlotIndex(mouseX, mouseY) == index,
species,
renderablePokemon,
discoveryLevel,
)
if (discoveryLevel == PokedexEntryProgress.OWNED) {
blitk(
matrixStack = matrices,
texture = caughtIcon,
x = (startPosX + 18) / SCALE,
y = (startPosY + 1.5) / SCALE,
width = 11,
height = 11,
scale = SCALE
)
}
matrices.popPose()
}
}
@@ -10,10 +10,10 @@ package com.cobblemon.mod.common.client.gui.pokedex.widgets
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexCosmeticVariation
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.text.bold
@@ -25,8 +25,9 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ProfileTransformType
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_PORTRAIT_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_PORTRAIT_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.PORTRAIT_POKE_BALL_HEIGHT
@@ -52,15 +53,13 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.joml.Quaternionf
import org.joml.Vector3f
import java.io.FileNotFoundException
class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm) -> (Unit)) : SoundlessWidget(
class PokemonInfoWidget(val parent: PokedexGUI, val pX: Int, val pY: Int, val updateForm: (PokedexForm) -> (Unit)) : SoundlessWidget(
pX,
pY,
POKEMON_PORTRAIT_WIDTH,
@@ -82,7 +81,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
private val arrowFormLeft = cobblemonResource("textures/gui/pokedex/forms_arrow_left.png")
private val arrowFormRight = cobblemonResource("textures/gui/pokedex/forms_arrow_right.png")
private val caughtIcon = cobblemonResource("textures/gui/pokedex/caught_icon.png")
private val typeBar = cobblemonResource("textures/gui/pokedex/type_bar.png")
private val typeBarDouble = cobblemonResource("textures/gui/pokedex/type_bar_double.png")
@@ -100,9 +98,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
var currentEntry : PokedexEntry? = null
var speciesName: MutableComponent = Component.translatable("")
var speciesNumber: MutableComponent = "0000".text()
val runtime = MoLangRuntime().setupClient().setup().also {
it.environment.query.addFunction("get_pokedex") { CobblemonClient.clientPokedexData.struct }
}
@@ -111,10 +106,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
var selectedFormIndex: Int = 0
var type: Array<ElementalType?> = arrayOf(null, null)
var seenShinyStates: Set<String> = setOf()
var shiny = false
var maleRatio = -1F
var gender: Gender = Gender.GENDERLESS
var renderablePokemon : RenderablePokemon? = null
@@ -195,7 +186,10 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
20,
resource = buttonGenderMale,
clickAction = {
if (maleRatio > 0 && maleRatio < 1) gender = if (gender == Gender.MALE) Gender.FEMALE else Gender.MALE
val maleRatio = parent.maleRatio
if (maleRatio > 0 && maleRatio < 1) {
parent.gender = if (parent.gender == Gender.MALE) Gender.FEMALE else Gender.MALE
}
updateAspects()
}
).apply { addWidget(this) }
@@ -207,8 +201,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
20,
buttonNone,
clickAction = {
if (seenShinyStates.size > 1) {
shiny = !shiny
if (parent.seenShinyStates.size > 1) {
parent.shiny = !parent.shiny
updateAspects()
}
}
@@ -263,8 +257,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val currentEntry = this.currentEntry ?: return
val hasKnowledge =
CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
val hasKnowledge = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
val species = currentEntry.speciesId.let { PokemonSpecies.getByIdentifier(it) } ?: return
val matrices = context.pose()
@@ -277,37 +270,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
height = HALF_OVERLAY_HEIGHT
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = speciesNumber.bold(),
x = pX + 3,
y = pY + 1,
shadow = true
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = (if (hasKnowledge) speciesName else lang("ui.generic.question_marks")).bold(),
x = pX + 26,
y = pY + 1,
colour = 0x606B6E
)
// Caught icon
if (isSelectedPokemonOwned()) {
blitk(
matrixStack = matrices,
texture = caughtIcon,
x = (pX + 129) / SCALE,
y = (pY + 2) / SCALE,
width = 14,
height = 14,
scale = SCALE
)
}
if (!suppressViewport) {
blitk(
matrixStack = matrices,
@@ -571,8 +533,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
if (species != null) {
this.visibleForms = CobblemonClient.clientPokedexData.getEncounteredForms(pokedexEntry).toMutableList()
this.speciesNumber = species.nationalPokedexNumber.toString().padStart(4, '0').text()
this.speciesName = species.translatedName
if (forms.isNotEmpty()) {
formLeftButton.active = true
@@ -598,23 +558,23 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
variationButtons.forEach { removeWidget(it.getWidget()) }
variationButtons.clear()
seenShinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(pokedexEntry, pokedexForm)
shiny = seenShinyStates.count() == 1 && seenShinyStates.first() == "shiny"
shinyButton.resource = if (shiny) buttonShiny else buttonNone
shinyButton.visible = shiny || seenShinyStates.size > 1
shinyButton.active = seenShinyStates.size > 1
parent.seenShinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(pokedexEntry, pokedexForm)
parent.shiny = parent.seenShinyStates.count() == 1 && parent.seenShinyStates.first() == "shiny"
shinyButton.resource = if (parent.shiny) buttonShiny else buttonNone
shinyButton.visible = parent.shiny || parent.seenShinyStates.size > 1
shinyButton.active = parent.seenShinyStates.size > 1
val form = species.forms.find { it.name.equals(pokedexForm.displayForm, ignoreCase = true) } ?: species.standardForm
maleRatio = form.maleRatio
parent.maleRatio = form.maleRatio
val seenGenders = CobblemonClient.clientPokedexData.getSeenGenders(pokedexEntry, pokedexForm)
val visibleGenders = seenGenders.filter { it != Gender.GENDERLESS }
if (visibleGenders.isEmpty()) {
gender = Gender.GENDERLESS
parent.gender = Gender.GENDERLESS
genderButton.visible = false
genderButton.active = false
} else {
gender = visibleGenders.first()
parent.gender = visibleGenders.first()
genderButton.visible = true
genderButton.active = visibleGenders.size > 1
}
@@ -693,8 +653,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
return
}
genderButton.resource = if (gender == Gender.FEMALE) buttonGenderFemale else buttonGenderMale
shinyButton.resource = if (shiny) buttonShiny else buttonNone
genderButton.resource = if (parent.gender == Gender.FEMALE) buttonGenderFemale else buttonGenderMale
shinyButton.resource = if (parent.shiny) buttonShiny else buttonNone
val species = currentEntry?.speciesId?.let { PokemonSpecies.getByIdentifier(it) }
if (species != null) {
@@ -703,12 +663,14 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
updateType(species, form)
val aspects = mutableSetOf<String>()
if (shiny) aspects.add(SHINY_ASPECT.aspect)
val aspects = parent.aspects
aspects.clear()
if (gender == Gender.FEMALE) {
if (parent.shiny) aspects.add(SHINY_ASPECT.aspect)
if (parent.gender == Gender.FEMALE) {
aspects.add("female")
} else if (gender == Gender.MALE) {
} else if (parent.gender == Gender.MALE) {
aspects.add("male")
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.text.bold
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.gui.pokedex.PokedexGUI
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_PORTRAIT_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_PORTRAIT_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
class PokemonNameWidget(
val parent: PokedexGUI,
val pX: Int,
val pY: Int,
) : SoundlessWidget(
pX,
pY,
POKEMON_PORTRAIT_WIDTH,
POKEMON_PORTRAIT_HEIGHT,
lang("ui.pokedex.pokemon_name"),
) {
companion object {
private val caughtIcon = cobblemonResource("textures/gui/pokedex/caught_icon.png")
}
var hasKnowledge: Boolean = false
var isOwned: Boolean = false
var speciesName: MutableComponent = Component.translatable("")
var speciesNumber: MutableComponent = "0000".text()
override fun renderWidget(
context: GuiGraphics,
mouseX: Int,
mouseY: Int,
partialTick: Float
) {
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = speciesNumber.bold(),
x = pX + 3,
y = pY + 1,
shadow = true
)
if (hasKnowledge) {
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = speciesName.bold(),
x = pX + 26,
y = pY + 1,
colour = 0x606B6E
)
}
if (isOwned) {
blitk(
matrixStack = context.pose(),
texture = caughtIcon,
x = (pX + 129) / SCALE,
y = (pY + 2) / SCALE,
width = 14,
height = 14,
scale = SCALE
)
}
}
fun update(pokedexEntry: PokedexEntry) {
val species = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId) ?: return
val progress = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(pokedexEntry.speciesId)
this.hasKnowledge = progress != PokedexEntryProgress.UNREGISTERED
this.isOwned = progress == PokedexEntryProgress.OWNED
this.speciesNumber = species.nationalPokedexNumber.toString().padStart(4, '0').text()
this.speciesName = species.translatedName
}
}
@@ -0,0 +1,151 @@
/*
* 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.pokedex.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.GuiGraphics
import org.joml.Quaternionf
import org.joml.Vector3f
abstract class PokemonSlotWidget {
companion object {
private val slotResource = cobblemonResource("textures/gui/pokedex/pokedex_slot.png")
private val slotHighlight = cobblemonResource("textures/gui/pokedex/slot_select.png")
private val caughtIcon = cobblemonResource("textures/gui/pokedex/caught_icon_small.png")
private val unknownIcon = cobblemonResource("textures/gui/pokedex/pokedex_slot_unknown.png")
private val unimplementedIcon = cobblemonResource("textures/gui/pokedex/pokedex_slot_unimplemented.png")
fun render(
context: GuiGraphics,
x: Int,
y: Int,
isHovered: Boolean,
species: Species,
renderablePokemon: RenderablePokemon?,
discoveryLevel: PokedexEntryProgress,
) {
val shouldDrawMon = renderablePokemon != null
// FIXME: This may not work properly when accounting for custom pokemon with the same dex number
val pokemonNumber = species.nationalPokedexNumber.toString().padStart(4, '0')
val speciesNumber = pokemonNumber.text()
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = slotResource,
x = x,
y = y,
width = SCROLL_SLOT_SIZE,
height = SCROLL_SLOT_SIZE,
)
if (isHovered) {
blitk(
matrixStack = matrices,
texture = slotHighlight,
x = x,
y = y,
width = SCROLL_SLOT_SIZE,
height = SCROLL_SLOT_SIZE,
vOffset = SCROLL_SLOT_SIZE,
textureHeight = SCROLL_SLOT_SIZE * 2,
)
}
if (shouldDrawMon && discoveryLevel != PokedexEntryProgress.UNREGISTERED) {
context.enableScissor(
x + 1,
y + 1,
x + SCROLL_SLOT_SIZE - 1,
y + SCROLL_SLOT_SIZE - 2,
)
matrices.pushPose()
matrices.translate(x + SCROLL_SLOT_SIZE / 2.0, y + 1.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
val state = FloatingState()
state.currentAspects = renderablePokemon.aspects
drawProfilePokemon(
renderablePokemon = renderablePokemon,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
partialTicks = 0F,
scale = 4.5F
)
matrices.popPose()
context.disableScissor()
} else {
blitk(
matrixStack = matrices,
texture = unknownIcon,
x = x + 8.5,
y = y + 9,
width = 8,
height = 10
)
if (!species.implemented) {
blitk(
matrixStack = matrices,
texture = unimplementedIcon,
x = (x + 14) / SCALE,
y = (y + 15.5) / SCALE,
width = 7,
height = 7,
scale = SCALE
)
}
}
// Ensure elements are not hidden behind Pokémon render
matrices.pushPose()
matrices.translate(0.0, 0.0, 100.0)
drawScaledText(
context = context,
text = speciesNumber,
x = x + 1.5,
y = y + 2.5,
shadow = true,
scale = SCALE
)
if (discoveryLevel == PokedexEntryProgress.OWNED) {
blitk(
matrixStack = matrices,
texture = caughtIcon,
x = (x + 18) / SCALE,
y = (y + 1.5) / SCALE,
width = 11,
height = 11,
scale = SCALE
)
}
matrices.popPose()
}
}
}
@@ -0,0 +1,143 @@
/*
* 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.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.locale.Language
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Items
internal fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
if (hasUndescribedProperty(properties)) {
return null
}
val descriptors = mutableListOf<MutableComponent>()
properties.species?.let { PokemonSpecies.getByIdentifier(cobblemonResource(it)) }?.let {
descriptors.add(it.translatedName.plainCopy().green())
}
properties.gender?.let {
descriptors.add(lang("gender.${it.name.lowercase()}").green())
}
properties.nickname?.let {
descriptors.add(lang("ui.pokedex.requirement.properties.nickname", it.plainCopy().green()))
}
properties.type?.let {
val typeName = ElementalTypes.get(it)?.displayName?.plainCopy() ?: Component.literal(it)
descriptors.add(lang("ui.pokedex.requirement.properties.type", typeName.green()))
}
properties.nature?.let {
val natureName = Natures.getNature(it.asIdentifierDefaultingNamespace())?.displayName?.asTranslated() ?: Component.literal(it)
descriptors.add(lang("ui.pokedex.requirement.properties.nature", natureName.green()))
}
properties.customProperties.forEach { customProperty ->
describeCustomProperty(customProperty)?.let { descriptors.add(it) }
}
val heldItemDescriptor = properties.heldItem?.let { heldItem ->
val itemId = heldItem.substringBefore('[').trim().asIdentifierDefaultingNamespace()
val item = BuiltInRegistries.ITEM.get(itemId)
if (item == Items.AIR) null else lang("ui.pokedex.requirement.properties.held_item", item.description.plainCopy().green())
}
if (descriptors.isEmpty() && heldItemDescriptor == null) {
return null
}
val mainDescriptor = when {
descriptors.isEmpty() -> null
descriptors.size == 1 -> descriptors[0]
else -> {
val allButLast = descriptors.dropLast(1).reduce { acc, next -> acc.add(", ").add(next) }
lang("ui.pokedex.requirement.properties.and", allButLast, descriptors.last())
}
}
return when {
mainDescriptor != null && heldItemDescriptor != null -> mainDescriptor.add(" ").add(heldItemDescriptor)
mainDescriptor != null -> mainDescriptor
else -> heldItemDescriptor
}
}
private fun hasUndescribedProperty(properties: PokemonProperties): Boolean {
return properties.form != null ||
properties.shiny != null ||
properties.level != null ||
properties.friendship != null ||
properties.fullness != null ||
properties.pokeball != null ||
properties.ability != null ||
properties.status != null ||
properties.teraType != null ||
properties.dmaxLevel != null ||
properties.gmaxFactor != null ||
properties.tradeable != null ||
properties.originalTrainerType != null ||
properties.originalTrainer != null ||
properties.moves != null ||
properties.movesetBuilders != null ||
properties.cosmeticItem != null ||
properties.scaleModifier != null ||
properties.isAlpha != null ||
properties.minPerfectIVs != null ||
properties.customProperties.any { describeCustomProperty(it) == null }
}
private fun describeCustomProperty(property: CustomPokemonProperty): MutableComponent? {
return when (property) {
is IntSpeciesFeature -> describeIntSpeciesFeature(property)
is StringSpeciesFeature -> describeStringSpeciesFeature(property)
else -> null
}
}
private fun describeIntSpeciesFeature(property: IntSpeciesFeature): MutableComponent? {
val value = Component.literal(property.value.toString())
val overrideKey = "ui.pokedex.requirement.properties.custom.${property.name}"
if (Language.getInstance().has("cobblemon.$overrideKey")) {
return lang(overrideKey, value.green())
}
val displayName = (SpeciesFeatures.getFeature(property.name) as? IntSpeciesFeatureProvider)
?.display?.name?.takeIf { it.isNotBlank() }
?: return null
return lang("ui.pokedex.requirement.properties.int_feature", value.green(), displayName.asTranslated().green())
}
private fun describeStringSpeciesFeature(property: StringSpeciesFeature): MutableComponent? {
val key = "ui.pokedex.requirement.properties.custom.${property.name}"
if (!Language.getInstance().has("cobblemon.$key")) {
return null
}
val value = PokemonSpecies.getByIdentifier(cobblemonResource(property.value))?.translatedName?.plainCopy()
?: Component.literal(property.value)
return lang(key, value.green())
}
@@ -0,0 +1,48 @@
/*
* 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.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
fun debugEvolutionRequirementRendering() {
if (!Cobblemon.config.enableDebugKeys) {
return
}
val gaps = mutableListOf<Pair<String, String>>()
for (species in PokemonSpecies.implemented) {
val evolutions = species.evolutions + species.forms.flatMap { it.evolutions }
for (evolution in evolutions) {
if (renderEvolutionData(evolution) == null) {
gaps.add("Evolution: ${evolution::class.simpleName}" to species.showdownId())
}
for (requirement in evolution.requirements) {
if (resolveRequirementData(requirement) == null) {
gaps.add("Requirement: ${requirement::class.simpleName}" to species.showdownId())
}
}
}
}
val player = Minecraft.getInstance().player
if (gaps.isEmpty()) {
player?.sendSystemMessage(Component.literal("Evolution render debug: no gaps found."))
return
}
val byType = gaps.groupBy({ it.first }, { it.second })
println("Evolution render debug found ${gaps.size} gap(s) across ${byType.size} type(s):")
byType.forEach { (type, species) -> println(" $type: ${species.distinct().joinToString(", ")}") }
player?.sendSystemMessage(Component.literal("Evolution render debug: ${gaps.size} gap(s) across ${byType.size} type(s), see log for details."))
}
@@ -0,0 +1,123 @@
/*
* 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.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.DummyEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.itemRegistry
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Item
data class RenderEvolutionData(
val icons: List<Item>,
val tooltips: List<MutableComponent>,
)
internal fun resolveRequirementData(requirement: Requirement): RenderRequirementData? {
val icon = requirement.pokedexIcon?.let {
Minecraft.getInstance()?.player?.level()?.itemRegistry?.get(it)
}
val tooltip = requirement.pokedexTooltip?.let {
Component.translatable(it)
}
if (icon != null && tooltip != null) {
return RenderRequirementData(icon, tooltip)
}
return renderRequirementData(requirement)
}
fun renderEvolutionData(evolution: Evolution): RenderEvolutionData? {
when (evolution) {
is BlockClickEvolution -> return renderBlockClickEvolution(evolution)
is DummyEvolution -> return renderDummyEvolution(evolution)
is ItemInteractionEvolution -> return renderItemInteractionEvolution(evolution)
is LevelUpEvolution -> return renderLevelUpEvolution(evolution)
is TradeEvolution -> return renderTradeEvolution(evolution)
}
return null
}
fun renderBlockClickEvolution(blockClickEvolution: BlockClickEvolution): RenderEvolutionData? {
return null
}
// TODO: Use dummyEvolution?
fun renderDummyEvolution(evolution: Evolution): RenderEvolutionData? {
return null
}
fun renderItemInteractionEvolution(itemInteractionEvolution: ItemInteractionEvolution): RenderEvolutionData? {
val item = itemInteractionEvolution.requiredContext.items.get().first().value()
val icons = mutableListOf(item)
val tooltips =
mutableListOf(lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
val requirementsDataList = itemInteractionEvolution.requirements.mapNotNull { requirement ->
resolveRequirementData(requirement)
}
requirementsDataList.forEach { requirement ->
icons.add(requirement.icon)
tooltips.add(requirement.tooltip)
}
return RenderEvolutionData(icons, tooltips)
}
fun renderLevelUpEvolution(levelUpEvolution: LevelUpEvolution): RenderEvolutionData? {
val requirementsDataList = levelUpEvolution.requirements.map { requirement ->
resolveRequirementData(requirement)
}
val icons = requirementsDataList.mapNotNull { requirementData -> requirementData?.icon }
val tooltips = requirementsDataList.mapNotNull { requirementData -> requirementData?.tooltip }
return RenderEvolutionData(icons, tooltips)
}
fun renderTradeEvolution(tradeEvolution: TradeEvolution): RenderEvolutionData? {
val icons = mutableListOf<Item>(CobblemonItems.LINK_CABLE)
val tooltips = mutableListOf<MutableComponent>()
val species = tradeEvolution.requiredContext.species
if (species == null) {
tooltips.add(lang("ui.pokedex.evolution.trade.tooltip.with_any_species"))
} else {
PokemonSpecies.getByIdentifier(cobblemonResource(species))?.let {
tooltips.add(lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.green()))
}
}
val requirementsDataList = tradeEvolution.requirements.mapNotNull { requirement ->
resolveRequirementData(requirement)
}
requirementsDataList.forEach { requirement ->
icons.add(requirement.icon)
tooltips.add(requirement.tooltip)
}
return RenderEvolutionData(icons, tooltips)
}
@@ -0,0 +1,379 @@
/*
* 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.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.structure.Structure
data class RenderRequirementData(
val icon: Item,
val tooltip: MutableComponent,
)
fun renderRequirementData(requirement: Requirement): RenderRequirementData? {
when (requirement) {
is AdvancementRequirement -> return renderAdvancementRequirement(requirement)
is AnyRequirement -> return renderAnyRequirement(requirement)
is AreaRequirement -> return renderAreaRequirement(requirement)
is AttackDefenceRatioRequirement -> return renderAttackDefenceRatioRequirement(requirement)
is BattleCriticalHitsRequirement -> return renderBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> return renderBiomeRequirement(requirement)
is BlocksTraveledRequirement -> return renderBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> return renderDamageTakenRequirement(requirement)
is DefeatRequirement -> return renderDefeatRequirement(requirement)
is FriendshipRequirement -> return renderFriendshipRequirement(requirement)
is HeldItemRequirement -> return renderHeldItemRequirement(requirement)
is LevelRequirement -> return renderLevelRequirement(requirement)
is MoonPhaseRequirement -> return renderMoonPhaseRequirement(requirement)
is MoveSetRequirement -> return renderMoveSetRequirement(requirement)
is MoveTypeRequirement -> return renderMoveTypeRequirement(requirement)
is OwnerHoldsItemRequirement -> return renderOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> return renderPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> return renderPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> return renderPropertyRangeRequirement(requirement)
is RecoilRequirement -> return renderRecoilRequirement(requirement)
is StatCompareRequirement -> return renderStatCompareRequirement(requirement)
is StatEqualRequirement -> return renderStatEqualRequirement(requirement)
is StructureRequirement -> return renderStructureRequirement(requirement)
is TimeRangeRequirement -> return renderTimeRangeRequirement(requirement)
is UseMoveRequirement -> return renderUseMoveRequirement(requirement)
is WeatherRequirement -> return renderWeatherRequirement(requirement)
is WorldRequirement -> return renderWorldRequirement(requirement)
}
return null
}
fun renderAdvancementRequirement(advancementRequirement: AdvancementRequirement): RenderRequirementData? {
val icon = Items.BOOK
val identifier = advancementRequirement.requiredAdvancement
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderAnyRequirement(anyRequirement: AnyRequirement): RenderRequirementData? {
return null
}
fun renderAreaRequirement(areaRequirement: AreaRequirement): RenderRequirementData? {
return null
}
fun renderAttackDefenceRatioRequirement(attackDefenceRatioRequirement: AttackDefenceRatioRequirement): RenderRequirementData? {
return null
}
fun renderBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): RenderRequirementData? {
val icon = CobblemonItems.MEDICINAL_LEEK
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderBiomeRequirement(biomeRequirement: BiomeRequirement): RenderRequirementData? {
val condition = biomeRequirement.biomeCondition
val anticondition = biomeRequirement.biomeAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.COMPASS
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> Component.literal(it.tag.location().path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) })
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(anticondition!!).green())
}
val playerBiome = Minecraft.getInstance()?.player?.let { it.level().getBiome(it.blockPosition()) }
if (playerBiome != null) {
val isMet = (condition == null || condition.fits(playerBiome)) && (anticondition == null || !anticondition.fits(playerBiome))
tooltip.add(if (isMet) Component.literal(" ✔").green() else Component.literal(" ✘").red())
}
return RenderRequirementData(icon, tooltip)
}
fun renderBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): RenderRequirementData? {
val icon = Items.LEATHER_BOOTS
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): RenderRequirementData? {
val icon = CobblemonItems.PUNCHING_GLOVE
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderDefeatRequirement(defeatRequirement: DefeatRequirement): RenderRequirementData? {
val description = describePokemonProperties(defeatRequirement.target) ?: return null
val icon = CobblemonItems.PUNCHING_GLOVE
val defeatedAmount = defeatRequirement.amount
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderFriendshipRequirement(friendshipRequirement: FriendshipRequirement): RenderRequirementData? {
val icon = CobblemonItems.SOOTHE_BELL
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderHeldItemRequirement(heldItemRequirement: HeldItemRequirement): RenderRequirementData? {
val item = heldItemRequirement.itemCondition.items.get().first().value()
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", item.description.plainCopy().green())
return RenderRequirementData(item, tooltip)
}
fun renderLevelRequirement(levelRequirement: LevelRequirement): RenderRequirementData? {
val minLevel = levelRequirement.minLevel
val maxLevel = levelRequirement.maxLevel
val icon = CobblemonItems.RARE_CANDY
val tooltip =
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
else
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
return RenderRequirementData(icon, tooltip)
}
fun renderMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): RenderRequirementData? {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderMoveSetRequirement(moveSetRequirement: MoveSetRequirement): RenderRequirementData? {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): RenderRequirementData? {
val type = ElementalTypes.get(moveTypeRequirement.type.name)
val icon = when (type) {
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
else -> CobblemonItems.NORMAL_GEM
}
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): RenderRequirementData? {
val item = ownerHoldsItemRequirement.itemCondition.items.get().first().value()
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
return RenderRequirementData(item, tooltip)
}
fun renderPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): RenderRequirementData? {
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
val icon = CobblemonItems.POKE_BALL
val tooltip = if (partyMemberRequirement.contains) {
lang("ui.pokedex.requirement.party_member.tooltip", description)
} else {
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
}
return RenderRequirementData(icon, tooltip)
}
fun renderPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): RenderRequirementData? {
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
val icon = Items.NAME_TAG
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
return RenderRequirementData(icon, tooltip)
}
fun renderPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): RenderRequirementData? {
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
val range = propertyRangeRequirement.range
val tooltip = lang(
"ui.pokedex.requirement.property_range.tooltip",
displayName,
Component.literal(range.first.toString()).green(),
Component.literal(range.last.toString()).green()
)
return RenderRequirementData(icon, tooltip)
}
fun renderRecoilRequirement(recoilRequirement: RecoilRequirement): RenderRequirementData? {
val icon = CobblemonItems.LIFE_ORB
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStatCompareRequirement(statCompareRequirement: StatCompareRequirement): RenderRequirementData? {
val highStat = Stats.getStat(statCompareRequirement.highStat)
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
val icon = when (highStat) {
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
}
val highStatComponent = highStat.displayName.plainCopy().green()
val lowStatComponent = lowStat.displayName.plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStatEqualRequirement(statEqualRequirement: StatEqualRequirement): RenderRequirementData? {
val statOne = Stats.getStat(statEqualRequirement.statOne)
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
val statOneComponent = statOne.displayName.plainCopy().green()
val statTwoComponent = statTwo.displayName.plainCopy().green()
val icon = CobblemonItems.FRESH_START_MOCHI
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStructureRequirement(structureRequirement: StructureRequirement): RenderRequirementData? {
val condition = structureRequirement.structureCondition
val anticondition = structureRequirement.structureAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.STRUCTURE_BLOCK
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
val path = when (it) {
is RegistryLikeTagCondition -> it.tag.location().path
is RegistryLikeIdentifierCondition -> it.identifier.path
else -> it.toString()
}
Component.literal(path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) })
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
}
return RenderRequirementData(icon, tooltip)
}
fun renderTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): RenderRequirementData? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return RenderRequirementData(icon, tooltip)
}
fun renderUseMoveRequirement(useMoveRequirement: UseMoveRequirement): RenderRequirementData? {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderWeatherRequirement(weatherRequirement: WeatherRequirement): RenderRequirementData? {
val icon: Item
val tooltip: MutableComponent
if (weatherRequirement.isThundering == true) {
icon = Items.LIGHTNING_ROD
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
} else {
icon = Items.WATER_BUCKET
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
}
return RenderRequirementData(icon, tooltip)
}
fun renderWorldRequirement(worldRequirement: WorldRequirement): RenderRequirementData? {
return null
}
@@ -0,0 +1,571 @@
/*
* 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.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.gui.*
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
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.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_DESCRIPTION_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.ScaledButton
import com.cobblemon.mod.common.client.gui.pokedex.renderTooltip
import com.cobblemon.mod.common.client.gui.pokedex.widgets.PokemonSlotWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
class EvolutionTreeWidget(
val parent: PokedexGUI,
val pX: Int,
val pY: Int,
) : SoundlessWidget(
pX, pY,
HALF_OVERLAY_WIDTH,
POKEMON_DESCRIPTION_HEIGHT,
lang("ui.pokedex.pokemon_info")
) {
companion object {
private val slotResource = cobblemonResource("textures/gui/pokedex/pokedex_slot.png")
private val expandResource = cobblemonResource("textures/gui/pokedex/expand.png")
private val minimizeResource = cobblemonResource("textures/gui/pokedex/minimize.png")
const val X_MARGIN = 180
const val Y_MARGIN_MINIMIZED = 135
const val Y_MARGIN_EXPANDED = 53
const val EXPANDED_HEIGHT = 125
const val HORIZONTAL_PADDING = 38
const val VERTICAL_PADDING = 2
const val TREE_FORK_LENGTH = 4
const val ICON_X_PADDING = -4.0
const val ICON_Y_PADDING = -4.0
const val MILLIS_BETWEEN_ICONS_CYCLE = 1000
const val ICON_SLOT_BACKGROUND_SIZE = 8
const val ICON_SLOT_PADDING = 4
const val ICON_SLOT_HALF_PADDING = ICON_SLOT_PADDING / 2
const val LINE_COLOR = 0xff1d6b85.toInt()
const val EXPAND_BUTTON_X_MARGIN = 127f
const val EXPAND_BUTTON_Y_MARGIN = -10.5f
const val EXPAND_BUTTON_SIZE = 20
private var previousRootPokedexEntry: PokedexEntry? = null
private var previousRootPokedexForm: PokedexForm? = null
}
private var rootTreeDrawingNode: TreeDrawingNode<EvolutionNode>? = null
private var treeDrawingBounds: TreeDrawingBounds? = null
private var zoomLevel: Double = 0.0
private var panningX: Double = 0.0
private var panningY: Double = 0.0
private var hoveredTooltips: List<MutableComponent>? = null
private val expandButton: ScaledButton = ScaledButton(
0f,
0f,
EXPAND_BUTTON_SIZE,
EXPAND_BUTTON_SIZE,
resource = expandResource,
clickAction = {
parent.evolutionTreeExpanded = !parent.evolutionTreeExpanded
expandButton.resource = if (parent.evolutionTreeExpanded) minimizeResource else expandResource
updatePositioning()
}
).apply { addWidget(this) }
override fun renderWidget(
guiGraphics: GuiGraphics,
mouseX: Int,
mouseY: Int,
partialTick: Float,
) {
drawScaledText(
context = guiGraphics,
font = CobblemonResources.DEFAULT_LARGE,
text = Component.translatable("cobblemon.ui.pokedex.info.evolution_tree").bold(),
x = x + 9,
y = y - 10,
shadow = true
)
expandButton.render(guiGraphics, mouseX, mouseY, partialTick)
rootTreeDrawingNode?.let {
enableScissors(guiGraphics)
renderTreeDrawingNode(guiGraphics, it, mouseX, mouseY, partialTick)
guiGraphics.disableScissor()
hoveredTooltips?.let { tooltips ->
renderTooltip(
guiGraphics,
tooltips,
mouseX,
mouseY,
partialTick,
4,
)
}
hoveredTooltips = null
}
}
fun setup(pokedexEntry: PokedexEntry?, pokedexForm: PokedexForm?) {
if (pokedexEntry == null || pokedexForm == null) return
updatePositioning()
val (rootPokedexEntry, rootPokedexForm) = Dexes.findRootDexEntryBySpeciesId(pokedexEntry.speciesId, pokedexForm.displayForm)
val rootEvolutionNode = buildEvolutionTree(rootPokedexEntry, rootPokedexForm, null)
// val isSameEntry = previousRootPokedexEntry != null && pokedexEntry?.id == previousRootPokedexEntry?.id
// val isSameForm =
// previousRootPokedexForm != null && pokedexForm?.displayForm == previousRootPokedexForm?.displayForm
// TODO: figure out how to prevent pokedex from resetting all the widgets state
// if (isSameEntry && isSameForm) {
// return
// }
previousRootPokedexEntry = pokedexEntry
previousRootPokedexForm = pokedexForm
createDrawingTree(rootEvolutionNode).also {
rootTreeDrawingNode = it
treeDrawingBounds = calculateBounds<EvolutionNode>(it)
}
zoomLevel = 0.0
panningX = 0.0
panningY = 0.0
}
fun updatePositioning() {
x = pX + X_MARGIN
y = pY + if (parent.evolutionTreeExpanded) Y_MARGIN_EXPANDED else Y_MARGIN_MINIMIZED
height = if (parent.evolutionTreeExpanded) EXPANDED_HEIGHT else POKEMON_DESCRIPTION_HEIGHT
panningX = 0.0
panningY = 0.0
expandButton.buttonX = x + EXPAND_BUTTON_X_MARGIN
expandButton.buttonY = y + EXPAND_BUTTON_Y_MARGIN
}
private fun buildEvolutionTree(
pokedexEntry: PokedexEntry,
pokedexForm: PokedexForm,
evolution: Evolution? = null,
resultProperties: PokemonProperties? = evolution?.result,
renderData: RenderEvolutionData? = evolution?.let { renderEvolutionData(it) }
): EvolutionNode {
val species = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId)!!
val formEquivalents = Dexes.getFormEquivalents(pokedexForm.displayForm)
val form = species.forms.firstOrNull { formEquivalents.contains(it.name.lowercase()) }
val parentSpecies = Dexes.findDexEntryBySpeciesId(species.resourceIdentifier)!!
val parentPokedexForm = parentSpecies.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
val aspects = resultProperties?.aspects?.toMutableSet() ?: mutableSetOf()
form?.aspects?.let { aspects.addAll(it) }
val discoveryLevel = CobblemonClient.clientPokedexData.getHighestKnowledgeFor(pokedexEntry, pokedexForm, aspects)
val evolutions = form?.evolutions ?: species.evolutions
// Avoid evolution.result.create() here: it builds a full battle-ready Pokemon (initializeMovesetFromDefault
// etc.) which relies on server-only data like CobblemonMovesetBuilders and crashes on the client
val children = evolutions.flatMap { evolution ->
val baseRenderData = renderEvolutionData(evolution)
listOfNotNull(evolution.result, evolution.shedder).mapNotNull { resultProperties ->
val resultSpecies = resultProperties.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return@mapNotNull null
val resultForm = resultSpecies.getForm(aspects + resultProperties.aspects)
val speciesId = resultSpecies.resourceIdentifier
val childPokedexEntry = Dexes.findDexEntryBySpeciesId(speciesId)
if (childPokedexEntry == null) {
Cobblemon.LOGGER.warn("Dex entry not found for species: $speciesId")
return@mapNotNull null
}
val formEquivalents = Dexes.getFormEquivalents(resultForm.name.lowercase())
val childPokedexForm = childPokedexEntry.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
val childRenderData = if (resultProperties === evolution.shedder) {
RenderEvolutionData(
icons = (baseRenderData?.icons ?: emptyList()) + CobblemonItems.POKE_BALL,
tooltips = (baseRenderData?.tooltips ?: emptyList()) + lang("ui.pokedex.evolution.shedder.tooltip"),
)
} else {
baseRenderData
}
return@mapNotNull buildEvolutionTree(childPokedexEntry, childPokedexForm, evolution, resultProperties, childRenderData)
}
}
return EvolutionNode(pokedexEntry, parentPokedexForm, aspects, discoveryLevel, children, renderData)
}
private fun renderTreeDrawingNode(
guiGraphics: GuiGraphics,
treeDrawingNode: TreeDrawingNode<EvolutionNode>,
mouseX: Int,
mouseY: Int,
delta: Float,
) {
val (treeX, treeY) = getDrawingCoordinates(treeDrawingNode)
if (treeDrawingNode.children.size == 1) {
val child = treeDrawingNode.children.first()
val (childX, _) = getDrawingCoordinates(child)
val lineStartX = treeX + SCROLL_SLOT_SIZE
val lineEndX = childX
val lineY = treeY + SCROLL_SLOT_SIZE / 2
child.value.renderData?.let { renderData ->
drawEvolutionInfo(
guiGraphics,
renderData,
lineStartX,
lineEndX,
lineY,
mouseX,
mouseY,
delta,
)
}
} else if (treeDrawingNode.children.size > 1) {
val treeForkStart = treeX + SCROLL_SLOT_SIZE
val treeForkEnd = treeX + SCROLL_SLOT_SIZE + TREE_FORK_LENGTH
val middleStart = treeY + SCROLL_SLOT_SIZE / 2
val (_, firstChildY) = getDrawingCoordinates(treeDrawingNode.children.first())
val (_, lastChildY) = getDrawingCoordinates(treeDrawingNode.children.last())
guiGraphics.hLine(
treeForkStart,
treeForkEnd,
middleStart,
LINE_COLOR,
)
guiGraphics.vLine(
treeForkEnd,
firstChildY + SCROLL_SLOT_SIZE / 2,
lastChildY + SCROLL_SLOT_SIZE / 2,
LINE_COLOR,
)
treeDrawingNode.children.forEach { child ->
val (childX, childY) = getDrawingCoordinates(child)
val lineStartX = treeForkEnd
val lineEndX = childX
val lineY = childY + SCROLL_SLOT_SIZE / 2
child.value.renderData?.let { renderData ->
drawEvolutionInfo(
guiGraphics,
renderData,
lineStartX,
lineEndX,
lineY,
mouseX,
mouseY,
delta,
)
}
}
}
treeDrawingNode.children.forEach { child ->
renderTreeDrawingNode(guiGraphics, child, mouseX, mouseY, delta)
}
val isVisible =
treeX >= x - SCROLL_SLOT_SIZE &&
treeX <= x + width &&
treeY >= y - SCROLL_SLOT_SIZE &&
treeY <= y + height
if (!isVisible) return
val evolutionNode = treeDrawingNode.value
val isSlotHovered =
isElementHovered(mouseX, mouseY, treeX, treeX + SCROLL_SLOT_SIZE, treeY, treeY + SCROLL_SLOT_SIZE)
val discoveryLevel = evolutionNode.pokedexEntryProgress
if (isSlotHovered) {
val text =
if (discoveryLevel == PokedexEntryProgress.UNREGISTERED) {
lang("ui.pokedex.unknown")
} else {
val species = PokemonSpecies.getByIdentifier(evolutionNode.pokedexEntry.speciesId)!!
val speciesComponent = species.translatedName
val formName = evolutionNode.pokedexForm.displayForm.lowercase()
val formComponent =
if (formName == "normal") null
else lang("ui.pokedex.info.form.${species}-${formName.replace("-", "")}")
formComponent?.add(" ")?.add(speciesComponent) ?: speciesComponent
}
guiGraphics.disableScissor()
renderTooltip(
context = guiGraphics,
text = text,
posX = mouseX,
posY = mouseY,
delta,
4,
)
enableScissors(guiGraphics)
}
val pokedexEntry = evolutionNode.pokedexEntry
val pokedexForm = evolutionNode.pokedexForm
val species = PokemonSpecies.getByIdentifier(evolutionNode.pokedexEntry.speciesId)!!
val encounteredForms = CobblemonClient.clientPokedexData.getEncounteredForms(pokedexEntry)
val formToRender = encounteredForms.firstOrNull { it.displayForm.equals(pokedexForm.displayForm, ignoreCase = true) }
val renderablePokemon =
if (formToRender != null) {
val seenGenders = CobblemonClient.clientPokedexData.getSeenGenders(pokedexEntry, formToRender)
val seenShinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(pokedexEntry, formToRender)
val aspectsToDraw = (pokedexEntry.displayAspects + evolutionNode.aspects).toMutableSet()
val formAspects = species.forms.firstOrNull { it.name.equals(formToRender.displayForm, ignoreCase = true) }?.aspects
if (formAspects != null) {
aspectsToDraw.addAll(formAspects)
}
if (seenGenders.contains(parent.gender)) {
aspectsToDraw.add(parent.gender.name.lowercase())
} else {
aspectsToDraw.add((seenGenders.firstOrNull() ?: Gender.GENDERLESS).name.lowercase())
}
if (parent.shiny && seenShinyStates.contains("shiny")) {
aspectsToDraw.add("shiny")
}
RenderablePokemon(species, aspectsToDraw)
} else {
null
}
PokemonSlotWidget.render(
context = guiGraphics,
x = treeX,
y = treeY,
isHovered = isSlotHovered,
species = species,
renderablePokemon = renderablePokemon,
discoveryLevel = discoveryLevel,
)
}
private fun drawEvolutionInfo(
guiGraphics: GuiGraphics,
renderData: RenderEvolutionData,
lineStartX: Int,
lineEndX: Int,
lineY: Int,
mouseX: Int,
mouseY: Int,
delta: Float,
) {
val lineXMiddle = (lineStartX + lineEndX) / 2
val iconX = (lineXMiddle + ICON_X_PADDING - ICON_SLOT_HALF_PADDING).toInt()
val iconY = (lineY + ICON_Y_PADDING - ICON_SLOT_HALF_PADDING).toInt()
val iconSize = ICON_SLOT_BACKGROUND_SIZE + ICON_SLOT_PADDING
val isVisible =
iconX >= x - iconSize &&
iconX <= x + width &&
iconY >= y - iconSize &&
iconY <= y + height
if (!isVisible) {
return
}
if (renderData.icons.isEmpty()) {
guiGraphics.hLine(
lineStartX,
lineEndX,
lineY,
LINE_COLOR,
)
} else {
guiGraphics.hLine(
lineStartX,
iconX - 1,
lineY,
LINE_COLOR,
)
guiGraphics.hLine(
iconX + iconSize,
lineEndX,
lineY,
LINE_COLOR,
)
blitk(
matrixStack = guiGraphics.pose(),
texture = slotResource,
x = iconX,
y = iconY,
width = iconSize,
height = iconSize,
)
val index = (System.currentTimeMillis() / MILLIS_BETWEEN_ICONS_CYCLE) % renderData.icons.size
val item = renderData.icons[index.toInt()]
renderScaledGuiItemIcon(
itemStack = item.defaultInstance,
x = lineXMiddle + ICON_X_PADDING,
y = lineY + ICON_Y_PADDING,
matrixStack = guiGraphics.pose(),
scale = PokedexGUIConstants.SCALE.toDouble(),
)
}
val isLineHovered = isElementHovered(mouseX, mouseY, lineStartX, lineEndX, lineY - 1, lineY + 1)
val isIconHovered = isElementHovered(
mouseX,
mouseY,
iconX.toInt(),
iconX.toInt() + iconSize,
iconY.toInt(),
iconY.toInt() + iconSize,
)
if (renderData.tooltips.isNotEmpty() &&
(isLineHovered || isIconHovered) &&
this.isHovered(mouseX.toDouble(), mouseY.toDouble())
) {
hoveredTooltips = renderData.tooltips
}
}
private fun getDrawingCoordinates(node: TreeDrawingNode<EvolutionNode>): Pair<Int, Int> {
val halfSlot = SCROLL_SLOT_SIZE / 2f
val xStep = halfSlot + HORIZONTAL_PADDING
val yStep = halfSlot + VERTICAL_PADDING
// Swaps the x and y to draw the tree horizontally
val xDislocation = (node.y - treeDrawingBounds!!.yCenter) * xStep
val yDislocation = (node.x - treeDrawingBounds!!.xCenter) * yStep
val xCenter = width / 2f - halfSlot
val yCenter = height / 2f - halfSlot
val screenX = (xCenter.toInt() + xDislocation.toInt() + panningX.toInt() + x)
val screenY = (yCenter.toInt() + yDislocation.toInt() + panningY.toInt() + y)
return screenX to screenY
}
private fun isElementHovered(
mouseX: Int,
mouseY: Int,
startX: Int,
endX: Int,
startY: Int,
endY: Int,
): Boolean {
return mouseX >= startX && mouseX <= endX && mouseY >= startY && mouseY <= endY
}
override fun mouseDragged(
mouseX: Double,
mouseY: Double,
button: Int,
deltaX: Double,
deltaY: Double
): Boolean {
panningX += deltaX
panningY += deltaY
val bounds = treeDrawingBounds ?: return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
val treeHeight = bounds.height.coerceAtLeast(1)
val treeWidth = bounds.width.coerceAtLeast(1)
val xStep = HORIZONTAL_PADDING + SCROLL_SLOT_SIZE / 2.0
val yStep = VERTICAL_PADDING + SCROLL_SLOT_SIZE / 2.0
val minPanningX = (treeHeight - 1) * -xStep / 2.0 - (width + SCROLL_SLOT_SIZE) / 2.0
val maxPanningX = (treeHeight - 1) * xStep / 2.0 + (width + SCROLL_SLOT_SIZE) / 2.0
val divideBy = if (treeWidth % 2 == 0) 4.0 else 2.0
val minPanningY = (treeWidth - 1) * -yStep / 2.0 - height / 2.0 + SCROLL_SLOT_SIZE / divideBy
val maxPanningY = (treeWidth - 1) * yStep / 2.0 + height / 2.0 - SCROLL_SLOT_SIZE / divideBy
panningX = panningX.coerceIn(minPanningX, maxPanningX)
panningY = panningY.coerceIn(minPanningY, maxPanningY)
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
return mouseX.toInt() in x..(x + width) && mouseY.toInt() in y..(y + height)
}
fun enableScissors(guiGraphics: GuiGraphics) {
guiGraphics.enableScissor(x + 1, y, x + width - 1, y + height)
}
private class EvolutionNode(
val pokedexEntry: PokedexEntry,
val pokedexForm: PokedexForm,
val aspects: Set<String>,
val pokedexEntryProgress: PokedexEntryProgress,
override val children: List<EvolutionNode>,
val renderData: RenderEvolutionData?,
) : TreeNode<EvolutionNode>
}
@@ -8,12 +8,10 @@
package com.cobblemon.mod.common.client.gui.startselection.widgets
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.PokemonGUIAnimationStyle
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionList
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.startselection.StarterSelectionScreen
@@ -29,12 +27,11 @@ import org.joml.Quaternionf
import org.joml.Vector3f
class CategoryList(
private val paneWidth: Int,
private val paneHeight: Int,
paneWidth: Int,
paneHeight: Int,
private val categories: List<RenderableStarterCategory>,
val listX: Int,
val listY: Int,
private val minecraft: Minecraft = Minecraft.getInstance(),
private val starterSelectionScreen: StarterSelectionScreen
) : VariableObjectSelectionList(
listX,
@@ -70,7 +67,8 @@ class CategoryList(
}
private fun createEntries() = categories.map { category ->
val rows = (category.pokemon.size - 1) / 3 + 1
val pokemonCount = category.pokemon.size - if (category.randomStarter) 0 else 1
val rows = pokemonCount / 3 + 1
val height = CONTAINER_BUTTON_SIZE * rows + BUTTON_SEPERATION * (rows + 1) + HEADER_HEIGHT + 3
Category(category, height)
}
@@ -11,11 +11,14 @@ package com.cobblemon.mod.common.client.keybind.keybinds
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinding
import com.cobblemon.mod.common.client.gui.debug.speed.SpeedTuner
import com.cobblemon.mod.common.client.gui.debug.speed.SpeedTunerScreen
import com.cobblemon.mod.common.client.keybind.KeybindCategories
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
import com.cobblemon.mod.common.net.messages.server.debug.RequestOpenSpeedTunerPacket
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
@@ -30,6 +33,7 @@ object DebugKeybindings {
TranslateDownKeybinding(),
PrintModelSettingsKeybinding(),
ToggleRidingStatsDebugGUIKeybinding(),
ToggleSpeedTunerKeybinding(),
)
class ScaleUpKeybinding: CobblemonKeyBinding(
@@ -163,4 +167,20 @@ object DebugKeybindings {
}
}
}
class ToggleSpeedTunerKeybinding : CobblemonKeyBinding(
"key.cobblemon.togglespeedtuner",
InputConstants.Type.KEYSYM,
InputConstants.KEY_APOSTROPHE,
KeybindCategories.COBBLEMON_DEBUG_CATEGORY
) {
override fun onPress() {
// A running session just needs its panel back, otherwise the server checks permission and sends speeds
if (SpeedTuner.active) {
Minecraft.getInstance().setScreen(SpeedTunerScreen())
} else {
RequestOpenSpeedTunerPacket().sendToServer()
}
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.debug
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.gui.debug.speed.SpeedTuner
import com.cobblemon.mod.common.net.messages.client.debug.OpenSpeedTunerPacket
import net.minecraft.client.Minecraft
object OpenSpeedTunerHandler : ClientNetworkPacketHandler<OpenSpeedTunerPacket> {
override fun handle(packet: OpenSpeedTunerPacket, client: Minecraft) {
SpeedTuner.open(packet.entries)
}
}
@@ -8,9 +8,9 @@
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.block.entity.DiscShelfBlockEntity
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.client.render.color.TechnicalMachineItemColorProvider
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
@@ -37,15 +37,15 @@ class DiscShelfBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) :
private val slotHeightPixels = 2
private val textureSize = 16f
private val slotUMax = slotWidthPixels / textureSize
private val slotVMax = slotHeightPixels / textureSize
private val leftMargin = 1f
private val rightMargin = 1f
private val topMargin = 1f
private val interRowSpacing = 0f
private val tmOverlayDepthOffset = -0.0002f
private val slotBrightnessMultiplier = 0.65f
private val tmBaseTexture = cobblemonResource("textures/block/disc_shelf/technical_machine_base.png")
private val tmOverlayTexture = cobblemonResource("textures/block/disc_shelf/technical_machine_overlay.png")
private val musicDiscUnknownTexture = cobblemonResource("textures/block/disc_shelf/music_disc_unknown.png")
override fun render(
@@ -98,35 +98,19 @@ class DiscShelfBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) :
poseStack.translate(posX, posY, -0.001f)
if (item.item == CobblemonItems.TECHNICAL_MACHINE) {
val baseColor = TechnicalMachineItemColorProvider.getColor(item, 0)
val overlayColor = TechnicalMachineItemColorProvider.getColor(item, 1)
val elementId = item.components.get(CobblemonItemComponents.TM_MOVE)!!.move.elementalType.showdownId
val texture = cobblemonResource("textures/block/disc_shelf/${elementId}_tm.png")
renderSlotQuad(
poseStack,
buffer,
tmBaseTexture,
baseColor,
texture,
0xFFFFFF,
packedLight,
slotWidth,
slotHeight,
tint = true,
useTextRenderType = true
)
poseStack.pushPose()
poseStack.translate(0.0, 0.0, tmOverlayDepthOffset.toDouble())
renderSlotQuad(
poseStack,
buffer,
tmOverlayTexture,
overlayColor,
packedLight,
slotWidth,
slotHeight,
tint = true,
useTextRenderType = true
)
poseStack.popPose()
} else {
val tex = getItemTexture(item)
renderSlotQuad(
@@ -176,13 +160,15 @@ class DiscShelfBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) :
val g = (gBase * slotBrightnessMultiplier).coerceIn(0f, 1f)
val b = (bBase * slotBrightnessMultiplier).coerceIn(0f, 1f)
consumer.addVertex(matrix, 0f, 0f, 0f).setColor(r, g, b, a).setUv(0f, 1f)
// The quad faces -Z, so a viewer looking at it sees +X on their left.
// U runs from slotUMax at x = 0 to 0 at x = width so the art is not mirrored.
consumer.addVertex(matrix, 0f, 0f, 0f).setColor(r, g, b, a).setUv(slotUMax, slotVMax)
.setUv1(0, 0).setUv2(light and 0xFFFF, light shr 16).setNormal(0f, 0f, -1f)
consumer.addVertex(matrix, 0f, height, 0f).setColor(r, g, b, a).setUv(0f, 0f)
consumer.addVertex(matrix, 0f, height, 0f).setColor(r, g, b, a).setUv(slotUMax, 0f)
.setUv1(0, 0).setUv2(light and 0xFFFF, light shr 16).setNormal(0f, 0f, -1f)
consumer.addVertex(matrix, width, height, 0f).setColor(r, g, b, a).setUv(1f, 0f)
consumer.addVertex(matrix, width, height, 0f).setColor(r, g, b, a).setUv(0f, 0f)
.setUv1(0, 0).setUv2(light and 0xFFFF, light shr 16).setNormal(0f, 0f, -1f)
consumer.addVertex(matrix, width, 0f, 0f).setColor(r, g, b, a).setUv(1f, 1f)
consumer.addVertex(matrix, width, 0f, 0f).setColor(r, g, b, a).setUv(0f, slotVMax)
.setUv1(0, 0).setUv2(light and 0xFFFF, light shr 16).setNormal(0f, 0f, -1f)
}
@@ -1,27 +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.render.color
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.item.components.TMMoveComponent.Companion.getTMMove
import net.minecraft.client.color.item.ItemColor
import net.minecraft.util.FastColor
import net.minecraft.world.item.ItemStack
object TechnicalMachineItemColorProvider : ItemColor {
override fun getColor(itemStack: ItemStack, layer: Int): Int {
val moveType = getTMMove(itemStack)?.elementalType ?: ElementalTypes.NORMAL
val color = when (layer) {
0 -> moveType.primaryColor
1 -> moveType.secondaryColor
else -> 0xFFFFFF
}
return FastColor.ARGB32.opaque(color)
}
}
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.layer
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.model.PlayerModel
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.entity.RenderLayerParent
import net.minecraft.client.renderer.entity.layers.RenderLayer
import net.minecraft.world.entity.HumanoidArm
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
class PlayerRecallBallRenderer<T : Player>(
renderLayerParent: RenderLayerParent<T, PlayerModel<T>>
) : RenderLayer<T, PlayerModel<T>>(renderLayerParent) {
override fun render(
matrixStack: PoseStack,
buffer: MultiBufferSource,
packedLight: Int,
player: T,
limbSwing: Float,
limbSwingAmount: Float,
partialTicks: Float,
ageInTicks: Float,
netHeadYaw: Float,
headPitch: Float
) {
val anim = RecallArmAnimationTracker.get(player.uuid) ?: return
val mc = Minecraft.getInstance()
if (player === mc.player && mc.options.cameraType.isFirstPerson) return
matrixStack.pushPose()
getParentModel().translateToHand(HumanoidArm.RIGHT, matrixStack)
matrixStack.mulPose(Axis.XP.rotationDegrees(-90F))
matrixStack.mulPose(Axis.YP.rotationDegrees(180F))
matrixStack.translate(0.0625, 0.125, -0.625)
matrixStack.scale(0.54F, -0.54F, -0.54F)
matrixStack.translate(0.0, 0.5, 0.0)
matrixStack.mulPose(Axis.YP.rotationDegrees(180F))
RecallArmAnimationTracker.renderBall(anim, matrixStack, buffer, packedLight, partialTicks)
anim.ballState.locatorStates["beam"]?.let { beam ->
matrixStack.pushPose()
matrixStack.mulPose(beam.matrix)
val offset = matrixStack.last().pose().transformPosition(Vector3f(0F, 0.18F, -0.3F))
matrixStack.popPose()
anim.beamOrigin = mc.gameRenderer.mainCamera.position.add(Vec3(offset))
}
matrixStack.popPose()
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.render.layer
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.client.render.item.HeldItemRenderer
@@ -23,7 +24,6 @@ import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.isPokemonEntity
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import java.util.UUID
import net.minecraft.client.Minecraft
import net.minecraft.client.model.PlayerModel
@@ -33,11 +33,14 @@ import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.renderer.entity.RenderLayerParent
import net.minecraft.client.renderer.entity.layers.RenderLayer
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ArmorItem
import net.minecraft.world.item.ItemStack
class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent<T, PlayerModel<T>>) :
@@ -165,16 +168,16 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
val width = shoulderData.form.hitbox.width
val heightOffset = -1.5 * scale
val widthOffset = width / 2 - 0.7
// If they're sneaking, the pokemon needs to rotate a little bit and push forward
// Shoulders move a bit when sneaking which is why the translation is necessary.
// Shoulder exact rotation according to testing (miasmus) is 0.4 radians, the -0.15 is eyeballed though.
if (livingEntity.isCrouching) {
matrixStack.mulPose(Axis.XP.rotation(0.4F))
matrixStack.translate(0F, 0F, -0.15F)
val chestItem = livingEntity.getItemBySlot(EquipmentSlot.CHEST)
val armorHeightOffset = if (chestItem.item is ArmorItem && !isArmorOffsetBlacklisted(chestItem)) {
ARMOR_HEIGHT_OFFSET
} else {
0F
}
parentModel.body.translateAndRotate(matrixStack)
matrixStack.translate(
if (pLeftShoulder) -widthOffset else widthOffset,
(if (livingEntity.isCrouching) heightOffset + 0.2 else heightOffset),
heightOffset - armorHeightOffset,
0.0
)
@@ -263,8 +266,14 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
companion object {
private const val ARMOR_HEIGHT_OFFSET = 1F / 16F
private val playerCache = hashMapOf<UUID, ShoulderCache>()
private fun isArmorOffsetBlacklisted(itemStack: ItemStack): Boolean {
val itemIdentifier = BuiltInRegistries.ITEM.getKey(itemStack.item)
return itemIdentifier in Cobblemon.config.shoulderMountArmorOffsetBlacklist
}
private fun extractUuid(shoulderNbt: CompoundTag): UUID {
if (!shoulderNbt.contains(DataKeys.SHOULDER_UUID)) {
return shoulderNbt.getCompound(DataKeys.POKEMON).getUUID(DataKeys.POKEMON_UUID)
@@ -48,6 +48,7 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import com.cobblemon.mod.common.client.render.pokemon.ModelPitch
import com.mojang.math.Axis
import net.minecraft.Util
import net.minecraft.client.model.geom.ModelPart
@@ -776,7 +777,14 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
matrixStack.mulPose(transformationMatrix)
} else {
yRot = Mth.lerp(state.getPartialTicks(), entity.yBodyRotO, entity.yBodyRot)
yRot = 180f - Mth.wrapDegrees(yRot)
val tilt = ModelPitch.tiltDegrees(entity, state.getPartialTicks())
if (tilt != 0F) {
// Same as the model tilt in PokemonRenderer
matrixStack.mulPose(ModelPitch.aroundCentre(entity, ModelPitch.orientation(yRot, tilt)))
yRot = 0F
} else {
yRot = 180f - Mth.wrapDegrees(yRot)
}
}
} else if (entity is EmptyPokeBallEntity) {
scale = 0.7F
@@ -19,7 +19,7 @@ import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.util.RidingAnimationData
import com.cobblemon.mod.common.api.riding.util.DynamicAnimationData
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
@@ -141,7 +141,7 @@ abstract class PosableState : Schedulable {
*/
val renderMarkers = mutableSetOf<String>()
/** The MoLang functions that expose current [RidingAnimationData] data.
/** The MoLang functions that expose current [DynamicAnimationData] data.
* They are all divided by a reference value and coerece into a float range useful in animation
*/
val ridingFunctions = QueryStruct(hashMapOf())
@@ -10,23 +10,20 @@ package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.util.Mth
/**
* Animation used to tilt the targeted bone of the entity by a pitch that is proportionate
* to the direction the entity is moving. This is used to make birds and fish tilt upwards
* or downwards based on their motion.
* or downwards based on their motion. The pitch comes from the eased heading locomotion travels on.
*
* @author Hiroku
* @since August 9th, 2025
*/
class PitchTiltAnimation(
val bone: Bone,
// Not used anymore since the heading is already eased, kept so existing posers still load
val maxChangePerTick: Float = 1F,
val minPitch: Float = -45F,
val maxPitch: Float = 45F,
@@ -35,6 +32,8 @@ class PitchTiltAnimation(
const val PREVIOUS_ANGLE = "previous_pitch_tilt_angle"
const val CORRECTED_ANGLE = "corrected_pitch_tilt_angle"
const val PITCHED_TILT = "has_pitched_tilt"
// How much of the tilt has been applied this render, so poses blending together add up to one full tilt
const val APPLIED_WEIGHT = "applied_pitch_tilt_weight"
}
override fun setupAnim(
@@ -48,36 +47,7 @@ class PitchTiltAnimation(
headPitch: Float,
intensity: Float
) {
val entity = context.entity ?: return
if (entity.passengers.isNotEmpty()) {
return
}
val delta = entity.deltaMovement
val vertical = delta.y
val horizontal = delta.horizontalDistance()
// It should be the angle in degrees that becomes higher as the entity's motion becomes more upwards, and negative as the entity's motion becomes more downwards
val angleOfMotion = if (horizontal == 0.0) {
when {
vertical > 0 -> maxPitch
vertical < 0 -> minPitch
else -> 0F
}
} else {
Mth.atan2(vertical, horizontal).toDegrees()
}.coerceIn(minPitch, maxPitch) * -1
val lastAngle = state.numbers[PREVIOUS_ANGLE] ?: 0F
val change = (angleOfMotion - lastAngle).coerceIn(-maxChangePerTick, maxChangePerTick)
if (PITCHED_TILT !in state.renderMarkers) {
state.numbers.remove(CORRECTED_ANGLE)
val correctedAngle = (lastAngle + change) * intensity
bone.addRotation(0, correctedAngle.toRadians())
state.numbers[PREVIOUS_ANGLE] = correctedAngle
} else {
// It was already pitched, which means the body bone already has a rotation, so only rotate it a pinch more.
bone.addRotation(0, change.toRadians() * intensity)
state.numbers[PREVIOUS_ANGLE] = lastAngle + change * intensity
}
state.renderMarkers += PITCHED_TILT
// The whole model is tilted by ModelPitch now so this does nothing\
//TODO: remove posers with this anim if we like ModelPitch
}
}
@@ -262,9 +262,9 @@ data class BedrockAnimation(
fun applyEffects(entity: Entity?, state: PosableState, previousSeconds: Float, newSeconds: Float) {
val effectCondition: (effectKeyframe: BedrockEffectKeyframe) -> Boolean =
if (previousSeconds > newSeconds) {
{ it.seconds >= previousSeconds || it.seconds <= newSeconds }
{ it.seconds >= previousSeconds || it.seconds < newSeconds }
} else {
{ it.seconds in previousSeconds..newSeconds }
{ it.seconds >= previousSeconds && it.seconds < newSeconds }
}
effects.filter(effectCondition).forEach { it.run(entity, state) }
@@ -1,104 +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.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.WaveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.WaveSegment
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.X_AXIS
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.Y_AXIS
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class EkansModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("ekans")
private val body = getPart("body")
override val head = getPart("head")
private val tail = getPart("tail")
private val tail2 = getPart("tail2")
private val tail3 = getPart("tail3")
private val tail4 = getPart("tail4")
private val tail5 = getPart("tail5")
private val tail6 = getPart("tail6")
val tailSegment = WaveSegment(modelPart = tail, length = 9F)
val tail2Segment = WaveSegment(modelPart = tail2, length = 9F)
val tail3Segment = WaveSegment(modelPart = tail3, length = 9F)
val tail4Segment = WaveSegment(modelPart = tail4, length = 9F)
val tail5Segment = WaveSegment(modelPart = tail5, length = 10F)
val tail6Segment = WaveSegment(modelPart = tail6, length = 10F)
override var portraitScale = 1.7F
override var portraitTranslation = Vec3(-0.3, -0.45, 0.0)
override var profileScale = 0.7F
override var profileTranslation = Vec3(-0.05, 0.6, 0.0)
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("ekans", "cry") }
override fun registerPoses() {
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("ekans", "sleep"))
)
val blink = quirk { bedrockStateful("ekans", "blink") }
registerPose(
poseName = "normal",
poseTypes = STATIONARY_POSES + MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("ekans", "ground_idle"),
WaveAnimation(
waveFunction = sineFunction(
period = 8F,
amplitude = 0.8F
),
basedOnLimbSwing = true,
oscillationsScalar = 5F,
head = head,
rotationAxis = Y_AXIS,
motionAxis = X_AXIS,
moveHead = false,
headLength = 16F,
segments = arrayOf(
tailSegment,
tail2Segment,
tail3Segment,
tail4Segment,
tail5Segment,
tail6Segment
)
)
)
)
registerPose(
poseName = "portrait",
poseTypes = UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(bedrock("ekans", "summary_idle"))
)
}
override fun getFaintAnimation(state: PosableState) = bedrockStateful("ekans", "faint")
}
@@ -1,63 +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.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class GloomModel(root: ModelPart) : PokemonPosableModel(root) {
override val rootPart = root.registerChildWithAllChildren("gloom")
override var portraitScale = 1.8F
override var portraitTranslation = Vec3(-0.0, -1.0, 0.0)
override var profileScale = 1.05F
override var profileTranslation = Vec3(0.0, 0.19, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("gloom", "cry") }
override fun registerPoses() {
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
animations = arrayOf(
bedrock("gloom", "ground_idle")
)
)
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("gloom", "sleep"))
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
animations = arrayOf(
bedrock("gloom", "ground_walk")
)
)
}
override fun getFaintAnimation(state: PosableState) = bedrockStateful("gloom", "faint")
}
@@ -1,66 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class OddishModel(root: ModelPart) : PokemonPosableModel(root) {
override val rootPart = root.registerChildWithAllChildren("oddish")
override var portraitScale = 2.0F
override var portraitTranslation = Vec3(-0.0, -1.0, 0.0)
override var profileScale = 0.85F
override var profileTranslation = Vec3(0.0, 0.5, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("oddish", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("oddish", "blink")}
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("oddish", "ground_idle")
)
)
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("oddish", "sleep"))
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("oddish", "ground_walk")
)
)
}
override fun getFaintAnimation(state: PosableState) = bedrockStateful("oddish", "faint")
}
@@ -1,75 +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.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.STANDING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class PoliwagModel(root: ModelPart) : PokemonPosableModel(root) {
override val rootPart = root.registerChildWithAllChildren("poliwag")
override var portraitScale = 1.8F
override var portraitTranslation = Vec3(0.0, -0.7, 0.0)
override var profileScale = 0.9F
override var profileTranslation = Vec3(0.0, 0.38, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var float: Pose
lateinit var swim: Pose
override val cryAnimation = CryProvider { bedrockStateful("poliwag", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("poliwag", "blink")}
standing = registerPose(
poseName = "standing",
poseTypes = STANDING_POSES + UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("poliwag", "ground_idle")
)
)
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("poliwag", "sleep"))
)
float = registerPose(
poseName = "float",
poseType = PoseType.FLOAT,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("poliwag", "water_idle")
)
)
swim = registerPose(
poseName = "swim",
poseType = PoseType.SWIM,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("poliwag", "water_swim")
)
)
}
// override fun getFaintAnimation(
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("poliwag", "faint") else null
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
}
@@ -1,66 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class VileplumeModel(root: ModelPart) : PokemonPosableModel(root) {
override val rootPart = root.registerChildWithAllChildren("vileplume")
override var portraitScale = 1.8F
override var portraitTranslation = Vec3(-0.35, -0.8, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.53, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("vileplume", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("vileplume", "blink")}
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("vileplume", "idle")
)
)
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("vileplume", "sleep"))
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("vileplume", "ground_walk")
)
)
}
override fun getFaintAnimation(state: PosableState) = bedrockStateful("vileplume", "faint")
}
@@ -1,95 +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.render.models.blockbench.pokemon.gen2
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.SHOULDER_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class BellossomModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("bellossom")
override val head = getPart("head")
override var portraitScale = 1.8F
override var portraitTranslation = Vec3(0.0, -0.2, 0.0)
override var profileScale = 1.0F
override var profileTranslation = Vec3(0.0, 0.21, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var shoulderLeft: Pose
lateinit var shoulderRight: Pose
val shoulderOffset = 5
override fun registerPoses() {
val blink = quirk { bedrockStateful("bellossom", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("bellossom", "ground_idle"),
singleBoneLook()
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("bellossom", "ground_idle"),
bedrock("bellossom", "ground_walk"),
singleBoneLook()
)
)
shoulderLeft = registerPose(
poseType = PoseType.SHOULDER_LEFT,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("bellossom", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, shoulderOffset)
)
)
shoulderRight = registerPose(
poseType = PoseType.SHOULDER_RIGHT,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("bellossom", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, -shoulderOffset)
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("bellossom", "faint") else null
}
@@ -1,80 +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.render.models.blockbench.pokemon.gen2
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class PolitoedModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("politoed")
override val head = getPart("head")
override val leftLeg = getPart("leftleg")
override val rightLeg = getPart("rightleg")
override var portraitScale = 2.4F
override var portraitTranslation = Vec3(-0.04, -0.55, 0.0)
override var profileScale = 1.1F
override var profileTranslation = Vec3(0.0, 0.1, 0.0)
lateinit var standing: Pose
lateinit var walking: Pose
lateinit var float: Pose
lateinit var swim: Pose
override fun registerPoses() {
standing = registerPose(
poseName = "standing",
poseTypes = UI_POSES + PoseType.STAND,
animations = arrayOf(
singleBoneLook(),
bedrock("politoed", "ground_idle")
)
)
walking = registerPose(
poseName = "walking",
poseType = PoseType.WALK,
animations = arrayOf(
singleBoneLook(),
bedrock("politoed", "ground_idle")
)
)
float = registerPose(
poseName = "float",
poseTypes = setOf(PoseType.FLOAT, PoseType.HOVER),
animations = arrayOf(
singleBoneLook(),
bedrock("politoed", "water_idle")
)
)
swim = registerPose(
poseName = "swim",
poseTypes = setOf(PoseType.SWIM, PoseType.FLY),
animations = arrayOf(
singleBoneLook(),
bedrock("politoed", "water_swim")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("politoed", "faint") else null
}
@@ -1,72 +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.render.models.blockbench.pokemon.gen2
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BimanualSwingAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class SudowoodoModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("sudowoodo")
override val head = getPart("head")
override val leftArm = getPart("arm_left")
override val rightArm = getPart("arm_right")
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
override var portraitScale = 1.9F
override var portraitTranslation = Vec3(-0.7, 1.6, 0.0)
override var profileScale = 0.5F
override var profileTranslation = Vec3(-0.1, 0.95, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override fun registerPoses() {
val blink = quirk { bedrockStateful("sudowoodo", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(yawMultiplier = 0F),
bedrock("sudowoodo", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(yawMultiplier = 0F),
bedrock("sudowoodo", "ground_idle"),
BipedWalkAnimation(this, periodMultiplier = 0.6F, amplitudeMultiplier = 0.9F),
BimanualSwingAnimation(this, swingPeriodMultiplier = 0.6F, amplitudeMultiplier = 0.9F)
//bedrock("sudowoodo", "ground_walk")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("sudowoodo", "faint") else null
}
@@ -1,91 +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.render.models.blockbench.pokemon.gen3
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ShroomishModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("shroomish")
override val head = getPart("torso")
override val leftLeg = getPart("foot_left")
override val rightLeg = getPart("foot_right")
override var portraitScale = 2.0F
override var portraitTranslation = Vec3(-0.1, -1.2, 0.0)
override var profileScale = 1.0F
override var profileTranslation = Vec3(0.0, 0.3, 0.0)
lateinit var standing: Pose
lateinit var battling: Pose
lateinit var sleeping: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("shroomish", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("shroomish", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(pitchMultiplier = 0.5F),
bedrock("shroomish", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(pitchMultiplier = 0.5F),
bedrock("shroomish", "ground_walk")
)
)
battling = registerPose(
poseName = "battlestanding",
poseTypes = STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
singleBoneLook(pitchMultiplier = 0.5F),
bedrock("shroomish", "battle_idle")
)
)
sleeping = registerPose(
poseName = "sleeping",
poseType = PoseType.SLEEP,
transformTicks = 10,
animations = arrayOf(
bedrock("shroomish", "sleep")
)
)
}
override fun getFaintAnimation(state: PosableState) = if (state.isPosedIn(standing, walk, battling, sleeping)) bedrockStateful("shroomish", "faint") else null
}
@@ -1,102 +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.render.models.blockbench.pokemon.gen4
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class BonslyModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("bonsly")
override val head = getPart("torso")
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.2, -0.8, 0.0)
override var profileScale = 0.72F
override var profileTranslation = Vec3(0.0, 0.65, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var battleidle: Pose
lateinit var sleep: Pose
lateinit var battlesleep: Pose
override fun registerPoses() {
val blink = quirk { bedrockStateful("bonsly", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("bonsly", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("bonsly", "ground_walk")
)
)
sleep = registerPose(
poseName = "sleep",
poseType = PoseType.SLEEP,
condition = { !it.isBattling },
animations = arrayOf(
bedrock("bonsly", "sleep_pot")
)
)
battlesleep = registerPose(
poseName = "battlesleep",
poseType = PoseType.SLEEP,
condition = { it.isBattling },
animations = arrayOf(
bedrock("bonsly", "sleep")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("bonsly", "battle_idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk))
// bedrockStateful("bonsly", "faint")
// else null
}
Loaded 100 of 1407 files, more files were not shown because too many files have changed in this diff. Show more