Compare commits

...
Author SHA1 Message Date
jackowes 714030458b first person render roll changes 2026-10-27 18:50:48 -05:00
jackowes 411a728282 bird changes 2026-10-27 18:25:51 -05:00
jackowes dddb44a9fd Merge the two mouse input paths into one single steer() 2026-10-26 23:07:51 -05:00
jackowes ede8bdf0e3 initial pass at ride camera refactor and ride velocity refactor 2026-10-26 22:33:14 -05:00
jackowes ef79cf79e3 camera fixes 2026-10-25 20:39:05 -05:00
jackowes 2b0128aed0 Port spiderpig board/climb work onto main 2026-10-25 20:28:36 -05:00
Polymeta c9d148ec9c bump showdown build and changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-26 00:41:23 +00:00
Polymeta e611bf4a77 Merge branch 'fix-missing-recipe-book-data' into 'main'
Fixed Cooking Pot recipe book tabs and entries on Fabric

See merge request cable-mc/cobblemon!2510
2026-10-26 00:37:47 +00:00
Polymeta 8093822140 Merge branch 'battle-selection-fix' into 'main'
Fixed a bug with battle selection.

See merge request cable-mc/cobblemon!2509
2026-10-26 00:27:00 +00:00
Fyre520 f695ec15f9 Fixed Cooking Pot recipe book tabs and entries on Fabric 2026-10-26 02:01:15 +05:30
Deltric 1f6c791ce6 Fixed a bug with battle selection. 2026-10-25 14:24:44 -04:00
Polymeta 570805fdf1 Merge branch 'structure-tags-for-spawning' into 'main'
Structure Spawn Tags

See merge request cable-mc/cobblemon!2508
2026-10-25 16:53:38 +00:00
dhi_holo ddecb08bc7 Edit is_illager_structure.json 2026-10-26 00:21:36 +10:00
Hiroku 4d7cfcdf61 Added q.target_fainted to MoveInstruction 2026-10-25 11:11:52 +10:00
Hiroku e56dd6ef64 snap it back on 2026-10-25 10:43:01 +10:00
Hiroku ffe005511d but what would snapshot look like when it's false? 2026-10-25 10:03:22 +10:00
Hiroku 1e634a20b8 Merge remote-tracking branch 'origin/lang-updates' 2026-10-25 09:57:49 +10:00
Polymeta f9f4745e93 Merge branch 'implement_pokemon' into 'main'
Some recent riding stuff adjustments

See merge request cable-mc/cobblemon!2506
2026-10-24 16:23:07 +00:00
Kenji_64 c700674289 Fearow animations updated (again) and added all placeholders I made to the changelog 2026-10-24 18:05:05 +02:00
Kenji_64 c4f0032eee Fixed Squawkabilly's missing toe 2026-10-24 15:24:17 +02:00
Kenji_64 d92986016b Adjusted Fearow's ride stats further 2026-10-24 02:12:39 +02:00
Kenji_64 ab57e01242 Fearow is now rideable with temporary animations 2026-10-24 01:46:41 +02:00
Kenji_64 0da1c740a4 Adjusted Altaria ride stats 2026-10-24 01:44:26 +02:00
Kenji_64 a7979aa80c Adjusted Togekiss ride stats. Changed style from jet to bird 2026-10-24 01:44:10 +02:00
Polymeta 59804a4e08 rescrape spawns and drops
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-23 23:36:25 +01:00
Polymeta 072c67acad Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2496
2026-10-23 22:30:54 +00:00
ewwww 60bbe221de tweaked vikavolt's jump stat 2026-10-23 17:51:10 -04:00
ewwww 5f06dc214b added vikavolt ride stats 2026-10-22 22:51:05 -04:00
ewwww a6607e585b changed vikavolt to bird style 2026-10-22 21:45:56 -04:00
Gitoido Okaski 20584f4507 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-23 01:02:35 +03:00
Kenji_64 9660f1f724 Fixed Appletun having a green tongue. (Likely from food coloring...) 2026-10-23 00:31:07 +03:00
Kenji_64 c8cbace800 Fixed Fearow's model being messed up and added the head_ai back 2026-10-23 00:31:07 +03:00
Kenji_64 a616a24923 Fixed Swampert's weird head_ai 2026-10-23 00:31:07 +03:00
Gitoido Okaski 12035dd589 chore: mudkip animation call fix, resolver orderings streamlined 2026-10-23 00:31:07 +03:00
Kenji_64 9627b80b01 Added Ninetales + alolan placeholder animations 2026-10-23 00:31:07 +03:00
Kenji_64 4e9cecc854 Converted Squawkabilly's poser to JSON and added placeholder animations 2026-10-23 00:31:06 +03:00
ewwww 9d4b19b185 vikavolt can be mounted (stat placeholders) 2026-10-23 00:31:06 +03:00
Kenji_64 aee96d06aa Made Pichu shut up in its sleep 2026-10-23 00:31:06 +03:00
Kenji_64 23e3c585f7 Wave 2 of profile adjustments 2026-10-23 00:31:06 +03:00
Kenji_64 c22a88079f Added Fearow placeholders and fixed its neck (also adjusted Swampert placeholder) 2026-10-23 00:31:06 +03:00
Kenji_64 2225402082 Added transparency to Vikavolt 2026-10-23 00:31:06 +03:00
Kenji_64 9197207431 Implemented Magby and Magmar animations 2026-10-23 00:31:06 +03:00
Kenji_64 47c7f7079c Added placeholder animations to Mudkip, Marshtomp, Swampert and Spearow 2026-10-23 00:31:06 +03:00
Kenji_64 eb0c1dddfd Scaled down a huge lot of small Pokémon's profiles and fixed some that were off over all 2026-10-23 00:31:06 +03:00
Kenji_64 e3dbfe6ff8 Added Eerie Spell activate and Hospitality lang (Hospitality's text does not work properly atm due to a Doubles bug) 2026-10-23 00:31:06 +03:00
whatsy fac207f36b Revert "Rescrape drops"
This reverts commit 6a7d5d2133.
2026-10-23 00:31:06 +03:00
ewwww 379e794848 removed igglybuff, jigglypuff, and wigglytuff's rights to float 2026-10-23 00:31:06 +03:00
ewwww 9081a4b131 added water shuriken fx 2026-10-23 00:31:06 +03:00
ewwww dc6d75b8ee sped up cacnea and hippowdon's walk speed 2026-10-23 00:31:06 +03:00
ewwww bb297a4448 fixed t-posing archeops during air_cry 2026-10-23 00:31:06 +03:00
whatsy a93922c438 Rescrape drops 2026-10-23 00:31:06 +03:00
Kenji_64 f7d21c80d7 Fixed Appletun having a green tongue. (Likely from food coloring...) 2026-10-22 23:16:45 +02:00
Kenji_64 9248d26fd4 Fixed Fearow's model being messed up and added the head_ai back 2026-10-22 23:03:50 +02:00
dhi_holo b5ac54cd16 Tweaks to prepare for Scrapes 2026-10-21 14:22:17 +10:00
dhi_holo dc703e67f7 Merge branch 'main' into structure-tags-for-spawning 2026-10-21 14:01:10 +10:00
Kenji_64 0930721865 Fixed Swampert's weird head_ai 2026-10-19 16:34:31 +02:00
Gitoido Okaski 644ff1772e chore: mudkip animation call fix, resolver orderings streamlined 2026-10-19 16:56:42 +03:00
Kenji_64 3c7e0bebb6 Added Ninetales + alolan placeholder animations 2026-10-19 15:44:42 +02:00
Kenji_64 0c0eea3876 Converted Squawkabilly's poser to JSON and added placeholder animations 2026-10-19 15:44:20 +02:00
ewwww 5535140bb7 vikavolt can be mounted (stat placeholders) 2026-10-19 03:37:39 -04:00
Polymeta f9dd1448e5 Merge branch 'item-2072' into 'main'
Added tab completion for the alpha property and three missing property aliases

Closes #2072

See merge request cable-mc/cobblemon!2388
2026-10-18 22:33:30 +00:00
Gitoido ecf5d57ad2 chore: resolve conflicts
# Conflicts:
#   common/src/main/kotlin/com/cobblemon/mod/common/pokemon/properties/PropertiesCompletionProvider.kt
2026-10-18 22:16:27 +00:00
Polymeta 8663888abc Merge branch 'item-2013' into 'main'
Fixed GUI Pokémon cries playing on the Master category instead of Friendly Creatures

Closes #2013

See merge request cable-mc/cobblemon!2389
2026-10-18 22:14:04 +00:00
Polymeta f452cd9a05 Merge branch 'fix/ride-interact-checks' into 'main'
fix: stricter riding checks on server side

Closes #2124

See merge request cable-mc/cobblemon!2499
2026-10-18 22:12:19 +00:00
Kiritiop 4fe9cd7fd6 Fixed GUI pokemon cries using the Master volume slider instead of Friendly Creatures, added a new file to prevent code duplication. Credit to Gitoido 2026-10-18 13:19:07 -04:00
Kenji_64 9b0ca1c16b Made Pichu shut up in its sleep 2026-10-18 18:46:52 +02:00
Kenji_64 052ff59f57 Wave 2 of profile adjustments 2026-10-18 18:46:15 +02:00
Kenji_64 40e5ef1c46 Added Fearow placeholders and fixed its neck (also adjusted Swampert placeholder) 2026-10-18 18:45:31 +02:00
Kenji_64 5025977d23 Added transparency to Vikavolt 2026-10-18 18:44:22 +02:00
Kenji_64 922006afe7 Implemented Magby and Magmar animations 2026-10-18 16:14:45 +02:00
Kenji_64 46cc7cb04d Added placeholder animations to Mudkip, Marshtomp, Swampert and Spearow 2026-10-18 15:48:35 +02:00
Kenji_64 fa3e3bbb1b Scaled down a huge lot of small Pokémon's profiles and fixed some that were off over all 2026-10-18 15:47:46 +02:00
Kenji_64 afbdcce337 Added Eerie Spell activate and Hospitality lang (Hospitality's text does not work properly atm due to a Doubles bug) 2026-10-18 15:46:19 +02:00
Gitoido Okaski 2c1d11ed11 chore: remove duplicate check that was accounted for earlier 2026-10-18 15:29:35 +03:00
Gitoido Okaski b88adf0479 fix: stricter riding checks on server side 2026-10-18 15:21:09 +03:00
Polymeta b1066671f5 Merge branch 'quick-fix' into 'main'
Flowerbed Clearning jigsaw fix, dripstone oasis jigsaw removal

See merge request cable-mc/cobblemon!2497
2026-10-18 12:09:48 +00:00
Polymeta 387e85d4ad Merge branch 'fix/preset-conditions-mergers' into 'main'
fix: Some spawn conditions merge minHeight/Depth data into max instead

Closes #2111

See merge request cable-mc/cobblemon!2498
2026-10-18 12:07:55 +00:00
Nico Merenq 1fa113f5ac Translated using Weblate (Russian)
Currently translated at 84.8% (8137 of 9587 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-10-18 11:56:30 +00:00
Gitoido Okaski 77ab58d823 fix: spawn condition mergers writing minHeight/depth into maxHeight/depth instead 2026-10-18 14:14:28 +03:00
Slippy 1a9e736d8b Flowerbed Clearning jigsaw fix, dripstone oasis jigsaw removal 2026-10-18 00:11:56 -05:00
whatsy f680682f61 Revert "Rescrape drops"
This reverts commit 6a7d5d2133.
2026-10-17 22:51:02 -04:00
whatsy 4d93d1244d Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-17 22:40:25 -04:00
whatsy 6a7d5d2133 Rescrape drops 2026-10-17 22:40:13 -04:00
ewwww 98397cd1da removed igglybuff, jigglypuff, and wigglytuff's rights to float 2026-10-17 21:57:59 -04:00
ewwww fc5a610394 added water shuriken fx 2026-10-17 20:37:00 -04:00
ewwww 504df5d998 sped up cacnea and hippowdon's walk speed 2026-10-17 20:16:26 -04:00
ewwww 3b4d3ebf50 fixed t-posing archeops during air_cry 2026-10-17 20:01:56 -04:00
Hiroku aa39c1906e Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2495
2026-10-17 23:48:54 +00:00
ewwww 2e460e2ca2 fixed floating frogadier 2026-10-17 19:43:44 -04:00
Polymeta a07ff7f442 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2494
2026-10-17 23:37:18 +00:00
Polymeta 5374569ea1 [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-18 00:15:37 +01:00
Polymeta af9c1196bf youre meant to be looking the other way
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-18 00:08:51 +01:00
Polymeta 09d5543469 Merge branch 'fix/haters-gonna-hate-properly' into 'main'
fix: Anger should be directed at proper target

Closes #2112

See merge request cable-mc/cobblemon!2492
2026-10-17 23:03:41 +00:00
ewwww 49e4dd0298 helioptile & heliolisk cries 2026-10-17 19:01:46 -04:00
Polymeta c8e408c50a actually copy components when building up evolution displays
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-17 23:57:05 +01:00
Gitoido Okaski e8a0467643 chore: move coercion at class variable level 2026-10-18 01:17:07 +03:00
Gitoido Okaski 2dd0b66b83 chore: remove duplicate i18n key 2026-10-18 01:04:29 +03:00
Gitoido Okaski 68374017e1 chore: changelog 2026-10-18 01:04:29 +03:00
ewwww 120520f51d vikavolt animations + cries for grubbin, charjabug, & vikavolt 2026-10-17 18:01:25 -04:00
Gitoido Okaski e8dea9773a feat: make sure zangoose and zeviper 100% hate each other 2026-10-18 00:59:40 +03:00
Gitoido Okaski 7b31e6bcc4 feat: make sure hate task uses new values from config and gamerule 2026-10-18 00:59:10 +03:00
Gitoido Okaski c6ffca639f feat: pokemon predation chance config i18n 2026-10-18 00:58:40 +03:00
Gitoido Okaski c491a1f7ca feat: introduce pokemon predation chance config 2026-10-18 00:56:06 +03:00
Gitoido Okaski 0b333b743d feat: introduce pokemon predation gamerule 2026-10-18 00:55:54 +03:00
ewwww 95e79865e5 updated posers for fennekin & braixen 2026-10-17 16:24:40 -04:00
Gitoido Okaski 0e2860cd90 chore: import cleanup skip_ci 2026-10-17 23:14:08 +03:00
Gitoido Okaski 3259bf032c chore: changelog skip_ci 2026-10-17 23:10:15 +03:00
Gitoido Okaski 116205cdf1 fix: Anger should be directed at proper target 2026-10-17 20:07:18 +00:00
ewwww 43277d18dc Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-17 13:37:05 -04:00
Polymeta 86153419ee Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2480
2026-10-17 17:36:00 +00:00
Gitoido Okaski 2b4d9c451c fix: Various poser/animation fixes (Wave 3.4.1) 2026-10-17 20:07:17 +03:00
Gitoido Okaski 7f0e2fa9d8 fix: Various poser/animation fixes (Wave 3.3) 2026-10-17 19:43:00 +03:00
Gitoido Okaski e165095bd5 fix: Various poser/animation fixes (Wave 3.2.1) 2026-10-17 19:38:06 +03:00
Gitoido Okaski b8708d6e10 fix: Various poser/animation fixes (Wave 3.2) 2026-10-17 19:36:25 +03:00
Kenji_64 ef536390ca Various poser/animation fixes (Wave 3.1). The rats make me crazy. 2026-10-17 18:27:28 +02:00
ewwww 28befab173 cries for combee & vespiquen 2026-10-17 12:22:20 -04:00
ewwww c142439d5b cries for wurmple, silcoon, cascoon. beautifly, & dustox 2026-10-17 12:21:49 -04:00
Jerzykv 1ca7335654 Various poser/animation fixes (Wave 3) 2026-10-17 18:18:39 +02:00
ewwww 9197d9cdcd cries for runerigus 2026-10-17 11:52:04 -04:00
ewwww f75813e64d cries for dusknoir 2026-10-17 11:45:44 -04:00
ewwww 1f617c76a4 cries for torkoal 2026-10-17 11:45:20 -04:00
ewwww ebf573aa7a cries for surskit & masquerain 2026-10-17 11:44:29 -04:00
Kenji_64 75363da470 Various poser/animation fixes (Wave 2.5) 2026-10-17 17:42:04 +02:00
ewwww cce4b932cc cries for hoppip, skiploom, & jumpluff 2026-10-17 11:41:45 -04:00
Kenji_64 0973e1ced0 Various poser/animation fixes (Wave 2) 2026-10-17 17:31:30 +02:00
Kenji_64 e7b5c84024 Various poser/animation fixes 2026-10-17 13:43:58 +02:00
ewwww 4001cc2a89 vikavolt animations + cries for grubbin, charjabug, & vikavolt 2026-10-17 06:08:31 -04:00
Polymeta 6e57d77d10 Merge branch 'More-Structure-Fixes' into 'main'
More structure fixes

See merge request cable-mc/cobblemon!2491
2026-10-16 22:54:46 +00:00
Slippy 2206d96a6e Lots of fixing 2026-10-16 14:26:55 -05:00
Slippy 33f05858f1 structure fixes for .3 2026-10-16 13:34:04 -05:00
Watuneni iteite 8443b88c16 Translated using Weblate (Japanese)
Currently translated at 99.0% (9500 of 9587 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-16 17:12:33 +00:00
ewwww a4f204a5a3 changelog 2026-10-16 06:06:11 -04:00
ewwww 31b6be2164 applin & flapple cry updates + cries for appletun & dipplin 2026-10-16 06:06:00 -04:00
ewwww 0dbf73067b houndour & houndoom cries 2026-10-16 06:03:48 -04:00
ewwww 775703e1f5 starly, staravia, and staraptor cries 2026-10-16 06:03:20 -04:00
Polymeta bcdfef4886 Merge branch 'lights-out' into 'main'
Bye Bye Magmarizer Glow

See merge request cable-mc/cobblemon!2490
2026-10-16 06:49:58 +00:00
dhi_holo a19adb463b Bye Bye Magmarizer Glow
- Removed Dynamic Lights data from the Magmarizer item for consistency with the block
2026-10-16 16:32:05 +10:00
Gustavo Puerari b71208299f Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.7% (9276 of 9587 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-10-15 18:07:01 +00:00
Watuneni iteite 136b5824b0 Translated using Weblate (Japanese)
Currently translated at 98.7% (9464 of 9587 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-15 18:06:58 +00:00
Polymeta 5004edbbb6 Merge branch 'fix/party-overlay-hide-nf' into 'main'
fix: party overlay not hiding when player opens the pokedex scanner

Closes #2071

See merge request cable-mc/cobblemon!2467
2026-10-15 15:37:49 +00:00
Gitoido Okaski d8bdd6329a fix: tidy-up even more 2026-10-15 17:57:42 +03:00
Gitoido Okaski 82d56637a0 chore: method signature name 2026-10-15 17:07:28 +03:00
Gitoido Okaski 7232a43816 refactor: move and enqueue client event registration to separate fn, homogenizing behavior with common side registration 2026-10-15 17:05:50 +03:00
Gitoido Okaski 098eb7d14b chore: tidy up changelog skip-ci 2026-10-15 16:22:30 +03:00
Gitoido Okaski 835b70ed48 feat: AppleSkin compat for dex scanner mode 2026-10-15 13:20:53 +00:00
Gitoido 9d828a86fb fix: party overlay not hiding when player opens the pokedex scanner 2026-10-15 13:20:53 +00:00
Polymeta 910551ee70 Merge branch 'fix/berry-pots' into 'main'
fix: flower pots require some tending on neoforge

Closes #2126

See merge request cable-mc/cobblemon!2486
2026-10-15 07:17:23 +00:00
ewwww 9beee548d4 Merge branch 'main' into implement_pokemon 2026-10-15 01:30:50 -04:00
Watuneni iteite f9189b265f Translated using Weblate (Japanese)
Currently translated at 98.7% (9464 of 9587 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-14 15:27:44 +00:00
Polymeta 6f3894f27b fix playermolang sometimes failing when they die
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-14 13:33:40 +01:00
Polymeta bbc9a5e62e Merge branch 'campfire-book-lies' into 'main'
Recipe Book Lies No More

See merge request cable-mc/cobblemon!2487
2026-10-14 11:32:52 +00:00
Gitoido Okaski 94765da04a chore: small tidy-up 2026-10-14 14:11:30 +03:00
dhi_holo 8caa82f123 Recipe Book Lies No More
Changed the icon for the misc category
2026-10-14 18:42:58 +10:00
dhi_holo 8e1fcc6f14 Structure Spawn Tags
Feel free to rename, delete, or rework any of these, it's moreso just setting the groundwork

`spawns/is_aquatic_monument` - Ocean Monuments and related structures
`spawns/is_aquatic_ruins` - Ocean Ruins and similar structures
`spawns/is_civilization` - Village and Towns tag, for anything that should spawn at any kind of village/town
`spawns/is_desert_monument` - Desert Pyramids and Temples
`spawns/is_dungeon` - Modded tag, the idea is things of the Stronghold *vibe*, but not being literal strongholds
`spawns/is_end_structure` - End Cities and related structures
`spawns/is_graveyard` - Modded tag, graveyards and crypts
`spawns/is_igloo` - just the vanilla igloo, it's me just wanting stuff in tags where it isn't yet so if some addon wants to give igloos another place they can
`spawns/is_illager_outpost` - small settlements for illagers, such as campsites and other small watchtower-like things
`spawns/is_illager_structure` - Bigger illager structures, the mansion, manors, stuff that has a sense of "infrastructure" I suppose
`spawns/is_jungle_monument` - Jungle Temples and Pyramids
`spawns/is_nether_structure` - is a structure of the Nether
`spawns/is_piglin_structure` - is a structure specifically inhabited by Piglins. Can be used to either add stuff specifically around Piglins, or to prevent things around them
`spawns/is_ruins` - Taking what we have as ruins and just applying that logic to other mods
`spawns/is_shipwreck` - not sure if we use this for spawns, but if we do this can be hooked into, if not then this could be deleted, no hassle, just felt like a logical thing to include. Is related to "surface" shipwrecks, not things like the coves
`spawns/is_witch_structure` - witch structures, like huts, villages, etc. Also added any structures that force black cat spawning, as that's used to reference to witches in the vanilla tags

Mods used within the tags so far:
https://modrinth.com/mod/dungeons-and-taverns (`nova_structures`)
A lot of https://modrinth.com/organization/nuVP8uUG mods (the Yung structure ones, usually starts as `better` and then the structure name)
https://modrinth.com/mod/enderscape
https://modrinth.com/mod/aquamirae
https://modrinth.com/mod/terralith
Will continue to investigate mods to add to these
2026-10-14 17:54:34 +10:00
Watuneni iteite 21c4d0233d Translated using Weblate (Japanese)
Currently translated at 98.7% (9464 of 9587 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-13 12:09:59 +00:00
Hiroku 2f87d53af6 Merge remote-tracking branch 'origin/lang-updates' 2026-10-13 17:34:12 +10:00
Gitoido Okaski 131ef31492 fix: tend to the pots registration on neoforge 2026-10-13 03:38:13 +03:00
Gitoido Okaski b6b067b1d3 chore: changelog skip-ci 2026-10-13 03:35:28 +03:00
RedGenesectNinja 546f10eca7 Gave Audino its alpha eyes back 2026-10-12 19:19:05 -05:00
Polymeta 542839ba69 bump neoforge version
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-12 14:36:24 +01:00
Landon efab1bba08 Merge branch 'fix/custom-choice-feature-tab-completion' into 'main'
fix: choice properties must always tab complete

See merge request cable-mc/cobblemon!2483
2026-10-12 08:42:42 +00:00
Landon 8f986bc326 Merge branch 'fix/molang-fn-changes' into 'main'
fix: behavior of `q.pokemon.nature` and `q.entity.is_looking_at`

Closes #2113

See merge request cable-mc/cobblemon!2482
2026-10-12 08:41:14 +00:00
Polymeta e155d32aaf small pokeball fix
Closes #1601

Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-12 08:54:52 +01:00
Polymeta 743e0b7d66 changelog [skip-ci]
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-12 07:49:14 +01:00
Polymeta 1518d703f9 Merge branch 'restore-the-menu' into 'main'
Fixed Mixins for Recipe Book causing conflict issues with other mods.

See merge request cable-mc/cobblemon!2484
2026-10-12 06:44:16 +00:00
Gertimoshka af9d851d67 Fixed Mixins being aggresive, causing massive issues with mod compatibility 2026-10-12 00:17:09 +02:00
Polymeta 20ca91f1c9 fix friendship booster item tag location
Closes #2125

Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-11 21:15:53 +01:00
Polymeta 6fb3c0e49a more evo dex fixes and crashes
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-11 21:12:22 +01:00
Watuneni iteite 5cb4ff5f5f Translated using Weblate (Japanese)
Currently translated at 100.0% (9464 of 9464 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-11 15:26:12 +00:00
Gitoido d035c08a5c chore: tidy up imports 2026-10-11 13:43:38 +03:00
Gitoido fda7b13f50 fix: choice properties must always tab complete 2026-10-11 13:41:21 +03:00
Kiritiop fb4aaed220 Edit CHANGELOG.md 2026-10-11 08:57:22 +00:00
Kiritiop c5e1b37ead Manual rebase 2026-10-11 08:53:30 +00:00
Kiritiop 2be9d303ea Added tab completion for the alpha property and three missing property aliases
Closes #2072
2026-10-11 08:53:30 +00:00
Kiritiop 99ac6d213a Added tab completion for the alpha property and three missing property aliases
Closes #2072
2026-10-11 08:53:30 +00:00
Gitoido f595724971 fix: q.entity.is_looking_at now returns proper results based on target passed 2026-10-11 10:55:46 +03:00
Gitoido 0511ba147b fix: use serialized name in q.pokemon.nature molang fn 2026-10-11 10:49:23 +03:00
Polymeta 760f48d850 Merge branch 'evo-tree-fumble' into 'main'
Evo tree now throws a warning instead of crashing when failing to build a mon's evo tree

See merge request cable-mc/cobblemon!2481
2026-10-11 06:41:08 +00:00
lion bf296b4921 Evo tree now throws a warning instead of crashing when failing to build a mon's evo tree 2026-10-11 06:41:08 +00:00
ewwww 6f4aae8b7d grubbin, charjabug, and vikavolt are marked as implemented 2026-10-10 23:33:06 -04:00
ewwww 01e5e74e64 snover and abomasnow are marked as implemented 2026-10-10 23:32:49 -04:00
ewwww b6565a5dee greavard and houndstone are marked as implemented 2026-10-10 23:29:38 -04:00
Hiroku 49886389cf Fine the snapshot can be true again 2026-10-11 12:57:43 +10:00
Hiroku 643bf9068b But what if the snapshot was false 2026-10-11 10:54:55 +10:00
Hiroku 53f3258246 Merge remote-tracking branch 'origin/lang-updates' 2026-10-11 10:53:42 +10:00
Hiroku 1e9895ec84 buncha changelog updates [skip-ci] 2026-10-11 10:49:11 +10:00
Polymeta 52dacb03ae Merge branch 'evo-tree-pls-behave' into 'main'
Evo tree small adjustments

See merge request cable-mc/cobblemon!2479
2026-10-10 23:47:53 +00:00
lion 85b0f3f393 Evo tree small adjustments 2026-10-10 23:47:53 +00:00
Polymeta a04a91bf24 Merge branch 'feature/evolution-mania' into 'main'
tweak some evo requirements

See merge request cable-mc/cobblemon!2476
2026-10-10 21:25:13 +00:00
Polymeta 5e814f7ab5 tweak some evo requirements 2026-10-10 21:25:13 +00:00
Polymeta 2780f491d6 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2478
2026-10-10 19:44:59 +00:00
ewwww 2d3ec7aff1 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-10 14:51:28 -04:00
ewwww cb7baf2696 changelog 2026-10-10 14:51:12 -04:00
Polymeta 5cc069df78 Merge branch 'implement_pokemon' into 'main'
Dipplin and Hydrapple shiny fixes

See merge request cable-mc/cobblemon!2477
2026-10-10 16:03:26 +00:00
Polymeta 9222476c1f Merge branch 'fix/qa-bundle' into 'main'
fix: an assortment of qa bugs

Closes #2070

See merge request cable-mc/cobblemon!2473
2026-10-10 15:40:51 +00:00
Kenji_64 f220755f99 Dipplin and Hydrapple shiny fixes 2026-10-10 17:31:21 +02:00
Gitoido d55bbf237c chore: move scale changes to other place for now 2026-10-10 18:22:31 +03:00
Watuneni iteite 83a9d44c3a Translated using Weblate (Japanese)
Currently translated at 100.0% (9472 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-10 14:22:54 +00:00
Polymeta c5a802eaf6 changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-10 15:04:26 +01:00
Polymeta 2e27875460 Merge branch 'lang-updates' 2026-10-10 15:04:06 +01:00
Polymeta cd66887faa [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-10 14:11:50 +01:00
Polymeta d665c87c21 more spawn tweaks
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-10 14:02:26 +01:00
Polymeta 044d3102b6 Merge branch 'fix-evo-tree-crash' into 'main'
Fix grotle evolution crashing evo tree and species features not synching to the client

See merge request cable-mc/cobblemon!2456
2026-10-10 12:51:04 +00:00
lion c421a93b06 Fix grotle evolution crashing evo tree and species features not synching to the client 2026-10-10 12:51:03 +00:00
Polymeta 2865aa2272 Merge branch 'implement_pokemon' into 'main'
poser tweaks + jellicent placeholders

See merge request cable-mc/cobblemon!2474
2026-10-10 12:15:04 +00:00
Polymeta 7c4aae3e9d Merge branch '1.8.2-tag-fixes' into 'main'
Fixes to regional tags.

See merge request cable-mc/cobblemon!2475
2026-10-10 12:14:09 +00:00
Veraxiel d9f671f2a8 Fixes to regional tags. 2026-10-10 07:00:14 -04:00
ewwww 69733b530b poser tweaks + jellicent placeholders 2026-10-10 05:44:57 -04:00
Gitoido d4471c30bc fix: use effective scale instead of scale modifier to get a size category for summary/scanner/molang 2026-10-10 09:38:02 +03:00
Gitoido 185f7e03f5 chore: changelog skip-ci 2026-10-10 09:24:24 +03:00
Gitoido a45eb0eb21 fix: close and open pc gui silently via command 2026-10-10 09:18:30 +03:00
Polymeta ed67142379 Merge branch '1.8.2-tag-fixes' into 'main'
Updates to some tags to fix some errors and add future biome support.

See merge request cable-mc/cobblemon!2472
2026-10-10 05:46:36 +00:00
Gitoido 13169c6848 fix: align oricorio pau form translation keys 2026-10-10 08:35:25 +03:00
Veraxiel 8be134ee2f More fixes. 2026-10-09 21:13:26 -04:00
Veraxiel 8c3206a413 Updates to some tags to fix some errors and add future biome support. 2026-10-09 20:03:32 -04:00
Watuneni iteite 4d4c071b95 Translated using Weblate (Japanese)
Currently translated at 100.0% (9472 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-09 23:21:40 +00:00
Polymeta b82c27754f Merge branch 'fix/minior-random-core-color' into 'main'
fix: minior core color should be decided randomly

See merge request cable-mc/cobblemon!2471
2026-10-09 16:57:27 +00:00
Polymeta 63afec184e Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2463
2026-10-09 16:41:24 +00:00
Gitoido 65699f045e fix: minior core color should be decided randomly 2026-10-09 19:38:34 +03:00
Polymeta d02441a273 Merge branch 'fix/mooshtank-checks' into 'main'
fix: added checks for mooshtank aspect, denying usage of bowl on common miltanks

See merge request cable-mc/cobblemon!2468
2026-10-09 15:25:11 +00:00
Gitoido f76ab784b5 fix: item stack must be copied into lastFlowerFed 2026-10-09 18:06:43 +03:00
Polymeta a566e36754 Merge branch 'fix/tms-consumption-tmm-screen' into 'main'
fix: tm stack not consumed on use in tmm screen

See merge request cable-mc/cobblemon!2466
2026-10-09 14:31:51 +00:00
Polymeta ef1479fde6 Merge branch 'fix/equip-one-not-whole' into 'main'
fix: equipping stackable items now equips one item of stack

See merge request cable-mc/cobblemon!2469
2026-10-09 14:31:02 +00:00
Gitoido d6c7b271e4 chore: flip when condition for readability 2026-10-09 11:11:34 +03:00
Gitoido df9acfa0d1 fix: equipping stackable items now equips one item of stack 2026-10-09 11:03:06 +03:00
Polymeta 9d7fc8435e scraped spawns again and pokemon that are part of herd will also get their specific drops
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-09 08:23:40 +01:00
Gitoido ab4492ac5c fix: forgor clientside level check 2026-10-09 09:43:38 +03:00
Gitoido cb78dc8d88 fix: flower should be consumed when feeding brown mooshtank 2026-10-09 09:42:44 +03:00
Gitoido 020adabe26 fix: added checks for mooshtank aspect, denying usage of bowl on common miltanks 2026-10-09 09:33:03 +03:00
Gitoido 08aca7a274 chore: changelog skip-ci 2026-10-09 09:14:02 +03:00
Gitoido 2a9ef2b9fe fix: increment menu state id on held tm stack click, small code cleanup 2026-10-09 08:39:46 +03:00
Jakotens 90dc489547 Minor poser fixes for Mime Jr. and slight model adjustments to Mime Jr. and Corsola (Johtonian form only).
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-10-08 13:47:42 -06:00
Polymeta 5ec5cfbc4a Merge branch 'fix/bees-grow-apricorns-galarica' into 'main'
Fixed bees not growing Apricorns or the Galarica Nut Bush

Closes #2095

See merge request cable-mc/cobblemon!2427
2026-10-08 14:59:41 +00:00
Polymeta 1ef65449a8 Merge branch 'fix/addon-species-namespaces' into 'main'
Fix crashes with addon-namespace species in evolution, bait and Pokédex lookups

Closes #1353

See merge request cable-mc/cobblemon!2465
2026-10-08 14:56:31 +00:00
Fyre520 c6ff1ea72e Fix crashes with addon-namespace species in evolution, bait and Pokédex lookups 2026-10-08 18:50:53 +05:30
Polymeta cbfa8d3e78 Merge branch 'fix/alpha-chance-config-category' into 'main'
fix: config category for alpha/shiny chance config fields for fossil machine...

See merge request cable-mc/cobblemon!2464
2026-10-08 09:45:34 +00:00
Gitoido 5098ff3cfd fix: config category for alpha/shiny chance config fields for fossil machine and honey log slathering should be Spawning 2026-10-08 12:27:35 +03:00
Polymeta 7c32b07d3f scraped spawns
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-08 08:42:43 +01:00
ewwww b2142fed1d frog tweaks 2026-10-08 03:07:03 -04:00
ewwww fef21516e9 lang 2026-10-08 03:05:45 -04:00
Polymeta 6fba0f55c1 Merge branch 'fix/recall-beam-3rdperson' into 'main'
fix: optimize condition checker for recall beam source position

See merge request cable-mc/cobblemon!2462
2026-10-08 06:48:03 +00:00
ewwww 95d43de79f various minior tweaks 2026-10-08 02:16:38 -04:00
Polymeta 0be617b8ab Merge branch 'fix/1359-sweep-fire-owned-pokemon' into 'main'
Fix sweeping attacks igniting owned Pokémon

Closes #1359

See merge request cable-mc/cobblemon!2460
2026-10-08 06:08:35 +00:00
Polymeta c02d22f449 Merge branch 'fix/1492-edible-medicinal-leek' into 'main'
Fix Medicinal Leek not being edible

Closes #1492

See merge request cable-mc/cobblemon!2459
2026-10-08 06:03:59 +00:00
Polymeta 846b645856 Merge branch 'type-gem-min-growth-fix' into 'main'
fix wrong value in CobblemonConfig for type gem min growth

See merge request cable-mc/cobblemon!2461
2026-10-08 06:01:53 +00:00
whatsy 25d93a771c fix wrong value in CobblemonConfig for type gem min growth 2026-10-08 06:01:53 +00:00
Gitoido f16c782336 fix: optimize condition checker for recall beam source position 2026-10-08 08:56:53 +03:00
shg ed0f315e41 Fix sweeping attacks igniting owned Pokémon
Closes #1359
2026-10-08 00:42:28 +00:00
shg d20b03f4c5 Fix Medicinal Leek not being edible
Closes #1492
2026-10-08 00:42:25 +00:00
Polymeta a3920413ac fix TMM UI not closing when supposed to 2026-10-07 21:48:28 +00:00
ewwww 92bccf620f Merge branch 'main' into implement_pokemon 2026-10-07 15:40:14 -04:00
ewwww 15f8a9ee71 Merge branch 'main' into implement_pokemon 2026-10-07 15:39:42 -04:00
Polymeta dfb9ee755e Merge branch 'Slippy-1.8.2-Fixes' into 'main'
Slippy 1.8.2 fixes

See merge request cable-mc/cobblemon!2458
2026-10-07 17:25:48 +00:00
Slippy 68f4117e29 Focus Sash changed to focus band in ruin sus loot 2026-10-07 12:09:05 -05:00
Slippy f74a24a056 Console Log Spam Fix 2026-10-07 12:06:19 -05:00
ewwww e5750cbba0 lang fixes for groudon, kyogre, and oricorio 2026-10-07 12:58:20 -04:00
ewwww c6482c82cd various minior tweaks 2026-10-07 12:57:06 -04:00
Slippy b4c23a9132 Alpha Loot Table nerfed 2026-10-07 11:34:35 -05:00
Kenji_64 c470894bd3 Fixed Politoed's hitbox 2026-10-07 15:29:52 +02:00
Kenji_64 a940e32f8d Fixed a texture error with Shiny Greninja 2026-10-07 15:29:21 +02:00
ewwww b6b59eccad vivillon dex tweak 2026-10-07 06:52:25 -04:00
ewwww b520b94fae minior lang fixes + tweaks to form & dex data 2026-10-07 06:52:16 -04:00
Polymeta 9af99d16f7 Merge branch 'feature/the-center-of-attention' into 'main'
constraining should start from center of spawn area

Closes #2119

See merge request cable-mc/cobblemon!2457
2026-10-07 07:17:30 +00:00
Polymeta 2cc17d3280 constraining should start from center of spawn area 2026-10-07 07:17:30 +00:00
Polymeta 20b2bcaa5a Merge branch 'implement_pokemon' into 'main'
Implement pokemon

See merge request cable-mc/cobblemon!2455
2026-10-06 11:47:36 +00:00
Kenji_64 d1ada605fd Fixed Alolan Bias Pichu resolver issues (again) 2026-10-06 13:20:30 +02:00
Polymeta d37f73626d Merge branch 'type-gem-growth-rework' into 'main'
Type gem growth rework

See merge request cable-mc/cobblemon!2451
2026-10-06 07:07:32 +00:00
Polymeta 73a94cb02e Merge branch 'burmy-cloak-change' into 'implement_pokemon'
Burmy Cloak Changing & Battle Ended Event Refinements

See merge request cable-mc/cobblemon!2452
2026-10-06 07:04:32 +00:00
Polymeta 5a48945e50 Merge branch 'fix/particles-respecting-graphics-setting' into 'main'
fix: particle density should respect minecraft particle density settings

See merge request cable-mc/cobblemon!2446
2026-10-06 05:49:29 +00:00
Polymeta 476448a2ce Merge branch 'more-block-noises' into 'main'
More more item blocks sounds

See merge request cable-mc/cobblemon!2450
2026-10-06 05:48:55 +00:00
Polymeta bcba4f7fb7 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2445
2026-10-06 05:47:25 +00:00
Konoha 20e9608ff3 cleanup: biome tag overlaps and priority 2026-10-05 22:08:49 -04:00
ewwww 3e5733ceaf GREAVARD EMISSIVES 2026-10-05 19:29:25 -04:00
ewwww af29c99b03 properly defined vivillon's pattern forms as forms viewable in the pokedex with form names and dex entries 2026-10-05 18:42:09 -04:00
ewwww 60d596a24f riolu + lucario remodels 2026-10-05 17:14:24 -04:00
Konoha 3f993e0466 feature: Burmy cloak changing (and battle event refinements) 2026-10-05 12:21:18 -04:00
whatsy 9e3d22ee54 default type gem cluster growth type is decoration, so porting doesn't give people an infinite gem block glitch 2026-10-05 11:04:59 -04:00
whatsy e86cb1981f Merge remote-tracking branch 'origin/type-gem-growth-rework' into type-gem-growth-rework 2026-10-05 10:55:16 -04:00
jackowes e94f265adb Add datafixer for type gems. Also ensure the fix runs even when mc version stays the same 2026-10-05 10:55:00 -04:00
whatsy b3fc35240a no i didnt forget the changelog 2026-10-05 10:55:00 -04:00
whatsy d74618bddb basically finalized 2026-10-05 10:55:00 -04:00
whatsy 8b54a8d459 moved stuff to config + more adjustments 2026-10-05 10:55:00 -04:00
whatsy c17104a0fd Basics for the type gem growth rework 2026-10-05 10:55:00 -04:00
Sterrezo 88ca3ebbf3 Added item block sound sets for glass jars with liquid 2026-10-05 15:14:53 +02:00
ewwww 4003456e5c slakoth animations + minor model tweaks 2026-10-05 05:41:06 -04:00
ewwww 1d21a52d28 shinx, luxio, and luxray remodels 2026-10-05 05:38:23 -04:00
ewwww 2d49c62f85 froakie, frogadier, greninja, & ash-greninja remodels + greninja animations 2026-10-05 05:38:23 -04:00
Polymeta 20fac724f6 Merge branch 'testing-fixes' into 'main'
Small Fixes

See merge request cable-mc/cobblemon!2448
2026-10-05 07:02:45 +00:00
dhi_holo 0807d73646 Small Fixes 2026-10-05 07:02:45 +00:00
Jackson Bowes 3013302f35 Merge branch 'drained' into 'main'
Mega Doinked

See merge request cable-mc/cobblemon!2447
2026-10-04 19:22:42 -05:00
dhi_holo 879e7bef21 Mega Doinked
Removes Mega Drain as a TM, due to only being learnt by a single line of Pokemon
2026-10-05 10:04:29 +10:00
jackowes 557623dfe2 Add datafixer for type gems. Also ensure the fix runs even when mc version stays the same 2026-10-04 15:01:49 -05:00
Gitoido a28d1e176d chore: remove log spam skip-ci 2026-10-04 18:24:18 +03:00
Gitoido f3c52db70d chore: changelog + unused imports cleanup skip-ci 2026-10-04 18:22:22 +03:00
Gitoido 525575b0ba fix: particle density should respect minecraft particle density settings 2026-10-04 18:10:54 +03:00
Sterrezo a55efeb378 Added metal can item block sounds, fixed missing sound implementation for a bunch of other item block sounds 2026-10-04 15:44:04 +02:00
Kenji_64 da18b082a6 Fixed Alolan Bias Pichu and Pikachu referring to the wrong models and removed the old ones 2026-10-04 12:49:46 +02: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
ewwww f8a16e5081 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-03 22:23:06 -04: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 cad39d8802 Merge branch 'form-move-fix' into 'implement_pokemon'
Form Move Fix

See merge request cable-mc/cobblemon!2420
2026-10-03 22:33:27 +00: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
dhi_holo af281d0bde Pa'uvillea 2026-10-04 07:57:39 +10:00
Kenji_64 00905b8c32 Oops, fixed Pikachu's walk 2026-10-03 21:54:11 +02:00
ewwww 79b3cb8611 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-03 15:47:45 -04:00
ewwww 797e908621 restored placeholder animations for chespin and quilladin 2026-10-03 15:47:40 -04:00
Kenji_64 3df3c395de Cap Animations for the entire Pikachu line, updated Raichu animations, added faint to Pikachu and 1 new cosmetic for Pikachu!!! 2026-10-03 21:25:11 +02:00
whatsy b2f682ebb1 no i didnt forget the changelog 2026-10-03 15:09:15 -04: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
whatsy 3bc2659740 basically finalized 2026-10-02 22:42:57 -04:00
Konoha 89bba8a67a Quick fix changelog and sneaky Spinda fix 2026-10-02 20:51:52 -04:00
whatsy cbd5cdaf92 moved stuff to config + more adjustments 2026-10-02 14:01:04 -04: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
Jerzykv 6d6e13be83 Changelog 2026-10-02 09:29:46 +02:00
Jerzykv 899bec77b6 Implemented Fennekin, Braixen and Delphox remodels 2026-10-02 09:28:58 +02:00
Jerzykv 6dd8325772 Implemented Chespin, Quilladin and Chesnaught remodels 2026-10-02 09:28:39 +02: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
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
whatsy 0fbe206893 Basics for the type gem growth rework 2026-10-01 23:46:18 -04: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
Yasito 1969af65b9 Translated using Weblate (French (Canada))
Currently translated at 95.1% (9016 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-09-30 09:25:23 +00: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
Konoha 6e0c005587 Changelog 2026-09-29 16:25:13 -04: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
Fantastic-Fanta 3b722068b0 Fixed bees not growing Apricorns or the Galarica Nut Bush
Bonemealable blocks in the bee growables tag are now grown through performBonemeal, with the nectar and particle updates vanilla skips.

Closes #2095
2026-09-29 11:25:59 +08:00
Polymeta f2fcc5ba79 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

Closes #2106

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

Closes #2107

See merge request cable-mc/cobblemon!2436
2026-09-28 13:14:51 +00:00
Sterrezo aca53de259 Update CHANGELOG.md 2026-09-28 14:50:24 +02:00
Sterrezo 41bde38727 Fixed effect keyframes sometimes getting doubled probably 2026-09-28 14:49:06 +02:00
Polymeta a4b7ee3b38 follow-up fix from registry change
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-28 09:01:58 +01:00
Jakotens 154b653c57 Fixed some small texture issues on Pidgeot.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-27 17:33:09 -06:00
Jerzykv 04489bc969 changelog 2026-09-27 12:39:00 +02:00
Jerzykv 25499b3e7a Fixed #2106 2026-09-27 12:37:49 +02:00
Instrafe bb6bda54dc Updated Changelog 2026-09-26 14:17:30 -04:00
Instrafe 9f3dbdc2d8 Implemented remodel for Shroomish 2026-09-26 14:13:33 -04:00
Jakotens 932148338c Added animations for Treecko and Bonsly. Added remodel and animations for Sudowoodo.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-25 23:05:21 -06:00
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
Sultan 905d63e098 Translated using Weblate (Russian)
Currently translated at 85.6% (8110 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-09-21 16:38:11 +00:00
Nico Merenq 1ea8ab7dcc Translated using Weblate (Russian)
Currently translated at 85.6% (8110 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-09-21 16:38:11 +00:00
geumu a45d21b279 Translated using Weblate (Korean)
Currently translated at 99.6% (9436 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ko/
2026-09-21 16:38:10 +00: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
geumu efe8f34413 Translated using Weblate (Korean)
Currently translated at 99.6% (9436 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ko/
2026-09-20 13:11:32 +00: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
Nico Merenq 6bb23582c4 Translated using Weblate (Russian)
Currently translated at 85.5% (8102 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-09-19 04:42:37 +00:00
Sultan 56ccb43997 Translated using Weblate (Russian)
Currently translated at 85.5% (8102 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-09-19 04:42:36 +00:00
Polymeta bf7d40c9b5 Merge branch 'small-jvm-fix' into 'main'
Added JvmField to Some values in Cobblemon Blocks cause it was forgotten

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

See merge request cable-mc/cobblemon!2421
2026-09-18 18:29:23 +00:00
Gitoido 975ad8186a chore: dangling variable 2026-09-18 18:03:41 +00:00
Gitoido 26b6419f04 fix: reintroduce delete_variables general molang fn 2026-09-18 18:00:44 +00:00
Jerzykv 331f7ca425 Implemented Burmy, Wormadam and Mothim 2026-09-18 07:15:33 +02:00
Konoha f445c05333 fix: Quick-fix For #updateMovesOnFormChange 2026-09-17 17:38:02 -04: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
Konoha a62485fc1f Fix Calyrex form moves 2026-09-17 15:14:27 -04:00
Konoha 044f3080ff Fix Hoopa form moves 2026-09-17 15:09:43 -04:00
Konoha 28b0cab906 Adjust Form Change moves for Kyurem's level-gated ones. 2026-09-17 14:35:15 -04:00
Konoha 82f34d651c Add Kyurem's form moves 2026-09-17 14:32:47 -04:00
GenotypeXD 6ca908a91d Implemented Helioptile Line 2026-09-17 13:05:12 -05:00
Jerzykv 0948b8da9a Fixed Dragonair not using its idle correctly 2026-09-17 19:17:29 +02:00
Jerzykv 22e04a53b3 changelog 2026-09-17 18:39:10 +02:00
Jerzykv df3217b41b Fixed Bramblin always sleeping 2026-09-17 18:38:48 +02:00
Jerzykv 9ff15a1913 Flygon poser fix 2026-09-17 06:43:03 +02:00
Aram 89e43945a3 Edit CobblemonBlocks.kt 2026-09-16 14:25:26 +02:00
Aram bd8dcddf35 Edit CobblemonBlocks.kt 2026-09-16 14:24:31 +02:00
Polymeta dd372f3160 Merge branch 'recipe-booking' into 'main'
Recipe Book Tweaks

Closes #2088

See merge request cable-mc/cobblemon!2418
2026-09-16 10:03:42 +00:00
dhi_holo c56aab28d4 Recipe Book Tweaks
Closes #2088 as well as fixes a lot of similar issues for gameplay consistency
2026-09-16 18:41:05 +10:00
RedGenesectNinja ca58801794 Implemented Tympole, Palpitoad and Seismitoad 2026-09-15 23:36:40 -05:00
Instrafe 38fa5e2628 Updated Changelog 2026-09-15 16:46:25 -04:00
Instrafe fae1ae7124 Implemented Absol's remodel 2026-09-15 16:45:59 -04:00
Jerzykv 3ec5f76c73 Implemented Deerling and Sawsbuck remodels 2026-09-15 14:15:37 +02:00
Dmytro Bunnua 5fbbcb5707 Translated using Weblate (Ukrainian)
Currently translated at 75.9% (7194 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/uk/
2026-09-15 09:37:39 +00:00
ewwww 5bb8aaba55 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-15 05:05:08 -04:00
ewwww 23ba41e981 wiglett + wugtrio underwater tweaks 2026-09-15 05:04:40 -04:00
Instrafe 29b049452b Updated Changelog 2026-09-14 19:24:03 -04:00
Instrafe 0b0f665804 Glimmet and Glimmora now have implemented remodels 2026-09-14 19:23:30 -04:00
Polymeta 3cdf28ffdd Merge branch 'Chorus-Briar-Fix' into 'main'
I'm never doing an update where I have to keep track of 650+ structure files again

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

See merge request cable-mc/cobblemon!2413
2026-09-14 13:03:40 +00:00
dhi_holo 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 f685be0aab TM Recipes
Corrected two incorrect TM recieps, both in the jsons and the spreadsheets
2026-09-14 16:30:06 +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
Polymeta 6ab5ad52ec Merge branch 'typegem-advancement-trigger' into 'main'
Adds typegem item advancement trigger

Closes #2086

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

See merge request cable-mc/cobblemon!2409
2026-09-13 14:41:13 +00:00
Polymeta 3aebcf66ec rename lang file to match convention 2026-09-13 14:26:44 +00:00
Gustavo Puerari 580a24eac3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.8% (9175 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-13 13:18:28 +00:00
Gertimoshka 07b1b22c2c Wistensu Flower Logic Implementation 2026-09-13 15:08:44 +02:00
dhi_holo 49a5f716b7 Fixed Missing Recipe Advancements
Why were the advancements in the recipes..?
2026-09-13 22:05:44 +10: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
Hiroku 0bd7b4dab3 Merge branch 'decorative-items-restored' into 'main'
Decorative Items Project Returns

See merge request cable-mc/cobblemon!2343
2026-09-13 02:24:00 +00:00
Hiroku 8cbada0cf5 Fix changelog whackiness [skip-ci] 2026-09-13 12:23:40 +10:00
Hiroku f0784fec2d Merge remote-tracking branch 'origin/main' into decorative-items-restored 2026-09-13 12:21:54 +10:00
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
Hiroku cfe0c5e0a5 Snap on 2026-09-13 11:27:40 +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 75bc03fdcb Merge branch 'main' into decorative-items-restored 2026-09-07 15:19:27 +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
dhi_holo 309ff3da13 Loaded Cleanup
Added Loaded Dice, cleaned up some particles
2026-08-12 20:15:52 +10:00
dhi_holo 3af91e2a74 Further Tweaking 2026-08-12 14:26:20 +10:00
dhi_holo 2e027e3db7 Some Visual and Auditory Tweaks 2026-08-12 14:13:21 +10:00
dhi_holo fc40fb02c0 Next Batch
Never-Melt Ice, Wide, Scope, and Zoom Lens, as well as new designs for Upgrade and Dubious Disc
2026-08-12 13:52:13 +10:00
dhi_holo 2c1c0299f9 SPHERICAL!
Adds Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball blocks
2026-08-12 12:24:21 +10:00
FrankTheFarmer 3c7637515c Placeholder idle animation for snover 2026-08-10 13:03:00 +10:00
dhi_holo 08cbbaf68c Merge branch 'main' into decorative-items-restored 2026-08-10 10:21:06 +10:00
Sterrezo 6e911acf01 Sound tweaks
Swapped pot sound types from tumblestone to decorated pots and rock sounds from tumblestone to deepslate.
2026-08-09 12:20:13 +02:00
dhi_holo f6d82a1b28 Changelog
and some last second sound tweaks
2026-08-09 19:17:19 +10:00
dhi_holo 702126aeab Adds Throat Spray and Iron Ball
Adds in Throat Spray and Iron Ball blocks, finishes clean up of King's Rock to look a bit more consistent
2026-08-09 19:04:08 +10:00
dhi_holo df566e8fcb Wearable Hat Cleanup
Fixed King's Rock Blockstate and loot table as well

Wearable "Hat" Items can now be prevented from swapping when trying to open the interaction wheel (sneak right-click), making it not a nightmare to give certain items to Pokemon. Yippee!
2026-08-09 18:53:58 +10:00
dhi_holo c83e0f33b5 Wearable Items Mostly Working Better
Wearable Items now can either be "Blocks" or "Hats"
"Blocks" can be placed as blocks in the world, while "Hats" can be put on the player's head by right clicking them in hand
Need to add some form of override when crouching and looking at a Pokemon though
2026-08-09 17:30:49 +10:00
dhi_holo f279f936b6 Generified Plaque/Wreath/Magnet Code
Moved shared code to a singular class
2026-08-09 16:04:22 +10:00
dhi_holo f8e20bafd9 Import Cleanup 2026-08-09 12:23:19 +10:00
dhi_holo a5211ca7ff Convention Alignment
Changed all stackable item blockstates/model files to utilise the convention from vanilla (seen on blocks such as pink petals)
This commit adds no files, and removes no files, it purely renames and swaps out numbers
2026-08-09 12:06:01 +10:00
FrankTheFarmer 0a5abdb331 🏔 Added snover line 🌲 2026-08-09 11:15:45 +10:00
FrankTheFarmer 4ed7413dd2 Obamasnow 2026-08-09 11:13:17 +10:00
FrankTheFarmer 40b66c24fc Donald Snover 2026-08-09 11:12:56 +10:00
dhi_holo 2f7bedf001 Update scribblenotes - Decorative Items.txt 2026-08-08 18:05:17 +10:00
dhi_holo 44c31a9ddb Update StackableItemBlock.kt
Simplified the way the hitboxes are defined, to allow for future planned hitboxes to actually work as intended
2026-08-08 17:33:34 +10:00
FrankTheFarmer 20a35de3d8 What if we started implementing pokemon rn rn 2026-08-08 14:58:20 +10:00
dhi_holo cfc8a15db1 Update scribblenotes - Decorative Items.txt 2026-08-06 18:35:29 +10:00
dhi_holo d5ba22a86c Removal of Unneeded Lines
deprecate deez nuts
2026-08-06 15:01:34 +10:00
dhi_holo a38505f1c5 Update scribblenotes - Decorative Items.txt
Documenting ideas and plans for future me
2026-08-05 19:28:29 +10:00
dhi_holo 3cf1ba1fb3 Tag Tweaks
Added stuff to relevant tags, removed unused tag from removed feature
2026-08-05 18:36:38 +10:00
dhi_holo 9f5d595827 Import Cleanup
Just removing some imports from reverted changes that snuck into commits
2026-08-05 18:03:06 +10:00
dhi_holo 75673f0204 Mild Polish 2026-08-05 17:09:40 +10:00
dhi_holo b43a7571ff Eject Button
Now with cleaner code, I hope this is more acceptable
2026-08-05 16:13:24 +10:00
dhi_holo b071dc04d3 Metronome
Reimplemented the Metronome, the code has been genericized to allow for more items to be implemented off the same base, although a little bit more work does need to be done there with the comparator output specifically
2026-08-05 15:34:32 +10:00
dhi_holo 12f9ff1647 Wreath, Magnet, and some tweaks to Plaques
Plaques now place the way I had originally set them up, and Galarica Wreaths along with the Magnet are back
2026-08-05 14:34:41 +10:00
dhi_holo 3256a302df Ring Target
Could maybe be polished up, but is good
2026-08-05 13:06:07 +10:00
dhi_holo edbe447477 Basic Decorations
Reimplemented Galarica Cuff, Teapots, Teacups, Weather Rocks, Sachet, Metal Powder, Quick Powder, Bright Powder, Destiny Knot, and Luminous Moss
2026-08-05 12:04:48 +10:00
Gertimoshka 2f43910368 Cleaner Implementation with less duplicate code 2026-08-04 22:21:58 +03:00
dhi_holo c2e87e4cfa Placeable Vitamins Experiment A
Need coder feedback regarding this
Makes Mochi and Feathers require clicking in the air or directly on a Pokemon, if it thinks it can place as a block it prevents usage
2026-08-04 19:25:13 +10:00
dhi_holo e40d2ee806 Create scribblenotes - Decorative Items.txt
Making this a unique commit so it's easy to reference back to later
2026-08-04 14:53:53 +10:00
dhi_holo ba74e7603b Wearables 1
Made Sharp Beak, Covert Cloak, and Shell Helmet wearable
2026-08-04 13:51:49 +10:00
dhi_holo 19c3fe6dc4 Update StackableItemBlock.kt
Fixed hitbox oversight
2026-08-04 13:35:17 +10:00
dhi_holo d0778dd124 Missing Stackables 1
Reimplemented Metal Coat, PP Up, and PP Max's placeable logic
Began work for other Vitamins to work (incomplete)
Added models, textures, blockstates, and loot tables for most incoming blocks
2026-08-04 13:16:46 +10:00
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
3306 changed files with 307740 additions and 47836 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
+24 -34
View File
@@ -1,41 +1,31 @@
# Changelog
## [1.8.1 (September 13th, 2026)](#1-8-1)
## [1.8.x (MONTH xth, 2026)](#1-8-x)
### Additions
### New rideable Pokémon
### Pokémon Added
### Model updates for the following Pokémon
### Animation updates for the following Pokémon
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
### Changes
- Reworked spawn presets and added structure tags for spawns
### Fixes
- Fixed a potential world generation stall caused by Type Gem clusters.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed NPCs getting their own TM progress file.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
- Fixed Lucario's aura particles not emitting.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having their sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
- Fixed blank Cooking Pot recipe book entries and missing tabs on Fabric.
- Fixed Minior's "Shields Down" ability not working as intended
### Developer
### Molang & Datapacks
- Added `q.target_fainted` function to move action effects that checks if any opponents fainted from the move.
### Particles
#### New Move Animations
### Localization
- Updated translations for:
- Brazilian Portuguese
- Simplified Chinese
- Japanese
- 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()}"
}
+4 -1
View File
@@ -36,6 +36,7 @@
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` stat for captured Alpha Pokémon.
### New rideable Pokémon
- Pidgeot
- Crobat
- Skarmory
- Milotic
@@ -106,7 +107,9 @@
- Avalugg (Hisuian Form)
### Model updates for the following Pokémon
- Pidgey
- Pidgeotto
- Pidgeot
- Sandshrew
- Sandslash
- Nidoking
+41
View File
@@ -0,0 +1,41 @@
# Changelog
## [1.8.1 (September 13th, 2026)](#1-8-1)
### Animation updates for the following Pokémon
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
### Fixes
- Fixed a potential world generation stall caused by Type Gem clusters.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed NPCs getting their own TM progress file.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
- Fixed Lucario's aura particles not emitting.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having their sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Simplified Chinese
- Japanese
+211
View File
@@ -0,0 +1,211 @@
# Changelog
## [1.8.2 (October 11th, 2026)](#1-8-2)
### 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, 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.
- I wasn't finished. 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:
- 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 whatever you've rebound use/interact to).
- Wearable items that can be placed down are exempt from this behaviour.
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots.
- The recall animation now renders the ball in hand with the player's arm raised.
- Added Oricorio Flowers.
- Added Terrain Plants, four plants grown from Electric, Grassy, Misty, and Psychic Seeds, which glow under specific environmental conditions and can be farmed for more of their seeds.
- Berries can now be placed in Flower Pots.
- The evolution tree for a Pokémon can now be viewed in the Pokédex, showing the 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.
- Added paper as a new cosmetic item for Pikachu.
- Burmy changes its cloak after a battle it took part in, based on the biome.
- Trash Cloak in the Deep Dark, Nether and End.
- Plant Cloak in lush biomes such as forests, jungles, plains and swamps.
- Sandy Cloak in sandy biomes, beaches and caves.
### Pokémon Added
#### Generation 4
- Snover
- Abomasnow
- Burmy
- Wormadam
- Mothim
#### Generation 5
- Tympole
- Palpitoad
- Seismitoad
#### Generation 6
- Helioptile
- Heliolisk
#### Generation 7
- Grubbin
- Charjabug
- Vikavolt
- Minior
#### Generation 8
- Eternatus
#### Generation 9
- Greavard
- Houndstone
### Model updates for the following Pokémon
- Sudowoodo
- Spheal
- Cacnea
- Cacturne
- Binacle
- Barbaracle
- Glimmet
- Glimmora
- Deerling
- Sawsbuck (All forms)
- Absol
- Poliwag
- Politoed
- Ekans
- Oddish
- Gloom
- Vileplume
- Bellossom
- Shroomish
- Chespin
- Quilladin
- Chesnaught
- Fennekin
- Braixen
- Delphox
- Froakie
- Frogadier
- Greninja
- Shinx
- Luxio
- Luxray
- Slakoth
- Riolu
- Lucario
- Corsola
- Mime Jr.
### Animation updates for the following Pokémon
- Treecko (New Shoulder Mount!)
- Bonsly (New Shoulder Mount!)
- Sudowoodo
- Cacnea
- Cacturne
- Ekans
- Arbok
- Oddish
- Gloom
- Vileplume
- Bellossom
- Pichu
- Pikachu
- Raichu (+ Alolan)
- Greninja
- Slakoth
### Changes
- Slowed the speed of Type Gem growth, and revamped their generation to grow in a more crystal-like shape.
- 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 and consistently within the Recipe Book's categories.
- Updated the following recipes
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM.
- Made 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 a TM creative tab.
- Made regional and Vivillon split-evo locations mutually exclusive.
- Vivillon's patterns are now viewable as forms with form names and unique Pokédex entries displayed.
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
- Exceptions being "forced" evolutions which stop after the first one.
- Removed Mega Drain as a TM.
- PokeSnacks (and other area spawns) will now start the vertical offset checking from the center rather than base coordinate
- This will help PokeSnacks to work inside caves or tighter spaces better.
### Fixes
- Type Gem blocks now properly generate clusters off of the block.
- Correctly grant "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.
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport and you are not viewing the top or bottom edges.
- The Random Starter option no longer runs off into other categories or becomes unaccessible 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 compatibility 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.
- Fixed Crabominable's head breaking when looking around.
- Fixed Shiny Dipplin and Hydrapple's textures.
- 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.
- Fixed alpha eye (and other) particles not respecting the client particle density setting in video options.
- Fixed the TM Machine UI not closing when player is moving away from it.
- Moved the config category for alpha/shiny chance config fields for fossil machine and honey log slathering to Spawning.
- Fixed crashes for addon species with non-Cobblemon namespaces in evolution, fishing bait, and Pokédex lookups.
- Bees will now grow Apricorns and the Galarica Nut Bush, which were in the bee growables tag but never handled, and correctly spend their nectar when they do.
- Saccharine Leaves can now be bone mealed.
- Fixed equipping a stack of wearable items instead of one item of a stack.
- Fixed TM stack consumption in TM Machine screen when a player teaches a move to a Pokémon.
- Fixed every Miltank giving mushroom stew, not only mushroom variants.
- Medicinal Leeks can now be eaten when they aren't being planted, matching their food properties.
- Sweeping attacks no longer set owned Pokémon on fire (e.g. with Fire Aspect) or knock them back. Friendly Fire is now off.
- Fixed Pokémon not losing their previous form's signature moves when changing forms (e.g. Rotom keeping Overheat after leaving its Heat form).
- Form-specific moves gained from a form change are now only granted if the Pokémon is high enough level to learn them, and no longer create duplicates in benched moves.
- Spinda now learns Teeter Dance at level 32 instead of via TM.
- Fixed Oricorio Pa'u form description in the Pokédex.
- Fixed PC playing sound on close when opened via command.
### 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 the wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing Pokémon to appear without the 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.
- Added a `battle_ended` callback, backed by the new `BATTLE_ENDED` event, which fires whenever a battle ends for any reason, alongside a multitude of flags not previously covered by `BATTLE_VICTORY`.
- Added `battle_pokemon` to battle actor structs and `has_faced_opponents` to battle Pokémon structs.
- Added the `cobblemon:form_change/bagworm_cloak/plant_cloak`, `sandy_cloak` and `trash_cloak` biome tags, which control Burmy's cloak after battle.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Canadian French
- Japanese
- Korean
- Russian
- Ukrainian
+107
View File
@@ -0,0 +1,107 @@
# Changelog
## [1.8.3 (October 25th, 2026)](#1-8-3)
### Additions
- Added compatability with AppleSkin.
### New Rideable Pokémon
- Vikavolt
- Fearow
### Particles
## New Move Animations
- Water Shuriken
### Cries added/updated for the following Pokémon
- Hoppip
- Skiploom
- Jumpluff
- Houndour
- Houndoom
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Surskit
- Masquerain
- Torkoal
- Starly
- Staravia
- Staraptor
- Combee
- Vespiquen
- Dusknoir
- Helioptile
- Heliolisk
- Grubbin
- Charjabug
- Vikavolt
- Applin (updated)
- Flapple (updated)
- Appletun
- Runerigus
- Dipplin
### Animation updates for the following Pokémon
- Vikavolt
- Magby
- Magmar
- Ninetales (+ Alolan)
- Spearow
- Fearow
- Squawkabilly
- Mudkip
- Marshtomp
- Swampert
### Changes
- Changed the Misc icon in the Campfire Pot Recipe Book to Health Candy.
- Moved EXP Candies from the Medicine recipe book category to the Misc category.
- Removed Dynamic Lights data from the Magmarizer item for consistency with the block.
- Added `doPokemonPredation` game rule, which controls aggressiveness of Pokémon with the `hate_entity` brain task defined.
- Added `predation_chance` server config value, which controls the possibility of attacking the hated entity found in `hate_entity` brain tasks.
- Prehistoric Eroded Pillar generates slightly more frequently.
- Togekiss was changed from a jet-style mount to bird-style.
### Fixes
- Fixed datapacks with item interaction evolutions that don't specify an item crashing the world on load.
- Fixed the Pokédex evolution tree crashing when a datapack's dex entry is missing an evolution's form.
- Fixed the Pokédex evolution tree crashing with a stack overflow on evolutions that loop back on themselves.
- Fixed the Pokédex evolution tree crashing when looking at a dex that is not part of the "National Dex" aggregate.
- Fixed some display names in the Pokédex evolution tree having unintended font colours.
- Fixed evolution trees not building properly when a Pokémon has several Pokédex entries for different regions.
- Fixed form-changing evolutions sometimes resolving to the source form in the Pokédex evolution tree.
- Broken datapack evolutions are now logged and skipped in the Pokédex evolution tree instead of crashing.
- Fixed the `alpha` property having no tab completion in commands such as `/pokespawn`, `/pokegive` and `/pokeedit`, and added the missing `dmax_level`, `gmax_factor` and `tradable` aliases to tab completion.
- Fixed friendship booster items not being recognised as such.
- Fixed conflicts with other mods that would make use of recipe books in certain ways (Farmer's Delight Cooking Pot).
- Fixed Pokémon battles stalling when a sidemod cancels the `THROWN_POKEBALL_HIT` hit event.
- Fixed choice feature tab completion in `spawnpokemon/pokegive/pokeedit` commands.
- Fixed MoLang that's targeting players intermittedly failing when they have died, requiring them to relog.
- Fixed a NeoForge bug where none of the Cobblemon plants/berries could be placed into pots.
- Fixed party overlay not hiding when the player opens the Pokédex scanner.
- Fixed `hate_entity` task focusing on its own entity, therefore enabling deadly warfare between Zangoose and Seviper.
- Prehistoric Frozen Pond, Prehistoric Frozen Spike, and Prehistoric Lush Den now actually generate (oops).
- Fixed Frogadier floating in its `battle_idle` animation.
- Fixed absence or incorrect mergers in spawning conditions, causing the max height/depth of conditions to use the min value instead.
- Added stricter riding checks on server side to prevent possible packet forgery when a player tries to ride Pokémon.
- Fixed Pokémon cries and animation sounds played from GUIs, such as the starter selection screen and the Pokédex, being tied to the Master volume slider instead of Friendly Creatures.
- Fixed Archeops T-posing during its `air_cry` animation.
### Developer
- Bumped NeoForge to `21.1.195` to get access to MixinExtras `0.5` and the expressions annotation.
- New Platform Event `PLAYER_RESPAWN` which is fired when a player respawns (either through death or returning from the end).
- Platform Events `SERVER_PLAYER_TICK_PRE` and `SERVER_PLAYER_TICK_POST` no longer fire when the player is dead.
- It is now tied much closer to the actual entity rather than the player or their connection.
### Molang & Datapacks
- Fixed `q.pokemon.nature` returning class instance instead of proper string value (`cobblemon:adamant`).
- Fixed `q.entity.is_looking_at` result being always `1` for whatever entity passed as the target parameter.
- Added hate chance optional parameter to `hate_entity` task. `1.0` will make an entity always attack the target.
### Localization
- Updated translations for:
- Russian
- Japanese
- Brazilian Portuguese
+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") })
}
@@ -8,54 +8,42 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.block.SaccharineLeafBlock;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeGrowCropGoal")
public abstract class BeeEntityMixin {
@Unique
private BlockState cobblemon$result = null;
@Inject(method = "tick()V", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/block/BonemealableBlock;performBonemeal(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V",
shift = At.Shift.BY,
by = 2
),
locals = LocalCapture.CAPTURE_FAILHARD
)
private void injectCustomGrowth(CallbackInfo ci, int i, BlockPos blockPos, BlockState blockState, Block block) {
if (block instanceof SaccharineLeafBlock) {
int age = blockState.getValue(SaccharineLeafBlock.Companion.getAGE());
boolean waterlogged = blockState.getValue(BlockStateProperties.WATERLOGGED);
if (age < 2 && !waterlogged) {
this.cobblemon$result = blockState.setValue(SaccharineLeafBlock.Companion.getAGE(), age + 1);
}
}
}
@Shadow(aliases = {"this$0", "field_20373"}) @Final private Bee bee;
// Grows bonemealable blocks vanilla's hardcoded checks miss, returning the state so it handles the rest.
@ModifyVariable(method = "tick", at = @At(value = "LOAD", ordinal = 0), index = 5)
private BlockState applyCustomBlockState(BlockState before) {
if(this.cobblemon$result != null && before == null) {
BlockState result = this.cobblemon$result;
this.cobblemon$result = null;
return result;
private BlockState cobblemon$growBonemealableBlocks(BlockState grownState, @Local(ordinal = 0) BlockState blockState, @Local BlockPos pos) {
if (grownState != null || blockState.is(Blocks.CAVE_VINES) || blockState.is(Blocks.CAVE_VINES_PLANT)) {
return grownState;
}
return before;
if (!(blockState.getBlock() instanceof BonemealableBlock bonemealable)) {
return null;
}
ServerLevel level = (ServerLevel) this.bee.level();
if (!bonemealable.isValidBonemealTarget(level, pos, blockState)) {
return null;
}
bonemealable.performBonemeal(level, this.bee.getRandom(), pos, blockState);
return level.getBlockState(pos);
}
}
@@ -15,6 +15,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.mojang.serialization.MapCodec;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.resources.ResourceKey;
@@ -45,4 +46,24 @@ public class ChunkStorageMixin {
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
return DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
}
// Our update on the same MC version still needs its own fixes so run them here in that case.
@Inject(
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$doChunkFixOnCurrentVersion(
ResourceKey<Level> levelKey,
Supplier<DimensionDataStorage> storage,
CompoundTag chunkData,
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
CallbackInfoReturnable<CompoundTag> cir
) {
if (ChunkStorage.getVersion(chunkData) != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
return;
}
int curVersion = chunkData.contains(VERSION_KEY) ? chunkData.getInt(VERSION_KEY) : 0;
cir.setReturnValue(DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), chunkData, curVersion, CobblemonSchemas.DATA_VERSION));
}
}
@@ -75,9 +75,10 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && pokemonEntity.getDelegate() instanceof PokemonClientDelegate delegate) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
// Passenger order isn't seat order, so look the seat up directly
Seat seat = pokemonEntity.getSeatForPassenger(this);
MatrixWrapper locator = seat != null ? delegate.getLocatorStates().get(seat.getLocator()) : null;
if (locator == null) return super.pick(hitDistance, partialTicks, hitFluids);
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
@@ -99,7 +100,8 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
@Unique
private Vec3 cobblemon$getEyePosition(float partialTicks, PokemonEntity pokemonEntity, Vec3 locatorOffset) {
OrientationController controller = this.getOrientationController();
// The ride owns the orientation, the passenger's own controller is never active
OrientationController controller = ((OrientationControllable) pokemonEntity).getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vec3 offset = locatorOffset.add(pokemonEntity.position());
@@ -107,7 +109,7 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
Quaternionf orientation = controller.getRenderOrientation(partialTicks);
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
offset.add(rotatedEyeHeight);
offset = offset.add(rotatedEyeHeight);
}
return offset;
@@ -35,13 +35,16 @@ import com.cobblemon.mod.common.util.DataKeys;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
@@ -50,6 +53,7 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
@@ -371,4 +375,33 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
this.cobblemon$rideEyePos = rideEyePos;
}
/**
* Sweeping attacks apply post-attack enchantment effects (e.g. Fire Aspect) to every entity caught
* in the sweep, even if the damage itself was blocked. Skip them for Pokémon immune to the attack.
*/
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/EnchantmentHelper;doPostAttackEffects(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)V")
)
private void cobblemon$skipPostAttackEffectsOnImmunePokemon(ServerLevel level, Entity target, DamageSource source, Operation<Void> original) {
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
return;
}
original.call(level, target, source);
}
/**
* Sweeping attacks also knock back every entity caught in the sweep regardless of whether it took damage.
*/
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V")
)
private void cobblemon$skipKnockbackOnImmunePokemon(LivingEntity target, double strength, double x, double z, Operation<Void> original, @Local DamageSource source) {
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
return;
}
original.call(target, strength, x, z);
}
}
@@ -83,7 +83,7 @@ public abstract class CameraMixin implements CameraDuck {
}
// reset the rotation values if it is now not being ridden.
MountedCameraRenderer.INSTANCE.setSmoothRotation(null);
MountedCameraRenderer.INSTANCE.reset();
original.call(instance, x, y, z);
}
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
@@ -63,23 +64,12 @@ public abstract class ClientRecipeBookMixin {
}
}
@Inject(method = "setupCollections", at = @At("RETURN"))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci) {
Map<RecipeBookCategories, List<List<RecipeHolder<?>>>> map = categorizeAndGroupRecipes(recipes);
Map<RecipeBookCategories, List<RecipeCollection>> map2 = Maps.newHashMap();
ImmutableList.Builder<RecipeCollection> builder = ImmutableList.builder();
map.forEach((recipeBookCategories, list) -> {
List<RecipeCollection> collections = list.stream()
.map(listx -> new RecipeCollection(registryAccess, listx))
.peek(builder::add)
.collect(ImmutableList.toImmutableList());
map2.put(recipeBookCategories, collections);
});
RecipeBookCategories.AGGREGATE_CATEGORIES.forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
@Inject(method = "setupCollections",
at = @At(
value = "INVOKE",
target = "Lcom/google/common/collect/ImmutableMap;copyOf(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;"
))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci, @Local(ordinal = 1) Map<RecipeBookCategories, List<RecipeCollection>> map2) {
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
this.collectionsByTab = ImmutableMap.copyOf(map2);
this.allCollections = builder.build();
}
}
@@ -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,10 +9,14 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.camera.MountedCameraRenderer;
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
import com.cobblemon.mod.common.item.PokedexItem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.ItemInHandRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.InteractionHand;
@@ -31,11 +35,22 @@ 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()) {
}
}
@Inject(method = "renderHandsWithItems", at = @At("HEAD"))
private void cobblemon$rollHandsWithRide(float partialTicks, PoseStack poseStack, MultiBufferSource.BufferSource buffer, LocalPlayer player, int light, CallbackInfo ci) {
float roll = MountedCameraRenderer.INSTANCE.getHandRoll();
if (roll != 0f) {
poseStack.mulPose(Axis.ZN.rotationDegrees(roll));
}
}
@Redirect(method = "renderHandsWithItems", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V"))
private void cobblemon$renderHandswithItems(PoseStack instance, Quaternionf quaternion) {
if (!(Minecraft.getInstance().player != null && Minecraft.getInstance().player.getVehicle() instanceof OrientationControllable controllable && controllable.getOrientationController().isActive())) {
@@ -32,7 +32,6 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.Input;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
@@ -50,11 +49,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
import java.util.Objects;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable, DoubleJump {
@Unique Matrix3f cobblemon$lastOrientation;
@Unique int cobblemon$lastOrientationVehicleId = -1;
@Unique float cobblemon$lastRideXRot;
@Unique float cobblemon$lastRideYRot;
@@ -81,30 +82,28 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
private void cobblemon$updateRotationMatrixPassenger(CallbackInfo ci) {
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
var vehicleController = controllableVehicle.getOrientationController();
if (!vehicleController.isActive() || vehicleController.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = vehicleController.getOrientation() != null ? new Matrix3f(vehicleController.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(this.getVehicle().getId(), vehicleController.getOrientation()));
if (!vehicleController.isActive()) {
// Forget the last sent value so the next ride always gets its first orientation sent
cobblemon$lastOrientation = null;
return;
}
var orientation = vehicleController.getOrientation();
var vehicleId = this.getVehicle().getId();
// Compare by value, the stored one is a copy so a reference check never matches
if (vehicleId == cobblemon$lastOrientationVehicleId && Objects.equals(orientation, cobblemon$lastOrientation)) return;
cobblemon$lastOrientation = orientation != null ? new Matrix3f(orientation) : null;
cobblemon$lastOrientationVehicleId = vehicleId;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(vehicleId, orientation));
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
var driver = (LocalPlayer)(Object)this;
var vehicle = driver.getVehicle();
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((LocalPlayer)(Object)this);
var vehicleController = controllableVehicle.getOrientationController();
// If the player has just switched to riding a custom orientation ride then set their
// x and y rots local to the vehicle rots(if the controller wasn't active and will now be active)
// This ensures that on transition your camera stays in the same spot
if (!vehicleController.isActive() && shouldUseCustomOrientation) {
// Set local to the vehicle x and yrot so
var playerRotater = (RidePassenger)driver;
playerRotater.cobblemon$setRideXRot(Mth.wrapDegrees(driver.getXRot() - vehicle.getXRot()));
playerRotater.cobblemon$setRideYRot(Mth.wrapDegrees(driver.getYRot() - vehicle.getYRot()));
}
// Look angles get seeded by the camera on its first rolling frame
vehicleController.setActive(shouldUseCustomOrientation);
}
@@ -11,6 +11,10 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput;
import com.cobblemon.mod.common.api.riding.camera.RideLookInput;
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings;
import com.cobblemon.mod.common.api.riding.camera.RideLookTake;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.client.keybind.keybinds.RidingFreelookBinding;
@@ -20,13 +24,12 @@ import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import kotlin.Pair;
import kotlin.Unit;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.phys.Vec3;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -42,6 +45,11 @@ import static net.minecraft.util.Mth.lerp;
@Mixin(MouseHandler.class)
public abstract class MouseHandlerMixin {
// Entity.turn scales mouse movement to degrees by this
@Unique private static final float COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT = 0.15F;
// Share of the look offset closed per second as it drifts back to centre. Raise to recentre faster
@Unique private static final float COBBLEMON$LOOK_RECENTER_RATE = 5.0F;
@Shadow private double accumulatedScrollY;
@Shadow @Final private Minecraft minecraft;
@@ -160,74 +168,48 @@ public abstract class MouseHandlerMixin {
}
var vehicleController = controllableVehicle.getOrientationController();
var freeLooking = RidingFreelookBinding.INSTANCE.isDown();
Pair<Boolean, Boolean> mouseShouldModifyDriver = ((PokemonEntity) vehicle).ifRidingAvailableSupply(new Pair(false, false), (behaviour, settings, state) -> {
return behaviour.mouseModifiesDriverRotation(
settings,
state,
(PokemonEntity) vehicle
);
var pokemonVehicle = (PokemonEntity) vehicle;
var playerRotater = (RidePassenger)player;
float yawDelta = (float)cursorDeltaX * COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT;
float pitchDelta = (float)cursorDeltaY * COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT;
// Passengers only ever look around
if (player.getControlledVehicle() == null) {
cobblemon$addLook(player, yawDelta, pitchDelta);
return false;
}
// The ride takes what it wants of the mouse to steer with and the rest moves the look. Freelook keeps it all for the look
var lookInput = new RideLookInput(yawDelta, pitchDelta, playerRotater.cobblemon$getRideYRot(), playerRotater.cobblemon$getRideXRot());
RideLookTake take = RidingFreelookBinding.INSTANCE.isDown() ? RideLookTake.NONE : pokemonVehicle.ifRidingAvailableSupply(RideLookTake.all(lookInput), (behaviour, settings, state) -> {
return behaviour.consumeLook(settings, state, pokemonVehicle, lookInput);
});
float lookYaw = yawDelta - take.getYaw();
float lookPitch = pitchDelta - take.getPitch();
if (lookYaw != 0.0f || lookPitch != 0.0f) {
cobblemon$addLook(player, lookYaw, lookPitch);
}
// Drift the look back to centre on the axes the ride steers with
float recenter = min(COBBLEMON$LOOK_RECENTER_RATE * (float)movementTime, 1.0f);
if (take.getRecenterYaw()) {
playerRotater.cobblemon$setRideYRot(lerp(recenter, Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
}
if (take.getRecenterPitch()) {
playerRotater.cobblemon$setRideXRot(lerp(recenter, Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
}
// Hand the ride its share back in mouse units since that's what every ride's steering is tuned for
double rideDeltaX = yawDelta == 0.0f ? 0.0 : cursorDeltaX * (take.getYaw() / yawDelta);
double rideDeltaY = pitchDelta == 0.0f ? 0.0 : cursorDeltaY * (take.getPitch() / pitchDelta);
var steerInput = cobblemon$getSteerInput(vehicleController, rideDeltaX, rideDeltaY, movementTime);
pokemonVehicle.ifRidingAvailable((behaviour, settings, state) -> {
behaviour.steer(settings, state, pokemonVehicle, player, steerInput);
return Unit.INSTANCE;
});
//-------------------------------------------------------------------------
// If looking around then modify the players orientation instead and zero the mouse deltas so the mouse doesn't
// affect the vehicle orientation when looking around.
// If the client is a passenger it will modify these rotations and return
//-------------------------------------------------------------------------
if (freeLooking || player.getControlledVehicle() == null) {
// Modify driver rotations
cobblemon$setDriverRotations(player, cursorDeltaX, cursorDeltaY);
// If the player/passenger is not the driver then return and ignore all vehicle rotation logic
if (player.getControlledVehicle() == null) return false;
// Set to zero so that vehicle rotations don't also get affected by movement.
cursorDeltaX = 0.0;
cursorDeltaY = 0.0;
} else if (mouseShouldModifyDriver.getFirst()) { // If not freelooking then check modification per mouse input dimension
cobblemon$setDriverRotations(player, cursorDeltaX, 0.0);
cursorDeltaX = 0.0;
} else if (mouseShouldModifyDriver.getSecond()) {
cobblemon$setDriverRotations(player, 0.0, cursorDeltaY);
cursorDeltaY = 0.0;
}
//-------------------------------------------------------------------------
// Smoothly return driver rotations to zero when needed
//-------------------------------------------------------------------------
var playerRotater = (RidePassenger)player;
if( !freeLooking && !mouseShouldModifyDriver.getFirst()){
playerRotater.cobblemon$setRideYRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
}
if( !freeLooking && !mouseShouldModifyDriver.getSecond()) {
playerRotater.cobblemon$setRideXRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
}
// Send mouse input to be interpreted into rotation deltas by the ride controller
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
// Perform Rotation using mouse influenced rotation deltas.
vehicleController.rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
);
// Gather and apply the current rotation deltas
var angRot = cobblemon$getAngularVelocity(movementTime);
// Apply smoothing if requested by the controller.
if(cobblemon$shouldUseAngVelSmoothing()) {
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
vehicleController.rotate((float) yaw, (float) pitch, (float) roll);
}
else {
vehicleController.rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
}
return returnValue;
// The camera sets the player's rotation from the look every frame so vanilla turn would only get overwritten
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At("HEAD"))
@@ -248,28 +230,9 @@ public abstract class MouseHandlerMixin {
this.turnPlayer(cobblemon$timeDelta);
}
/** The ride's share of the mouse put through the riding sensitivity, invert and swap options. */
@Unique
private Vec3 cobblemon$getAngularVelocity(double deltaTime) {
var player = minecraft.player;
if (player == null) return Vec3.ZERO;
if (!(player instanceof OrientationControllable)) return Vec3.ZERO;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return Vec3.ZERO;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.angRollVel(settings, state, pokemonEntity, player, deltaTime);
});
}
@Unique
private Vec3 cobblemon$getRideMouseRotation(double mouseX, double mouseY, double deltaTime) {
var player = minecraft.player;
if (player == null) return Vec3.ZERO;
var vehicle = player.getVehicle();
if (vehicle == null) return Vec3.ZERO;
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
private RideSteerInput cobblemon$getSteerInput(OrientationController controller, double mouseX, double mouseY, double deltaTime) {
var invertYaw = Cobblemon.config.getInvertYaw();
var invertPitch = Cobblemon.config.getInvertPitch();
@@ -282,21 +245,18 @@ public abstract class MouseHandlerMixin {
var pitch = (swapXAndY ? xValue : yValue) * (invertPitch? -1.0f : 1.0f);
var sensitivity = cobblemon$getRidingSensitivity();
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.rotationOnMouseXY(
settings,
state,
pokemonEntity,
player,
pitch,
yaw,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
);
});
return new RideSteerInput(
controller,
yaw,
pitch,
xMouseSmoother,
yMouseSmoother,
cobblemon$getRidingSensitivity(),
deltaTime,
yawSmoother,
pitchSmoother,
rollSmoother
);
}
@Unique
@@ -305,31 +265,33 @@ public abstract class MouseHandlerMixin {
return Math.pow(sensitivity, 3);
}
/** Moves the ride look by degrees within the ride's look limits. Same job as vanilla turn but on the look instead of the player. */
@Unique
private void cobblemon$setDriverRotations(LocalPlayer player, double cursorDeltaX, double cursorDeltaY) {
// the vanilla turn() method redone so that it uses rideRotations.
float f = (float)cursorDeltaY * 0.15F; // Note: turn() uses xRot for pitch (up/down)
float g = (float)cursorDeltaX * 0.15F; // Note: turn() uses yRot for yaw (left/right)
private void cobblemon$addLook(LocalPlayer player, float yawDelta, float pitchDelta) {
var playerRotater = (RidePassenger)player;
playerRotater.cobblemon$setRideXRot(playerRotater.cobblemon$getRideXRot() + f);
playerRotater.cobblemon$setRideYRot(playerRotater.cobblemon$getRideYRot() + g);
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -89.0F, 89.0F));
playerRotater.cobblemon$setRideYRot(Mth.clamp(playerRotater.cobblemon$getRideYRot(), -105.0F, 105.0F));
var lookSettings = cobblemon$getLookSettings(player);
playerRotater.cobblemon$setRideXRot(cobblemon$softClamp(playerRotater.cobblemon$getRideXRot(), pitchDelta, lookSettings.getMaxPitch()));
playerRotater.cobblemon$setRideYRot(cobblemon$softClamp(playerRotater.cobblemon$getRideYRot(), yawDelta, lookSettings.getMaxYaw()));
if (player.getVehicle() != null) {
player.getVehicle().onPassengerTurned(player);
}
}
/**
* Moves an angle by delta while keeping it inside the limit. An angle already past the limit
* (the base turning under a low carry look can put it there) isn't snapped back, it just can't go further out.
*/
@Unique
private boolean cobblemon$shouldUseAngVelSmoothing() {
var player = minecraft.player;
if (player == null) return true;
private static float cobblemon$softClamp(float angle, float delta, float limit) {
float bound = Math.max(limit, Math.abs(angle));
return Mth.clamp(angle + delta, -bound, bound);
}
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return true;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
return pokemonEntity.ifRidingAvailableSupply(true, (behaviour, settings, state) -> {
return behaviour.useAngVelSmoothing(settings, state, pokemonEntity);
@Unique
private RideLookSettings cobblemon$getLookSettings(LocalPlayer player) {
if (!(player.getVehicle() instanceof PokemonEntity pokemonEntity)) return RideLookSettings.DEFAULT;
return pokemonEntity.ifRidingAvailableSupply(RideLookSettings.DEFAULT, (behaviour, settings, state) -> {
return behaviour.lookSettings(settings, state, pokemonEntity);
});
}
@@ -9,55 +9,25 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.Minecraft;
import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.Expression;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.client.gui.screens.recipebook.RecipeBookPage;
import net.minecraft.client.gui.screens.recipebook.RecipeBookTabButton;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RecipeBookComponent.class)
public class RecipeBookComponentMixin {
@Shadow private ClientRecipeBook book;
@Shadow protected Minecraft minecraft;
@Shadow private int width;
@Shadow private int xOffset;
@Shadow private int height;
@Shadow @Final private RecipeBookPage recipeBookPage;
@Inject(method = "updateTabs", at = @At("HEAD"), cancellable = true)
private void customUpdateTabs(CallbackInfo ci) {
RecipeBookComponent instance = (RecipeBookComponent) (Object) this;
int i = (width - 147) / 2 - xOffset - 30;
int j = (height - 166) / 2 + 3;
int k = 27;
int l = 0;
for (RecipeBookTabButton recipeBookTabButton : instance.tabButtons) {
RecipeBookCategories recipeBookCategories = recipeBookTabButton.getCategory();
if (recipeBookCategories != RecipeBookCategories.CRAFTING_SEARCH && recipeBookCategories != RecipeBookCategories.FURNACE_SEARCH && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory()) {
if (recipeBookTabButton.updateVisibility(book)) {
recipeBookTabButton.setPosition(i, j + 27 * l++);
recipeBookTabButton.startAnimation(minecraft);
}
} else {
recipeBookTabButton.visible = true;
recipeBookTabButton.setPosition(i, j + k * l++);
}
}
ci.cancel();
@Definition(id = "recipeBookCategories", local = @Local(type = RecipeBookCategories.class))
@Definition(id = "FURNACE_SEARCH", field = "Lnet/minecraft/client/RecipeBookCategories;FURNACE_SEARCH:Lnet/minecraft/client/RecipeBookCategories;")
@Expression("recipeBookCategories != FURNACE_SEARCH")
@ModifyExpressionValue(
method = "updateTabs",
at = @At(value = "MIXINEXTRAS:EXPRESSION")
)
private boolean notCookingPotSearch(boolean original, @Local RecipeBookCategories recipeBookCategories) {
return original && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory();
}
}
@@ -38,6 +38,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Entity.class)
public abstract class EntityMixin {
// How far the clientsent ride eye can sit from the players
@Unique private static final double COBBLEMON$MAX_RIDE_EYE_OFFSET = 3.0;
@Shadow @Nullable public abstract Entity getVehicle();
@Inject(method = "updateInWaterStateAndDoWaterCurrentPushing", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;wasTouchingWater:Z", ordinal = 1), cancellable = true)
@@ -91,6 +94,9 @@ public abstract class EntityMixin {
if (vehicleController == null) return;
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
Vec3 vanillaEyePos = new Vec3(player.getX(), player.getEyeY(), player.getZ());
double maxSqr = COBBLEMON$MAX_RIDE_EYE_OFFSET * COBBLEMON$MAX_RIDE_EYE_OFFSET;
if (customEyePos == null || !(customEyePos.distanceToSqr(vanillaEyePos) <= maxSqr)) return;
cir.setReturnValue(customEyePos);
}
@@ -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)
@@ -260,6 +260,9 @@ object Cobblemon {
RequestManager.onLogoff(it.player)
serverPlayerStructs.remove(it.player.uuid)
}
PlatformEvents.PLAYER_RESPAWN.subscribe {
serverPlayerStructs.remove(it.player.uuid)
}
PlatformEvents.PLAYER_DEATH.subscribe {
PCLinkManager.removeLink(it.player.uuid)
battleRegistry.getBattleByParticipatingPlayer(it.player)?.stop()
@@ -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,6 +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.*
import com.cobblemon.mod.common.block.general.BaleBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
@@ -26,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
@@ -621,37 +623,131 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
@JvmField
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val LIGHT_BALL = create("light_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{13}))
@JvmField
val SMOKE_BALL = create("smoke_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val 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(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val 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))
@JvmField
val SCOPE_LENS = create("scope_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.SCOPE))
@JvmField
val WIDE_LENS = create("wide_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.WIDE))
@JvmField
val ZOOM_LENS = create("zoom_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.ZOOM))
@JvmField
val ICY_ROCK = create("icy_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val HEAT_ROCK = create("heat_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val SMOOTH_ROCK = create("smooth_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val DAMP_ROCK = create("damp_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x6))
@JvmField
val DESTINY_KNOT = create("destiny_knot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.KNOT))
@JvmField
val LUMINOUS_MOSS = create("luminous_moss", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.MOSS).mapColor(MapColor.NONE).noOcclusion().lightLevel{5}, DecorativeItemBlock.DecorativeItemBlockType.CENTERED_8x))
@JvmField
val METRONOME = create("metronome", ActivatableDecorationBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion(), ActivatableDecorationBlock.ActivatableDecorationBlockType.METRONOME))
@JvmField
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
@JvmField
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_DICE_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
@JvmField
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
@JvmField
val METAL_COAT = create("metal_coat", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.METAL_COAT))
@JvmField
val UPGRADE = create("upgrade", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
@JvmField
val TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_METAL_CAN_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
@JvmField
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_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_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@@ -670,6 +766,23 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
@JvmField
@@ -685,6 +798,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
@JvmField
val TM_MACHINE = create(
"tm_machine",
TMMachineBlock(
@@ -713,7 +827,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
@JvmField
val RED_MINT = create("red_mint", MintBlock(MintType.RED, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@JvmField
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@@ -731,29 +845,68 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PEP_UP_FLOWER = this.create("pep_up_flower", FlowerBlock(MobEffects.LEVITATION, 10F, BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).pushReaction(PushReaction.DESTROY)))
@JvmField
val POTTED_PEP_UP_FLOWER = this.create("potted_pep_up_flower", BlocksInvoker.createFlowerPotBlock(PEP_UP_FLOWER))
@JvmField
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak()))
@JvmField
val GALARICA_NUT_BUSH = this.create("galarica_nut_bush", NutBushBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).randomTicks().noCollission().sound(SoundType.SWEET_BERRY_BUSH).pushReaction(PushReaction.DESTROY)))
@JvmField
val POTTED_RED_APRICORN_SAPLING = this.create("potted_red_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(RED_APRICORN_SAPLING))
val RED_MINT_LEAF_LITTER = this.mintLitter("red_mint_leaf_litter", MapColor.COLOR_RED)
@JvmField
val POTTED_YELLOW_APRICORN_SAPLING = this.create("potted_yellow_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(YELLOW_APRICORN_SAPLING))
val POTTED_RED_MINT = this.flowerPot("potted_red_mint", RED_MINT_LEAF_LITTER)
@JvmField
val POTTED_GREEN_APRICORN_SAPLING = this.create("potted_green_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(GREEN_APRICORN_SAPLING))
val BLUE_MINT_LEAF_LITTER = this.mintLitter("blue_mint_leaf_litter", MapColor.COLOR_BLUE)
@JvmField
val POTTED_BLUE_APRICORN_SAPLING = this.create("potted_blue_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLUE_APRICORN_SAPLING))
val POTTED_BLUE_MINT = this.flowerPot("potted_blue_mint", BLUE_MINT_LEAF_LITTER)
@JvmField
val POTTED_PINK_APRICORN_SAPLING = this.create("potted_pink_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(PINK_APRICORN_SAPLING))
val CYAN_MINT_LEAF_LITTER = this.mintLitter("cyan_mint_leaf_litter", MapColor.COLOR_CYAN)
@JvmField
val POTTED_BLACK_APRICORN_SAPLING = this.create("potted_black_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLACK_APRICORN_SAPLING))
val POTTED_CYAN_MINT = this.flowerPot("potted_cyan_mint", CYAN_MINT_LEAF_LITTER)
@JvmField
val POTTED_WHITE_APRICORN_SAPLING = this.create("potted_white_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(WHITE_APRICORN_SAPLING))
val PINK_MINT_LEAF_LITTER = this.mintLitter("pink_mint_leaf_litter", MapColor.COLOR_PINK)
@JvmField
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
val POTTED_PINK_MINT = this.flowerPot("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.flowerPot("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.flowerPot("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)))
@@ -931,6 +1084,165 @@ 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.flowerPot("potted_aguav_berry", AGUAV_BERRY)
@JvmField
val POTTED_APICOT_BERRY = this.flowerPot("potted_apicot_berry", APICOT_BERRY)
@JvmField
val POTTED_ASPEAR_BERRY = this.flowerPot("potted_aspear_berry", ASPEAR_BERRY)
@JvmField
val POTTED_BABIRI_BERRY = this.flowerPot("potted_babiri_berry", BABIRI_BERRY)
@JvmField
val POTTED_BELUE_BERRY = this.flowerPot("potted_belue_berry", BELUE_BERRY)
@JvmField
val POTTED_BLUK_BERRY = this.flowerPot("potted_bluk_berry", BLUK_BERRY)
@JvmField
val POTTED_CHARTI_BERRY = this.flowerPot("potted_charti_berry", CHARTI_BERRY)
@JvmField
val POTTED_CHERI_BERRY = this.flowerPot("potted_cheri_berry", CHERI_BERRY)
@JvmField
val POTTED_CHESTO_BERRY = this.flowerPot("potted_chesto_berry", CHESTO_BERRY)
@JvmField
val POTTED_CHILAN_BERRY = this.flowerPot("potted_chilan_berry", CHILAN_BERRY)
@JvmField
val POTTED_CHOPLE_BERRY = this.flowerPot("potted_chople_berry", CHOPLE_BERRY)
@JvmField
val POTTED_COBA_BERRY = this.flowerPot("potted_coba_berry", COBA_BERRY)
@JvmField
val POTTED_COLBUR_BERRY = this.flowerPot("potted_colbur_berry", COLBUR_BERRY)
@JvmField
val POTTED_CORNN_BERRY = this.flowerPot("potted_cornn_berry", CORNN_BERRY)
@JvmField
val POTTED_CUSTAP_BERRY = this.flowerPot("potted_custap_berry", CUSTAP_BERRY)
@JvmField
val POTTED_DURIN_BERRY = this.flowerPot("potted_durin_berry", DURIN_BERRY)
@JvmField
val POTTED_EGGANT_BERRY = this.flowerPot("potted_eggant_berry", EGGANT_BERRY)
@JvmField
val POTTED_ENIGMA_BERRY = this.flowerPot("potted_enigma_berry", ENIGMA_BERRY)
@JvmField
val POTTED_FIGY_BERRY = this.flowerPot("potted_figy_berry", FIGY_BERRY)
@JvmField
val POTTED_GANLON_BERRY = this.flowerPot("potted_ganlon_berry", GANLON_BERRY)
@JvmField
val POTTED_GREPA_BERRY = this.flowerPot("potted_grepa_berry", GREPA_BERRY)
@JvmField
val POTTED_HABAN_BERRY = this.flowerPot("potted_haban_berry", HABAN_BERRY)
@JvmField
val POTTED_HONDEW_BERRY = this.flowerPot("potted_hondew_berry", HONDEW_BERRY)
@JvmField
val POTTED_HOPO_BERRY = this.flowerPot("potted_hopo_berry", HOPO_BERRY)
@JvmField
val POTTED_IAPAPA_BERRY = this.flowerPot("potted_iapapa_berry", IAPAPA_BERRY)
@JvmField
val POTTED_JABOCA_BERRY = this.flowerPot("potted_jaboca_berry", JABOCA_BERRY)
@JvmField
val POTTED_KASIB_BERRY = this.flowerPot("potted_kasib_berry", KASIB_BERRY)
@JvmField
val POTTED_KEBIA_BERRY = this.flowerPot("potted_kebia_berry", KEBIA_BERRY)
@JvmField
val POTTED_KEE_BERRY = this.flowerPot("potted_kee_berry", KEE_BERRY)
@JvmField
val POTTED_KELPSY_BERRY = this.flowerPot("potted_kelpsy_berry", KELPSY_BERRY)
@JvmField
val POTTED_LANSAT_BERRY = this.flowerPot("potted_lansat_berry", LANSAT_BERRY)
@JvmField
val POTTED_LEPPA_BERRY = this.flowerPot("potted_leppa_berry", LEPPA_BERRY)
@JvmField
val POTTED_LIECHI_BERRY = this.flowerPot("potted_liechi_berry", LIECHI_BERRY)
@JvmField
val POTTED_LUM_BERRY = this.flowerPot("potted_lum_berry", LUM_BERRY)
@JvmField
val POTTED_MAGO_BERRY = this.flowerPot("potted_mago_berry", MAGO_BERRY)
@JvmField
val POTTED_MAGOST_BERRY = this.flowerPot("potted_magost_berry", MAGOST_BERRY)
@JvmField
val POTTED_MARANGA_BERRY = this.flowerPot("potted_maranga_berry", MARANGA_BERRY)
@JvmField
val POTTED_MICLE_BERRY = this.flowerPot("potted_micle_berry", MICLE_BERRY)
@JvmField
val POTTED_NANAB_BERRY = this.flowerPot("potted_nanab_berry", NANAB_BERRY)
@JvmField
val POTTED_NOMEL_BERRY = this.flowerPot("potted_nomel_berry", NOMEL_BERRY)
@JvmField
val POTTED_OCCA_BERRY = this.flowerPot("potted_occa_berry", OCCA_BERRY)
@JvmField
val POTTED_ORAN_BERRY = this.flowerPot("potted_oran_berry", ORAN_BERRY)
@JvmField
val POTTED_PAMTRE_BERRY = this.flowerPot("potted_pamtre_berry", PAMTRE_BERRY)
@JvmField
val POTTED_PASSHO_BERRY = this.flowerPot("potted_passho_berry", PASSHO_BERRY)
@JvmField
val POTTED_PAYAPA_BERRY = this.flowerPot("potted_payapa_berry", PAYAPA_BERRY)
@JvmField
val POTTED_PECHA_BERRY = this.flowerPot("potted_pecha_berry", PECHA_BERRY)
@JvmField
val POTTED_PERSIM_BERRY = this.flowerPot("potted_persim_berry", PERSIM_BERRY)
@JvmField
val POTTED_PETAYA_BERRY = this.flowerPot("potted_petaya_berry", PETAYA_BERRY)
@JvmField
val POTTED_PINAP_BERRY = this.flowerPot("potted_pinap_berry", PINAP_BERRY)
@JvmField
val POTTED_POMEG_BERRY = this.flowerPot("potted_pomeg_berry", POMEG_BERRY)
@JvmField
val POTTED_QUALOT_BERRY = this.flowerPot("potted_qualot_berry", QUALOT_BERRY)
@JvmField
val POTTED_RABUTA_BERRY = this.flowerPot("potted_rabuta_berry", RABUTA_BERRY)
@JvmField
val POTTED_RAWST_BERRY = this.flowerPot("potted_rawst_berry", RAWST_BERRY)
@JvmField
val POTTED_RAZZ_BERRY = this.flowerPot("potted_razz_berry", RAZZ_BERRY)
@JvmField
val POTTED_RINDO_BERRY = this.flowerPot("potted_rindo_berry", RINDO_BERRY)
@JvmField
val POTTED_ROSELI_BERRY = this.flowerPot("potted_roseli_berry", ROSELI_BERRY)
@JvmField
val POTTED_ROWAP_BERRY = this.flowerPot("potted_rowap_berry", ROWAP_BERRY)
@JvmField
val POTTED_SALAC_BERRY = this.flowerPot("potted_salac_berry", SALAC_BERRY)
@JvmField
val POTTED_SHUCA_BERRY = this.flowerPot("potted_shuca_berry", SHUCA_BERRY)
@JvmField
val POTTED_SITRUS_BERRY = this.flowerPot("potted_sitrus_berry", SITRUS_BERRY)
@JvmField
val POTTED_SPELON_BERRY = this.flowerPot("potted_spelon_berry", SPELON_BERRY)
@JvmField
val POTTED_STARF_BERRY = this.flowerPot("potted_starf_berry", STARF_BERRY)
@JvmField
val POTTED_TAMATO_BERRY = this.flowerPot("potted_tamato_berry", TAMATO_BERRY)
@JvmField
val POTTED_TANGA_BERRY = this.flowerPot("potted_tanga_berry", TANGA_BERRY)
@JvmField
val POTTED_TOUGA_BERRY = this.flowerPot("potted_touga_berry", TOUGA_BERRY)
@JvmField
val POTTED_WACAN_BERRY = this.flowerPot("potted_wacan_berry", WACAN_BERRY)
@JvmField
val POTTED_WATMEL_BERRY = this.flowerPot("potted_watmel_berry", WATMEL_BERRY)
@JvmField
val POTTED_WEPEAR_BERRY = this.flowerPot("potted_wepear_berry", WEPEAR_BERRY)
@JvmField
val POTTED_WIKI_BERRY = this.flowerPot("potted_wiki_berry", WIKI_BERRY)
@JvmField
val POTTED_YACHE_BERRY = this.flowerPot("potted_yache_berry", YACHE_BERRY)
@JvmField
val POTTED_PEP_UP_FLOWER = this.flowerPot("potted_pep_up_flower", PEP_UP_FLOWER)
@JvmField
val POTTED_RED_APRICORN_SAPLING = this.flowerPot("potted_red_apricorn_sapling", RED_APRICORN_SAPLING)
@JvmField
val POTTED_YELLOW_APRICORN_SAPLING = this.flowerPot("potted_yellow_apricorn_sapling", YELLOW_APRICORN_SAPLING)
@JvmField
val POTTED_GREEN_APRICORN_SAPLING = this.flowerPot("potted_green_apricorn_sapling", GREEN_APRICORN_SAPLING)
@JvmField
val POTTED_BLUE_APRICORN_SAPLING = this.flowerPot("potted_blue_apricorn_sapling", BLUE_APRICORN_SAPLING)
@JvmField
val POTTED_PINK_APRICORN_SAPLING = this.flowerPot("potted_pink_apricorn_sapling", PINK_APRICORN_SAPLING)
@JvmField
val POTTED_BLACK_APRICORN_SAPLING = this.flowerPot("potted_black_apricorn_sapling", BLACK_APRICORN_SAPLING)
@JvmField
val POTTED_WHITE_APRICORN_SAPLING = this.flowerPot("potted_white_apricorn_sapling", WHITE_APRICORN_SAPLING)
@JvmField
val POTTED_SACCHARINE_SAPLING = this.flowerPot("potted_saccharine_sapling", SACCHARINE_SAPLING)
init {
/**
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
@@ -1032,4 +1344,57 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
}
fun typeGemClusters() = typeGemClusters.toMap()
private fun flowerPot(name: String, flower: Block) = this.create(
name,
FlowerPotBlock(
flower,
BlockBehaviour.Properties.of()
.instabreak()
.noOcclusion()
.pushReaction(PushReaction.DESTROY)
)
)
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
*
@@ -472,18 +472,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
@JvmField val UPGRADE = noSettingsItem("upgrade")
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
@JvmField val METAL_COAT = itemNameBlockItem("metal_coat", CobblemonBlocks.METAL_COAT)
@JvmField val UPGRADE = itemNameBlockItem("upgrade", CobblemonBlocks.UPGRADE)
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
@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")
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
@JvmField val SACHET = noSettingsItem("sachet")
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.SACHET)
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
@@ -492,22 +492,20 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
@JvmField val CHIPPED_POT = itemNameBlockItem("chipped_pot", CobblemonBlocks.CHIPPED_POT)
@JvmField val CRACKED_POT = itemNameBlockItem("cracked_pot", CobblemonBlocks.CRACKED_POT)
@JvmField val MASTERPIECE_TEACUP = itemNameBlockItem("masterpiece_teacup", CobblemonBlocks.MASTERPIECE_TEACUP)
@JvmField val UNREMARKABLE_TEACUP = itemNameBlockItem("unremarkable_teacup", CobblemonBlocks.UNREMARKABLE_TEACUP)
@JvmField val SWEET_APPLE = compostableItem("sweet_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val TART_APPLE = compostableItem("tart_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val SYRUPY_APPLE = compostableItem("syrupy_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
@@ -651,17 +649,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val EXPERIENCE_CANDY_XL = candyItem("exp_candy_xl") { _, _ -> CandyItem.DEFAULT_XL_CANDY_YIELD }
@JvmField
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK))
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
@JvmField
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
@JvmField
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
@JvmField
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
@JvmField
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
@JvmField
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
@JvmField
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
@JvmField
@@ -713,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
@@ -721,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))
@@ -743,34 +741,34 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
@JvmField
val PP_UP = create("pp_up", PPUpItem(1))
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
@JvmField
val PP_MAX = create("pp_max", PPUpItem(3))
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
@JvmField
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
@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>()
@@ -922,11 +920,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
@JvmField val EXP_SHARE = wearableItem("exp_share")
@JvmField val FOCUS_BAND = wearableItem("focus_band")
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
@JvmField val KINGS_ROCK = wearableBlockItem("kings_rock", CobblemonBlocks.KINGS_ROCK)
@JvmField val NEVER_MELT_ICE = wearableBlockItem("never_melt_ice", CobblemonBlocks.NEVER_MELT_ICE)
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
@JvmField val SHARP_BEAK = wearableItem("sharp_beak")
@JvmField val COVERT_CLOAK = wearableItem("covert_cloak")
@JvmField val SHELL_HELMET = wearableItem("shell_helmet")
@JvmField val GALARICA_WREATH = wearableBlockItem("galarica_wreath", CobblemonBlocks.GALARICA_WREATH)
@JvmField val CLEANSE_TAG = wearableBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField val SPELL_TAG = wearableBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
// Held Items
@JvmField
@@ -954,21 +959,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField
val CLEAR_AMULET = heldItem("clear_amulet")
@JvmField
val COVERT_CLOAK = heldItem("covert_cloak")
@JvmField
val DESTINY_KNOT = heldItem("destiny_knot")
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
@JvmField
val DRAGON_FANG = heldItem("dragon_fang")
@JvmField
val EJECT_BUTTON = heldItem("eject_button")
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
@JvmField
val EJECT_PACK = heldItem("eject_pack")
@JvmField
val ELECTRIC_SEED = heldItem("electric_seed")
val ELECTRIC_SEED = itemNameBlockItem("electric_seed", CobblemonBlocks.ELECTRIC_SEED)
@JvmField
val EVERSTONE = heldItem("everstone")
@JvmField
@@ -978,13 +979,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val FAIRY_FEATHER = heldItem("fairy_feather")
@JvmField
val FLAME_ORB = heldItem("flame_orb")
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
@JvmField
val FLOAT_STONE = heldItem("float_stone")
@JvmField
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
@@ -992,34 +993,32 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
@JvmField
val IRON_BALL = heldItem("iron_ball")
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
@JvmField
val LAGGING_TAIL = heldItem("lagging_tail")
@JvmField
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
@JvmField
val LIFE_ORB = heldItem("life_orb")
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
@JvmField
val LIGHT_BALL = heldItem("light_ball")
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
@JvmField
val LIGHT_CLAY = heldItem("light_clay")
@JvmField
val LOADED_DICE = heldItem("loaded_dice")
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
@JvmField
val LUCKY_EGG = heldItem("lucky_egg")
@JvmField
val LUMINOUS_MOSS = heldItem("luminous_moss")
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
@JvmField
val MAGNET = heldItem("magnet")
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
@JvmField
val METRONOME = heldItem("metronome")
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.METRONOME)
@JvmField
val MIRACLE_SEED = heldItem("miracle_seed")
@JvmField
val MYSTIC_WATER = heldItem("mystic_water")
@JvmField
val NEVER_MELT_ICE = heldItem("never_melt_ice")
@JvmField
val POISON_BARB = heldItem("poison_barb")
@JvmField
val POWER_ANKLET = heldItem("power_anklet")
@@ -1034,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
@@ -1044,13 +1043,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val RED_CARD = heldItem("red_card")
@JvmField
val RING_TARGET = heldItem("ring_target")
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
@JvmField
val ROOM_SERVICE = heldItem("room_service")
@JvmField
val SCOPE_LENS = heldItem("scope_lens")
@JvmField
val SHARP_BEAK = heldItem("sharp_beak")
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
@JvmField
val SHED_SHELL = heldItem("shed_shell")
@JvmField
@@ -1060,21 +1057,19 @@ 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 SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
@JvmField
val SMOKE_BALL = heldItem("smoke_ball")
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
@JvmField
val SOOTHE_BELL = heldItem("soothe_bell")
@JvmField
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 = heldItem("throat_spray")
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
@JvmField
val TOXIC_ORB = heldItem("toxic_orb")
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
@JvmField
val TWISTED_SPOON = heldItem("twisted_spoon")
@JvmField
@@ -1082,33 +1077,33 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
@JvmField
val WIDE_LENS = heldItem("wide_lens")
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
@JvmField
val ZOOM_LENS = heldItem("zoom_lens")
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.ZOOM_LENS)
@JvmField
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 0.85F)
@JvmField
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 = heldItem("bright_powder")
val BRIGHT_POWDER = glowItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
@JvmField
val METAL_POWDER = heldItem("metal_powder")
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
@JvmField
val QUICK_POWDER = heldItem("quick_powder")
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
@JvmField
val DAMP_ROCK = heldItem("damp_rock")
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
@JvmField
val HEAT_ROCK = heldItem("heat_rock")
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
@JvmField
val SMOOTH_ROCK = heldItem("smooth_rock")
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
@JvmField
val ICY_ROCK = heldItem("icy_rock")
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
// Mulch
@JvmField
@@ -1563,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)))
@@ -1599,7 +1613,16 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
name,
WearableItem(name).also {
WearableHatItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
}
}
)
private fun wearableBlockItem(name: String, block: Block, heldItemRemappedName: String? = null): WearableBlockItem = create(
name,
WearableBlockItem(name, block).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
@@ -1756,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()))
}
@@ -425,6 +425,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEvolutionDisplaySyncPacket.ID, DexEvolutionDisplaySyncPacket::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()))
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.HelicopterSetting
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.RocketSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.BoardSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
@@ -56,6 +57,7 @@ object CobblemonRideSettings : DataRegistry {
var hover = HoverSettings()
var jet = JetSettings()
var rocket = RocketSettings()
var board = BoardSettings()
var horse = HorseSettings()
var minekart = MinekartSettings()
@@ -75,6 +77,7 @@ object CobblemonRideSettings : DataRegistry {
hover = hover,
jet = jet,
rocket = rocket,
board = board,
horse = horse,
minekart = minekart,
vehicle = vehicle,
@@ -93,6 +96,7 @@ object CobblemonRideSettings : DataRegistry {
hover = loadStyle(manager, "hover", HoverSettings::class.java)
jet = loadStyle(manager, "jet", JetSettings::class.java)
rocket = loadStyle(manager, "rocket", RocketSettings::class.java)
board = loadStyle(manager, "board", BoardSettings::class.java)
horse = loadStyle(manager, "horse", HorseSettings::class.java)
minekart = loadStyle(manager, "minekart", MinekartSettings::class.java)
vehicle = loadStyle(manager, "vehicle", VehicleSettings::class.java)
@@ -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,95 @@ 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
)
@JvmField
val ITEM_BLOCK_METAL_CAN_PLACE = this.create("block.item_block.metal_can.place")
@JvmField
val ITEM_BLOCK_METAL_CAN_BREAK = this.create("block.item_block.metal_can.break")
@JvmField
val ITEM_BLOCK_METAL_CAN_STEP = this.create("block.item_block.metal_can.step")
@JvmField
val ITEM_BLOCK_METAL_CAN_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_METAL_CAN_BREAK,
ITEM_BLOCK_METAL_CAN_STEP,
ITEM_BLOCK_METAL_CAN_PLACE,
ITEM_BLOCK_METAL_CAN_STEP,
ITEM_BLOCK_METAL_CAN_STEP
)
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE = this.create("block.item_block.jar_glass_liquid.place")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK = this.create("block.item_block.jar_glass_liquid.break")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_STEP = this.create("block.item_block.jar_glass_liquid.step")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
@@ -17,6 +18,7 @@ import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.withQueryValue
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
@@ -34,6 +36,8 @@ import net.minecraft.world.entity.ai.sensing.SensorType
class HateEntityTaskConfig : SingleTaskConfig {
val entityCondition: Expression = "true".asExpression()
val range: ExpressionOrEntityVariable = Either.left("24".asExpression())
val chance: Float = Cobblemon.config.predationChance
private val coercedChance = chance.coerceIn(0f, 1f)
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
override fun createTask(
@@ -43,24 +47,44 @@ class HateEntityTaskConfig : SingleTaskConfig {
val range = range.resolveFloat(behaviourConfigurationContext.runtime)
behaviourConfigurationContext.addMemories(
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
MemoryModuleType.ANGRY_AT
MemoryModuleType.ANGRY_AT,
MemoryModuleType.ATTACK_TARGET
)
behaviourConfigurationContext.addSensors(SensorType.NEAREST_LIVING_ENTITIES)
return BehaviorBuilder.create { instance ->
instance.group(
instance.present(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES),
instance.registered(MemoryModuleType.ANGRY_AT)
).apply(instance) { entities, angryAt ->
instance.registered(MemoryModuleType.ANGRY_AT),
instance.registered(MemoryModuleType.ATTACK_TARGET),
).apply(instance) { entities, angryAt, attackTarget ->
Trigger { world, entity, _ ->
val target = instance.get(entities).findClosest {
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// bail if gamerule is disabled
if (!world.gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_PREDATION)) return@Trigger false
val target = instance.get(entities).findClosest { hated ->
mainThreadRuntime.withQueryValue("entity", hated.asMostSpecificMoLangValue())
return@findClosest mainThreadRuntime.resolveBoolean(entityCondition)
}.orElse(null)
var shouldTrigger = false
if (target != null && target.distanceTo(entity) <= range) {
angryAt.set(target.uuid)
shouldTrigger = when {
// set always attacking if activity chance is equal to 1.0
coercedChance == 1f -> {
angryAt.set(target.uuid)
attackTarget.set(target)
true
}
// roll the dice in other way
coercedChance != 1f && world.random.nextFloat() < coercedChance -> {
angryAt.set(target.uuid)
attackTarget.set(target)
true
}
// fallback to peaceful behavior in unattended cases
else -> false
}
}
return@Trigger true
return@Trigger shouldTrigger
}
}
}
@@ -0,0 +1,25 @@
/*
* 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.battles.model
/**
* How a [PokemonBattle] came to an end.
*/
enum class BattleEndResult {
/** One side defeated the other. */
VICTORY,
/** A wild Pokémon was captured, ending the battle. */
CAPTURE,
/** An actor forfeited, giving the victory to the other side. */
FORFEIT,
/** A player fled from a wild Pokémon. */
FLED,
/** The battle was stopped without a winner, such as by a command, a player disconnecting or an error. */
STOPPED
}
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
@@ -124,6 +125,12 @@ open class PokemonBattle(
var winners = listOf<BattleActor>()
var losers = listOf<BattleActor>()
var ended = false
/** How the battle ended, or null if it hasn't ended yet. */
var endResult: BattleEndResult? = null
/** The actor that forfeited the battle, if any. */
var forfeitedBy: BattleActor? = null
/** Whether a player fled from the battle. */
var fled = false
// TEMP battle showcase stuff
var announcingRules = false
var turn: Int = 0
@@ -232,6 +239,10 @@ open class PokemonBattle(
}
fun writeShowdownAction(vararg messages: String) {
if (messages.any { it.contains("\n") || it.contains("\r") }) {
LOGGER.warn("Refusing to forward Showdown action with an embedded line break for battle {}: {}", battleId, messages.joinToString(" | "))
return
}
log(messages.joinToString("\n"))
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
}
@@ -251,6 +262,14 @@ open class PokemonBattle(
fun end() {
ended = true
val result = when {
winners.isNotEmpty() && showdownMessages.any { "capture" in it } -> BattleEndResult.CAPTURE
winners.isNotEmpty() && forfeitedBy != null -> BattleEndResult.FORFEIT
winners.isNotEmpty() -> BattleEndResult.VICTORY
fled -> BattleEndResult.FLED
else -> BattleEndResult.STOPPED
}
endResult = result
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
this.actors.forEach { actor ->
@@ -332,6 +351,7 @@ open class PokemonBattle(
}
}
}
CobblemonEvents.BATTLE_ENDED.post(BattleEndedEvent(this, result, forfeitedBy.takeIf { result == BattleEndResult.FORFEIT }))
sendUpdate(BattleEndPacket())
BattleRegistry.closeBattle(this)
}
@@ -502,6 +522,7 @@ open class PokemonBattle(
.forEach{it.pokemon.heal()}
CobblemonEvents.BATTLE_FLED.post(BattleFledEvent(this, actors.asSequence().filterIsInstance<PlayerBattleActor>().iterator().next()))
actors.filterIsInstance<EntityBackedBattleActor<*>>().mapNotNull { it.entity }.forEach { it.sendSystemMessage(battleLang("flee").yellow()) }
fled = true
stop()
}
}
@@ -539,6 +560,7 @@ open class PokemonBattle(
private fun checkForfeit(): Boolean {
val forfeit = actors.find { it.responses.any { it is ForfeitActionResponse } }
return forfeit?.let {
forfeitedBy = it
this.dispatchWaiting { this.broadcastChatMessage(battleLang("forfeit", it.getName()).red()) }
writeShowdownAction(">forcelose ${it.showdownId}")
true
@@ -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"
}
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
@@ -139,6 +140,9 @@ object CobblemonEvents {
@JvmField
val BATTLE_FAINTED = EventObservable<BattleFaintedEvent>()
@JvmField
val BATTLE_ENDED = EventObservable<BattleEndedEvent>()
// instructions
@JvmField
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
@@ -0,0 +1,69 @@
/*
* 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.events.battles
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.BattleEndResult
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.util.asArrayValue
/**
* Event fired whenever a [PokemonBattle] ends, regardless of how it ended (victory, fleeing, being stopped, etc).
*
* [PokemonBattle.winners] and [PokemonBattle.losers] are only populated if the [result] has a winner,
* that is [BattleEndResult.VICTORY], [BattleEndResult.CAPTURE] or [BattleEndResult.FORFEIT].
*
* @param result How the battle ended.
* @param forfeitedBy The actor that forfeited, if the [result] is [BattleEndResult.FORFEIT].
*/
data class BattleEndedEvent(
override val battle: PokemonBattle,
val result: BattleEndResult,
val forfeitedBy: BattleActor?
) : BattleEvent {
/** Whether one side won the battle, which includes captures and forfeits. */
val hasWinner = battle.winners.isNotEmpty()
val wasVictory = result == BattleEndResult.VICTORY
val wasCapture = result == BattleEndResult.CAPTURE
val wasForfeit = result == BattleEndResult.FORFEIT
val wasFled = result == BattleEndResult.FLED
val wasStopped = result == BattleEndResult.STOPPED
/** Whether the battle was between players and wild Pokémon. */
val isPvW = battle.isPvW
/** Whether the battle was between players and NPCs. */
val isPvN = battle.isPvN
/** Whether the battle was between players. */
val isPvP = battle.isPvP
val context = mutableMapOf<String, MoValue>(
"battle" to battle.struct,
"result" to StringValue(result.name.lowercase()),
"has_winner" to DoubleValue(hasWinner),
"was_victory" to DoubleValue(wasVictory),
"was_capture" to DoubleValue(wasCapture),
"was_forfeit" to DoubleValue(wasForfeit),
"was_fled" to DoubleValue(wasFled),
"was_stopped" to DoubleValue(wasStopped),
"is_pvw" to DoubleValue(isPvW),
"is_pvn" to DoubleValue(isPvN),
"is_pvp" to DoubleValue(isPvP),
"winners" to battle.winners.asArrayValue { it.struct },
"losers" to battle.losers.asArrayValue { it.struct },
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
).apply {
forfeitedBy?.let { put("forfeited_by", it.struct) }
}
}
@@ -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.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.util.asArrayValue
object BattleActorMoLangFunctions : AbstractMoLangFunctionHolder<BattleActor>() {
override fun BattleActor.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
@@ -25,6 +26,7 @@ object BattleActorMoLangFunctions : AbstractMoLangFunctionHolder<BattleActor>()
functions["is_npc"] = { DoubleValue(actor.type == ActorType.NPC) }
functions["is_player"] = { DoubleValue(actor.type == ActorType.PLAYER) }
functions["is_wild"] = { DoubleValue(actor.type == ActorType.WILD) }
functions["battle_pokemon"] = { actor.pokemonList.asArrayValue { it.struct } }
when (actor) {
is NPCBattleActor -> {
@@ -55,6 +55,11 @@ object GeneralMoLangFunctions {
struct.map.remove(variable)
DoubleValue.ONE
}
functions["delete_variables"] = { params ->
val struct = params.get<VariableStruct>(0)
struct.map.clear()
DoubleValue.ONE
}
functions["get_variable"] = { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
@@ -98,14 +98,14 @@ object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>(
val targetEntity = params.get<MoValue>(0)
val maxDistance = params.getDoubleOrNull(1)?.toFloat() ?: 2.0F
val entity =
val target =
if (targetEntity is ObjectValue<*> && targetEntity.obj is Entity) {
targetEntity.obj as Entity
} else {
return@isLookingAt DoubleValue.ZERO
}
DoubleValue(entity.isLookingAt(entity, maxDistance))
DoubleValue(entity.isLookingAt(target, maxDistance))
}
functions["is_living_entity"] = { DoubleValue.ONE }
functions["is_flying"] = { DoubleValue(entity.isFallFlying) }
@@ -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()) }
@@ -212,7 +212,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
// Lol. Lmao, even.
// TO-DO: Subscribe to [PokemonSpecies.observable].
functions["pre_evolution"] = { pokemon.species.preEvolution ?: DoubleValue.ZERO }
functions["nature"] = { StringValue(pokemon.nature.toString()) }
functions["nature"] = { StringValue(pokemon.nature.name.toString()) }
functions["is_wild"] = { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
functions["is_shiny"] = { DoubleValue(pokemon.shiny) }
functions["is_in_party"] = { DoubleValue(pokemon.storeCoordinates.get()?.store is PartyStore) }
@@ -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,9 @@ 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
// Seconds for the render orientation to close half the gap to the real one. Raise for smoother but laggier visuals
private const val RENDER_SMOOTH_HALFLIFE = 0.15f
}
open var active: Boolean = false
@@ -33,6 +38,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 +61,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 +86,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, RENDER_SMOOTH_HALFLIFE, TICK_DT)
val newRenderOrientation = Matrix3f()
renderQuat.get(newRenderOrientation)
renderSpring.quat.get(newRenderOrientation)
renderOrientation = newRenderOrientation
}
@@ -108,7 +108,7 @@ object CatchRateModifiers {
* Used by [PokeBalls.SAFARI_BALL].
* Checks if the target is not battling, if true boost by *1.5
*/
val SAFARI = WorldStateModifier { _, entity -> if (!entity.isBattling) 1.5F else 1F }
val SAFARI = MultiplierModifier(1.5f) { _, poke -> !poke.entity?.isBattling!! }
/**
* Used by [PokeBalls.PARK_BALL].
@@ -13,6 +13,10 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Gender
import net.minecraft.resources.ResourceLocation
@@ -72,6 +76,57 @@ abstract class AbstractPokedexManager {
} ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?): PokedexEntryProgress {
if (form == null) return getHighestKnowledgeFor(entry)
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.UNREGISTERED
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
if (!hasAllAspects) {
return PokedexEntryProgress.UNREGISTERED
}
return speciesRecord.getFormRecord(form.displayForm)?.knowledge ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?, aspects: Iterable<String>): PokedexEntryProgress {
val formKnowledge = getHighestKnowledgeFor(entry, form)
if (formKnowledge == PokedexEntryProgress.UNREGISTERED) return PokedexEntryProgress.UNREGISTERED
val seenAspects = getSeenAspects(entry)
// Aspects that select one of the species' own forms are already covered by the form knowledge check above,
// so exempt them (and every other value of the same feature, since defaults like "phony" never appear on a form directly).
val species = PokemonSpecies.getByIdentifier(entry.speciesId)
val formAspects = species?.forms?.flatMapTo(hashSetOf()) { it.aspects } ?: emptySet()
val formSelectingFeatureAspects = species?.let(SpeciesFeatures::getFeaturesFor).orEmpty()
.mapNotNull { provider ->
val allAspects: Collection<String> = when (provider) {
is ChoiceSpeciesFeatureProvider -> provider.getAllAspects()
is WeightedChoiceSpeciesFeatureProvider -> provider.getAllAspects().keys
else -> return@mapNotNull null
}
allAspects.takeIf { it.any { aspect -> aspect in formAspects } }
}
.flatten()
.toHashSet()
val allSeenAspects = aspects
.filter { aspect ->
if (Dexes.equivalentFormsLookup[aspect] != null) return@filter false
if (aspect in formAspects || aspect in formSelectingFeatureAspects) return@filter false
if (aspect.uppercase() in setOf(Gender.MALE.name, Gender.FEMALE.name, Gender.GENDERLESS.name)) return@filter false
if ("shiny".equals(aspect, ignoreCase = true)) return@filter false
return@filter true
}
.all { aspect -> seenAspects.contains(aspect) }
if (!allSeenAspects) return PokedexEntryProgress.UNREGISTERED
return formKnowledge
}
fun getEncounteredForms(entry: PokedexEntry) : List<PokedexForm> {
return getFormsWithKnowledge(entry, PokedexEntryProgress.SEEN)
}
@@ -40,6 +40,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 +60,9 @@ object Dexes : JsonDataRegistry<PokedexDef> {
override fun sync(player: ServerPlayer) {
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
}
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]. */
@@ -8,13 +8,20 @@
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
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.DexEntrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.DexEvolutionDisplaySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
@@ -38,6 +45,8 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
val entries = mutableMapOf<ResourceLocation, PokedexEntry>()
val evolutionDisplays = mutableListOf<DexEvolutionDisplay>()
override fun reload(data: Map<ResourceLocation, PokedexEntry>) {
data.forEach { _, entry ->
entries[entry.id] = entry
@@ -50,6 +59,7 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
}
}
}
computeEvolutionDisplays()
observable.emit(this)
}
@@ -57,5 +67,58 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
override fun sync(player: ServerPlayer) {
DexEntrySyncPacket(entries.values).sendToPlayer(player)
DexEvolutionDisplaySyncPacket(evolutionDisplays).sendToPlayer(player)
}
}
fun computeEvolutionDisplays() {
evolutionDisplays.clear()
PokemonSpecies.species.forEach {
try {
evolutionDisplays.addAll(buildDisplays(it.standardForm) + it.forms.flatMap { form -> buildDisplays(form) })
} catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to build dex evolution displays for ${it.resourceIdentifier}", e)
}
}
}
fun buildDisplays(form: FormData): List<DexEvolutionDisplay> {
val result = mutableListOf<DexEvolutionDisplay>()
(form.ownEvolutions ?: form.evolutions).forEach { evolution ->
val requirements = mutableListOf<DexEvolutionRequirementDisplay>()
getToolTipHeaderIcon(evolution)?.let { header ->
requirements.add(header)
}
evolution.requirements.mapNotNull { resolveRequirementData(it) }.forEach { requirement ->
requirements.add(requirement)
}
listOfNotNull(evolution.result, evolution.shedder).forEach resultLoop@{ resultProperties ->
val resultSpecies = resultProperties.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return@resultLoop
// The result's own aspects win, otherwise carry the source form's aspects over
// like a real evolution does (Alola-Bias Pichu -> Alola-Bias Pikachu)
val resultForm = resultSpecies.getForm(resultProperties.aspects).takeIf { it != resultSpecies.standardForm }
?: resultSpecies.getForm(form.aspects.toSet() + resultProperties.aspects)
val toAspects = resultProperties.aspects + resultForm.aspects
val resultRequirements = if (resultProperties === evolution.shedder) {
requirements + CobblemonDexEvolutionRequirementDisplay(CobblemonItems.POKE_BALL, lang("ui.pokedex.evolution.shedder.tooltip"))
} else {
requirements
}
result.add(DexEvolutionDisplay(
fromSpecies = form.species.resourceIdentifier,
fromForm = form.name,
fromAspects = form.aspects.toSet(),
toSpecies = resultSpecies.resourceIdentifier,
toForm = resultForm.name,
toAspects = toAspects,
requirements = resultRequirements,
))
}
}
return result
}
}
@@ -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.api.pokedex.entry
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.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
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.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.adapters.RegistryAccessResolver
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.ExtraCodecs
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
import kotlin.jvm.optionals.getOrNull
data class DexEvolutionDisplay(
val fromSpecies: ResourceLocation,
val fromForm: String,
val fromAspects: Set<String>,
val toSpecies: ResourceLocation,
val toForm: String,
val toAspects: Set<String>,
val requirements: List<DexEvolutionRequirementDisplay>,
)
interface DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
}
data class CobblemonDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
) : DexEvolutionRequirementDisplay
data class CobblemonBiomeDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
val condition: ExtraCodecs.TagOrElementLocation? = null,
val antiCondition: ExtraCodecs.TagOrElementLocation? = null,
) : DexEvolutionRequirementDisplay
fun getToolTipHeaderIcon(evolution: Evolution): DexEvolutionRequirementDisplay? {
when (evolution) {
is ItemInteractionEvolution -> return getToolTipHeaderIcon(evolution)
is TradeEvolution -> return getToolTipHeaderIcon(evolution)
}
return null
}
fun getToolTipHeaderIcon(itemInteractionEvolution: ItemInteractionEvolution): CobblemonDexEvolutionRequirementDisplay? {
val item = itemInteractionEvolution.requiredContext.items.orElse(null)?.firstOrNull()?.value() ?: return null
return CobblemonDexEvolutionRequirementDisplay(item, lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
}
fun getToolTipHeaderIcon(tradeEvolution: TradeEvolution): CobblemonDexEvolutionRequirementDisplay {
val species = tradeEvolution.requiredContext.species
val tooltip = if (species == null) {
lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
} else {
PokemonSpecies.getByIdentifier(species.asIdentifierDefaultingNamespace())
?.let { lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.plainCopy().green()) }
?: lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
}
return CobblemonDexEvolutionRequirementDisplay(CobblemonItems.LINK_CABLE, tooltip)
}
fun resolveRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
val icon = requirement.pokedexIcon?.let {
RegistryAccessResolver.getAccess().registryOrThrow(Registries.ITEM).get(it)
}
val tooltip = requirement.pokedexTooltip?.let {
Component.translatable(it)
}
if (icon != null && tooltip != null) {
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
return buildRequirementData(requirement)
}
fun buildRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
when (requirement) {
is AdvancementRequirement -> return buildAdvancementRequirement(requirement)
is BattleCriticalHitsRequirement -> return buildBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> return buildBiomeRequirement(requirement)
is BlocksTraveledRequirement -> return buildBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> return buildDamageTakenRequirement(requirement)
is DefeatRequirement -> return buildDefeatRequirement(requirement)
is FriendshipRequirement -> return buildFriendshipRequirement(requirement)
is HeldItemRequirement -> return buildHeldItemRequirement(requirement)
is LevelRequirement -> return buildLevelRequirement(requirement)
is MoonPhaseRequirement -> return buildMoonPhaseRequirement(requirement)
is MoveSetRequirement -> return buildMoveSetRequirement(requirement)
is MoveTypeRequirement -> return buildMoveTypeRequirement(requirement)
is NegateRequirement -> return buildNegateRequirement(requirement)
is OwnerHoldsItemRequirement -> return buildOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> return buildPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> return buildPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> return buildPropertyRangeRequirement(requirement)
is RecoilRequirement -> return buildRecoilRequirement(requirement)
is StatCompareRequirement -> return buildStatCompareRequirement(requirement)
is StatEqualRequirement -> return buildStatEqualRequirement(requirement)
is StructureRequirement -> return buildStructureRequirement(requirement)
is TimeRangeRequirement -> return buildTimeRangeRequirement(requirement)
is UseMoveRequirement -> return buildUseMoveRequirement(requirement)
is WeatherRequirement -> return buildWeatherRequirement(requirement)
}
return null
}
fun buildAdvancementRequirement(advancementRequirement: AdvancementRequirement): DexEvolutionRequirementDisplay {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): DexEvolutionRequirementDisplay {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
private fun tagDisplayName(registry: String, id: ResourceLocation): MutableComponent {
return Component.translatableWithFallback("tag.$registry.${id.namespace}.${id.path.replace('/', '.')}", prettifyPath(id.path))
}
private fun prettifyPath(path: String): String {
return path.substringAfterLast('/').removePrefix("is_").split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
}
fun buildNegateRequirement(negateRequirement: NegateRequirement): DexEvolutionRequirementDisplay? {
val inner = negateRequirement.inner
when (inner) {
is BiomeRequirement -> buildBiomeRequirement(inner, negated = true)?.let { return it }
is StructureRequirement -> buildStructureRequirement(inner, negated = true)?.let { return it }
}
val innerDisplay = resolveRequirementData(inner) ?: return null
return CobblemonDexEvolutionRequirementDisplay(innerDisplay.icon, lang("ui.pokedex.requirement.negate.tooltip", innerDisplay.tooltip))
}
fun buildBiomeRequirement(biomeRequirement: BiomeRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
// Negating "in X" is "not in X"; negating both a condition and an anticondition isn't expressible as one line
if (negated && biomeRequirement.biomeCondition != null && biomeRequirement.biomeAnticondition != null) return null
val condition = if (negated) biomeRequirement.biomeAnticondition else biomeRequirement.biomeCondition
val antiCondition = if (negated) biomeRequirement.biomeCondition else biomeRequirement.biomeAnticondition
if (condition == null && antiCondition == null) {
return null
}
val icon = Items.COMPASS
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.biome", it.tag.location())
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())
}
var conditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (condition != null) {
val conditionIdentifier = if (condition is RegistryLikeIdentifierCondition) condition.identifier else (condition as RegistryLikeTagCondition).tag.location
conditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, condition is RegistryLikeTagCondition)
}
var antiConditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (antiCondition != null) {
val conditionIdentifier = if (antiCondition is RegistryLikeIdentifierCondition) antiCondition.identifier else (antiCondition as RegistryLikeTagCondition).tag.location
antiConditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, antiCondition is RegistryLikeTagCondition)
}
return CobblemonBiomeDexEvolutionRequirementDisplay(icon, tooltip, conditionConverted, antiConditionConverted)
}
fun buildBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): DexEvolutionRequirementDisplay {
val icon = Items.LEATHER_BOOTS
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.PUNCHING_GLOVE
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDefeatRequirement(defeatRequirement: DefeatRequirement): DexEvolutionRequirementDisplay? {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildFriendshipRequirement(friendshipRequirement: FriendshipRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.SOOTHE_BELL
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildHeldItemRequirement(heldItemRequirement: HeldItemRequirement): DexEvolutionRequirementDisplay? {
val items = heldItemRequirement.itemCondition.items.getOrNull() ?: return null
val item = items.firstOrNull()?.value() ?: return null
val itemComponent = items.unwrapKey().map { tag ->
val id = tag.location()
val fallback = id.path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
Component.translatableWithFallback("tag.item.${id.namespace}.${id.path.replace('/', '.')}", fallback)
}.orElseGet { item.description.plainCopy() }
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", itemComponent.green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildLevelRequirement(levelRequirement: LevelRequirement): DexEvolutionRequirementDisplay {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): DexEvolutionRequirementDisplay {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveSetRequirement(moveSetRequirement: MoveSetRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): DexEvolutionRequirementDisplay {
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.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): DexEvolutionRequirementDisplay? {
val item = ownerHoldsItemRequirement.itemCondition.items.orElse(null)?.firstOrNull()?.value() ?: return null
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): DexEvolutionRequirementDisplay? {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
val icon = Items.NAME_TAG
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): DexEvolutionRequirementDisplay? {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildRecoilRequirement(recoilRequirement: RecoilRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.LIFE_ORB
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatCompareRequirement(statCompareRequirement: StatCompareRequirement): DexEvolutionRequirementDisplay {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatEqualRequirement(statEqualRequirement: StatEqualRequirement): DexEvolutionRequirementDisplay {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStructureRequirement(structureRequirement: StructureRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
if (negated && structureRequirement.structureCondition != null && structureRequirement.structureAnticondition != null) return null
val condition = if (negated) structureRequirement.structureAnticondition else structureRequirement.structureCondition
val anticondition = if (negated) structureRequirement.structureCondition else structureRequirement.structureAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.FILLED_MAP
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.structure", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.literal(prettifyPath(it.identifier.path))
else -> Component.literal(it.toString())
}
}
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): DexEvolutionRequirementDisplay? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName()?.plainCopy() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildUseMoveRequirement(useMoveRequirement: UseMoveRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildWeatherRequirement(weatherRequirement: WeatherRequirement): DexEvolutionRequirementDisplay {
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 CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
@@ -0,0 +1,142 @@
/*
* 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.pokedex.entry
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.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
fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
if (hasUndescribedProperty(properties)) {
return null
}
val descriptors = mutableListOf<MutableComponent>()
properties.species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }?.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(property.value.asIdentifierDefaultingNamespace())?.translatedName?.plainCopy()
?: Component.literal(property.value)
return lang(key, value.green())
}
@@ -26,7 +26,6 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
@@ -128,7 +127,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)
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.ParticleStatus
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
@@ -40,13 +42,25 @@ sealed class ParticleData {
val chance: Double,
val amount: Int,
val locatorResolver: (Map<String, MatrixWrapper>) -> List<String>
): ParticleData()
): ParticleData() {
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
ParticleStatus.ALL -> amount
ParticleStatus.DECREASED -> amount / 2
ParticleStatus.MINIMAL -> amount / 4
}
}
data class MinecraftParticle(
val particle: ParticleOptions,
val chance: Double,
val amount: Int
): ParticleData()
): ParticleData() {
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
ParticleStatus.ALL -> amount
ParticleStatus.DECREASED -> amount / 2
ParticleStatus.MINIMAL -> amount / 4
}
}
}
/**
@@ -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,53 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.orientation.OrientationController
import net.minecraft.util.SmoothDouble
import net.minecraft.world.phys.Vec3
/**
* One frame of steering for a rolling ride on the client that's driving it.
* The mouse values are the share the ride took in consumeLook, already through the riding
* sensitivity, invert and swap options and still in mouse units.
*/
class RideSteerInput(
/** The ride's own orientation controller */
val controller: OrientationController,
val mouseYaw: Double,
val mousePitch: Double,
val mouseXSmoother: SmoothDouble,
val mouseYSmoother: SmoothDouble,
/** Vanilla's mouse sensitivity curve, cubed */
val sensitivity: Double,
/** Seconds since the last frame */
val deltaTime: Double,
private val yawSmoother: SmoothDouble,
private val pitchSmoother: SmoothDouble,
private val rollSmoother: SmoothDouble
) {
companion object {
// Share of each push the smoothers get. Lower makes smoothed turning softer
const val SMOOTHED_SHARE = 0.5
}
/** Turns the ride by local yaw, pitch and roll degrees right away. */
fun rotate(rotation: Vec3) {
controller.rotate(rotation.x.toFloat(), rotation.y.toFloat(), rotation.z.toFloat())
}
/** Turns the ride by local yaw, pitch and roll degrees eased through vanilla's mouse smoother so it spreads over a few frames. */
fun rotateSmoothed(rotation: Vec3) {
controller.rotate(
yawSmoother.getNewDeltaValue(rotation.x * SMOOTHED_SHARE, deltaTime).toFloat(),
pitchSmoother.getNewDeltaValue(rotation.y * SMOOTHED_SHARE, deltaTime).toFloat(),
rollSmoother.getNewDeltaValue(rotation.z * SMOOTHED_SHARE, deltaTime).toFloat()
)
}
}
@@ -12,15 +12,20 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf
/**
* Represents the behaviour of a Pokemon when being ridden.
@@ -41,30 +46,39 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun pose(settings: Settings, state: State, vehicle: PokemonEntity): PoseType
/** Ride speed for vanilla's setSpeed. Only reads state, anything that changes state goes in [driverTick]. */
fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
/**
* Runs once a tick only on the client that's steering, after [tick] and the ride's rotation.
* Whatever it works out reaches the server and other players through the riding state sync.
*/
fun driverTick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player) {}
fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2
/**
* Ride velocity for this tick in world space blocks per tick.
* [RideFrame.yawToWorld] turns a velocity worked out along the ride's yaw into world space.
*/
fun velocity(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3): Vec3
fun angRollVel(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, deltaTime: Double): Vec3
/**
* True to have the driver's client call [velocity] a second time each tick inside travel.
* Vanilla already asks for it once in getRiddenInput and travel always asked again, so every ride's
* numbers are tuned around running twice a tick. Return false once a ride is retuned for a single run.
*/
fun velocityRunsTwicePerTick(settings: Settings, state: State, vehicle: PokemonEntity): Boolean = true
fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3
/**
* Turns the ride for one frame while it rolls. Runs every frame on the client that's driving with the mouse
* share the ride took in [consumeLook]. Rotate through [RideSteerInput.controller] or its helpers.
*/
fun steer(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: RideSteerInput) {}
fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean
@@ -76,8 +90,6 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double
fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
@@ -111,10 +123,22 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun applyRenderRotation(settings: Settings, state: State, vehicle: PokemonEntity, partialTicks: Float) {}
/**
* Determines if the pair of mouse inputs Pair(mouseX, mouseY) get used to modify the driverRotations or the vehicle rotations themselves.
* Driver rotations are the rotations used by passengers or a driver that is freelooking. This does not override the freelook button.
* How much of this frame's mouse look the ride takes to steer with. What it takes goes through
* [steer] and the rest moves the rider's look. Only asked for the driver and never while freelooking.
*/
fun mouseModifiesDriverRotation(settings: Settings, state: State, vehicle: PokemonEntity): Pair<Boolean, Boolean> = Pair(false, false)
fun consumeLook(settings: Settings, state: State, vehicle: PokemonEntity, input: RideLookInput): RideLookTake = RideLookTake.all(input)
/**
* Rotation the rider's camera hangs off while the ride rolls. Called every frame on the client
* and the rider's look sits on top of it. The player's disableRoll setting still applies after this.
*/
fun cameraBase(settings: Settings, state: State, vehicle: PokemonEntity, context: RideCameraContext): Quaternionf = context.defaultBase()
/** Degrees to roll the first person hands and held items */
fun firstPersonHandRoll(settings: Settings, state: State, vehicle: PokemonEntity, context: RideCameraContext): Float = 0f
/** Look limits and carry for riders while the ride rolls. */
fun lookSettings(settings: Settings, state: State, vehicle: PokemonEntity): RideLookSettings = RideLookSettings.DEFAULT
/**
* Used in MoLang functions for querying states for animations or other magical MoLang effects.
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.ClimbBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
@@ -35,7 +36,9 @@ object RidingBehaviours {
register(MinekartBehaviour.KEY, MinekartBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(RocketBehaviour.KEY, RocketBehaviour())
register(BoardBehaviour.KEY, BoardBehaviour())
register(SubmarineBehaviour.KEY, SubmarineBehaviour())
register(ClimbBehaviour.KEY, ClimbBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
@@ -15,8 +15,13 @@ import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.FORWARDS
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
@@ -24,7 +29,9 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.FloatSpring
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.simpleSpringDamper
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -34,6 +41,8 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.*
@@ -42,6 +51,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val KEY = cobblemonResource("air/bird")
private const val FLY_POSE_SPEED = 0.1
// At or below this the bird hovers instead of flying
private const val HOVER_SPEED = 0.1
// Within this many degrees of straight up or down wings level means nothing so the camera holds its roll
private const val VERTICAL_DEADZONE_DEGREES = 15f
private val MIN_LEVEL_UP_LENGTH_SQ = sin(VERTICAL_DEADZONE_DEGREES.toRadians()).pow(2)
// Below this speed the bird is too slow to bank so its wings always level out
private const val NO_BANK_SPEED = 0.2
// Degrees before the max roll where rolling further starts easing off. Raise for a softer stop
private const val ROLL_CAP_BAND_DEGREES = 20f
// Degrees past the turn roll limit over which mouse pitch hands over from world up and down to the bird's own pitch
private const val PITCH_BLEND_BAND_DEGREES = 15f
// Pitch where mouse pitch starts handing back to the bird's own pitch. By straight up or down it's all the bird's
// own so a loop carries on over the top instead of world up pulling it back
private const val WORLD_PITCH_FADE_START = 60f
// Fastest the wings level out by themselves in degrees per second
private const val MAX_ROLL_SETTLE_RATE = 45f
// Within this many degrees of level the settle rate eases off so it lands on level instead of overshooting
private const val ROLL_SETTLE_ARRIVAL_DEGREES = 35f
// How fast the settle rate can change in degrees per second squared. Same as the old per frame cap at 60 fps
private const val ROLL_SETTLE_ACCEL = 67.5f
// Keeps the turn roll limit from dividing by zero
private const val MIN_TURN_ROLL_LIMIT = 1f
private const val GLIDE_ENTRY_SPEED = 0.19
private const val GLIDE_EXIT_SPEED = 0.13
}
@@ -84,25 +115,17 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
) {
if(vehicle.level().isClientSide) {
tickStamina(settings, state, vehicle, driver)
checkUpsideDown(state, vehicle, driver)
checkStaminaDive(state)
}
}
private fun checkUpsideDown(
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
) {
// Also checks stamina
val controller = (vehicle as OrientationControllable).orientationController
if (cos(controller.roll.toRadians()) < 0.0) {
val upsideDownRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out upsideDown force
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate * abs(cos(controller.roll.toRadians()))))
} else if (state.stamina.get() == 0.0f) { // checks stamina depletion and forces dive if depleted
val upsideDownRate = 1.0 / (2.0 * 20.0) // 1 seconds to max out
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate))
} else { // Its right side up or has stamina so reset it
state.currUpsideDownForce.set(0.0)
// Out of stamina the bird can't hold itself up so it builds up a dive
private fun checkStaminaDive(state: BirdState) {
if (state.stamina.get() == 0.0f) {
val diveRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out
state.staminaDiveForce.set(min(1.0, state.staminaDiveForce.get() + diveRate))
} else {
state.staminaDiveForce.set(0.0)
}
}
@@ -161,8 +184,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
// The downward force used to encourage players to stop flying upside down.
val extraDownwardForce = state.currUpsideDownForce.get().pow(2) * -0.3 // 6 blocks a second downward
// Pulls the bird down once it's out of stamina
val extraDownwardForce = state.staminaDiveForce.get().pow(2) * -0.3 // 6 blocks a second downward
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
@@ -170,8 +193,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed
)
// Unrotate preemptively as this vector gets rotatee later down the line in MC logic.
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians())
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d()
worldVelVec = worldVelVec.add(0.0, extraDownwardForce, 0.0) // Add the stamina depletion force to bring the ride down.
return worldVelVec
@@ -282,19 +304,23 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
state.rideVelocity.set(newVelocity)
}
override fun angRollVel(
private fun rotateOverTime(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
) {
if (vehicle !is OrientationControllable) return
val controller = (vehicle as OrientationControllable).orientationController
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBird.handlingExpr!!)
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble()))
// Turn rate tops out at the turn roll limit. Banking further is for show and doesn't turn any harder
val turnRollLimit = frameTuning(settings, state, vehicle).turnRollLimit.coerceIn(MIN_TURN_ROLL_LIMIT, 90f)
val bankFromLevel = if (abs(controller.roll) > 90f) 180f - abs(controller.roll) else abs(controller.roll)
val turnShare = sin(min(bankFromLevel, turnRollLimit).toRadians()) / sin(turnRollLimit.toRadians()) * controller.roll.sign
val yawDeltaDeg = deltaTime * handling * turnShare
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))).coerceIn(-1.0, 1.0)
// Dampen yaw when upside down
@@ -309,8 +335,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
controller.applyGlobalPitch(pCorr)
controller.rotateRoll(rCorr)
//yaw, pitch, roll
return Vec3(0.0, 0.0, 0.0)
}
private fun correctOrientation(
@@ -320,45 +344,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
controller: OrientationController,
deltaTime: Double
) {
// Calculate correcting roll force.
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!)
// If the ride is moving then have the correction rate be an equal proportion of:
// - how rolled are you
// - how long has it been since last mouse input (higher is stronger correction)
// Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
val maxRollCorrectionRate =
if (state.rideVelocity.get().length() < 0.2) 45.0f
else 45.0f * cos(controller.pitch.toRadians()).pow(2) * (
howRolledAmI*1.0f +
(state.noInputTimeRoll.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
)
val maxRollForce = maxRollCorrectionRate / 40.0f
val rollArrivalDeg = 35.0f
val desiredRoll = 0.0f
// Calculate the signed error (how far and in what direction)
val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
// Use the absolute error to calculate the magnitude of the influence
val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
// Wings only level out by themselves when the rider has let go and they're nearly level already,
// or when the bird's too slow to bank at all. A bank the rider holds stays where they put it
val tuning = frameTuning(settings, state, vehicle)
val idle = state.noInputTimeRoll.get() >= tuning.rollSettleDelay
val settling = state.rideVelocity.get().length() < NO_BANK_SPEED || (idle && abs(controller.roll) < tuning.rollSettleAngle)
val rollError = Mth.wrapDegrees(0.0f.angleDifference(controller.roll))
val desiredRollRate = if (settling) {
MAX_ROLL_SETTLE_RATE * (min(ROLL_SETTLE_ARRIVAL_DEGREES, abs(rollError)) / ROLL_SETTLE_ARRIVAL_DEGREES) * -rollError.sign
} else {
0f
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
val maxRateChange = ROLL_SETTLE_ACCEL * deltaTime
val currentRollRate = state.currRollCorrectionForce.get()
state.currRollCorrectionForce.set(currentRollRate + (desiredRollRate - currentRollRate).coerceIn(-maxRateChange, maxRateChange))
//Calculate correcting pitch force.
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 15.0f * state.currUpsideDownForce.get().toFloat() else 30.0f
val maxPitchCorrectionRate = if (state.staminaDiveForce.get() != 0.0) 15.0f * state.staminaDiveForce.get().toFloat() else 30.0f
val maxPitchForce = maxPitchCorrectionRate / 40.0f
val pitchArrivalDeg = 35.0f
val desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0f else 0.0f
val desiredPitch = if (state.staminaDiveForce.get() != 0.0) 90.0f else 0.0f
if((state.rideVelocity.get().length() < 0.1) || state.currUpsideDownForce.get() != 0.0) {
if((state.rideVelocity.get().length() < 0.1) || state.staminaDiveForce.get() != 0.0) {
val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
@@ -381,7 +388,18 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
}
override fun rotationOnMouseXY(
override fun steer(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
}
private fun mouseRotation(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
@@ -394,15 +412,10 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
// Begin the roll correction time counter if not enough mouse input
// is detected
if (mouseX < 1 && abs(mouseY) < 1) {
// Time since the rider last moved the mouse, wings only settle once it passes the settle delay
if (abs(mouseX) < 1 && abs(mouseY) < 1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime)
} else if (cos(controller.roll.toRadians()) < 0.1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime * 0.5)
}
else {
} else {
state.noInputTimeRoll.set(0.0)
}
@@ -443,46 +456,42 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.mouseInputMult!!)
// Applied to mouseX to prevent turning upside down when trying to roll hard.
val rollDegreeDebuff = if(mouseX.sign.toFloat() == controller.roll.sign) 1 - 0.9f*sqrt(abs(sin(controller.roll.toRadians())))*sqrt(abs(cos(controller.pitch))) else 1.0f
val tuning = frameTuning(settings, state, vehicle)
//Smooth out mouse input.
val smoothingSpeed = 2.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult * rollDegreeDebuff
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
// of being upside down
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
val mouseYc = ((mouseY).coerceIn(-45.0, 45.0) * mouseInputMult) + rolledOverPitchCorrection
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed)
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed)
val currSpeed = state.rideVelocity.get().length()
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
val hoverSpeed = 0.1
val hoverSpeed = HOVER_SPEED
val pitchDamping = abs(cos(controller.pitch.toRadians())).pow(2) // Helper value used when wanting to discard the influence of another value at steep pitches
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
val globalYaw = xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
controller.applyGlobalYaw(globalYaw.toFloat())
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
var localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
localPitch *= if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
// No pitching at hover speed and faster pitching the faster the bird goes
val pitchInput = yInput * if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
else 0.0
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val pitchInfluencedRollCorrection = 0.0
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2) //Coerce into how much roll you want to let in in the deadzone
// Inside a normal bank mouse pitch is world up and down so pulling up climbs instead of tightening the turn.
// Past the turn roll limit, or pointing near straight up or down, it hands over to the bird's own pitch
val pastTurnRoll = smoothstep01((abs(controller.roll) - tuning.turnRollLimit) / PITCH_BLEND_BAND_DEGREES)
val nearVertical = smoothstep01((abs(controller.pitch) - WORLD_PITCH_FADE_START) / (90f - WORLD_PITCH_FADE_START))
val localShare = 1f - (1f - pastTurnRoll) * (1f - nearVertical)
controller.applyGlobalPitch((pitchInput * (1f - localShare)).toFloat())
// Pulling with the bird's own pitch while rolled turns hard so it's held back by handling there
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
val localPitch = pitchInput * localShare * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
// Roll less when slow and not at all when at hoverSpeed or lower
val isInputtingTowardsRoll = if ( xInput.sign == controller.roll.sign.toDouble()) 1.0 else 0.0
val inputInRolledDir = if (isInputtingTowardsRoll == 1.0) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI.coerceIn(0.0f, 0.7f) * pitchDamping * inputInRolledDir)
val rollForce = if (currHorzSpeed > hoverSpeed) xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
else 0.0
val rollInput = xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0))
val rollForce = if (currHorzSpeed > hoverSpeed) capRoll(rollInput, controller.roll, tuning.maxRoll) else 0.0
// When already rolled significantly part of the roll force should turn to local yaw force
val localYawForce = rollForce * abs(sin(controller.roll.toRadians())).pow(2) * pitchDamping * isInputtingTowardsRoll
@@ -546,14 +555,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -607,6 +608,139 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
override fun createDefaultState(settings: BirdSettings) = BirdState()
/**
* Keeps the camera level until the bird rolls past the engage angle, then eases it into the bird's roll.
* It levels out again once the bird comes back under the release angle. Yaw and pitch always follow the bird.
*/
override fun cameraBase(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
context: RideCameraContext
): Quaternionf {
val ride = context.defaultBase()
val tuning = frameTuning(settings, state, vehicle)
val roll = context.data as? BirdCameraRoll ?: BirdCameraRoll().also { context.data = it }
val forward = FORWARDS.rotate(ride, Vector3f())
val up = UP.rotate(ride, Vector3f())
// Up with wings level. Pointing near straight up or down it flips sides so it's only used away from vertical
val levelUp = Vector3f(0f, 1f, 0f).sub(Vector3f(forward).mul(forward.y))
val hasLevel = levelUp.lengthSquared() > MIN_LEVEL_UP_LENGTH_SQ
if (hasLevel) {
levelUp.normalize()
val rideRoll = signedAngle(levelUp, up, forward)
if (roll.engaged && abs(rideRoll) < tuning.rollRelease) {
roll.engaged = false
} else if (!roll.engaged && abs(rideRoll) > tuning.rollEngage) {
roll.engaged = true
}
}
// Camera up lives in world space so the level up flipping over the top of a loop can't drag the camera round
val cameraUp = roll.cameraUp?.let { flatten(it, forward) } ?: Vector3f(up)
val target = when {
roll.engaged -> up
hasLevel -> levelUp
else -> Vector3f(cameraUp)
}
if (roll.cameraUp == null) {
cameraUp.set(target)
} else if (context.frameSeconds > 0f) {
// Spring the leftover angle so it builds up speed and settles instead of lunging at the start
roll.spring.value = signedAngle(target, cameraUp, forward)
simpleSpringDamper(roll.spring, 0f, tuning.rollHalflife, context.frameSeconds)
cameraUp.set(target).rotateAxis(roll.spring.value * Mth.DEG_TO_RAD, forward.x, forward.y, forward.z)
}
roll.cameraUp = cameraUp
return levelFrame(forward, cameraUp)
}
override fun firstPersonHandRoll(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
context: RideCameraContext
): Float {
return context.rideRollAroundView() * frameTuning(settings, state, vehicle).handRollShare
}
// Hovering the rider can look up and down freely and around up to the hover limit. Only pushing past it turns the bird
override fun consumeLook(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
input: RideLookInput
): RideLookTake {
// Only forward speed counts so rising or sinking on the spot is still hovering
if (state.rideVelocity.get().horizontalDistance() > HOVER_SPEED) return RideLookTake.all(input)
val limit = frameTuning(settings, state, vehicle).hoverLookYawLimit
// Never snap a look that's already past the limit, it just can't be pushed further out
val bound = max(limit, abs(input.yaw))
val target = input.yaw + input.yawDelta
val pushed = target - target.coerceIn(-bound, bound)
return RideLookTake(pushed, 0f, recenterYaw = false, recenterPitch = false)
}
/** Knobs the bird reads every frame, resolved at most once a tick so MoLang doesn't run per frame. */
private fun frameTuning(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): BirdTuning {
val cached = state.frameTuning.get()
if (cached != null && cached.tick == vehicle.tickCount) return cached
val runtime = vehicle.runtime
return BirdTuning(
tick = vehicle.tickCount,
rollEngage = runtime.resolveFloat(settings.cameraRollEngage ?: globalBird.cameraRollEngage!!),
rollRelease = runtime.resolveFloat(settings.cameraRollRelease ?: globalBird.cameraRollRelease!!),
rollHalflife = runtime.resolveFloat(settings.cameraRollHalflife ?: globalBird.cameraRollHalflife!!),
hoverLookYawLimit = runtime.resolveFloat(settings.hoverLookYawLimit ?: globalBird.hoverLookYawLimit!!),
turnRollLimit = runtime.resolveFloat(settings.turnRollLimit ?: globalBird.turnRollLimit!!),
maxRoll = runtime.resolveFloat(settings.maxRoll ?: globalBird.maxRoll!!),
rollSettleAngle = runtime.resolveFloat(settings.rollSettleAngle ?: globalBird.rollSettleAngle!!),
rollSettleDelay = runtime.resolveFloat(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!),
handRollShare = runtime.resolveFloat(settings.handRollShare ?: globalBird.handRollShare!!)
).also { state.frameTuning.set(it) }
}
/** Smoothstep of [t] clamped to 0 to 1. */
private fun smoothstep01(t: Float): Float {
val x = t.coerceIn(0f, 1f)
return x * x * (3f - 2f * x)
}
/**
* Eases roll that would take the bird further from level to a stop at [maxRoll] degrees.
* Rolling back toward level is never held back.
*/
private fun capRoll(rollDelta: Double, roll: Float, maxRoll: Float): Double {
if (rollDelta.sign != roll.sign.toDouble()) return rollDelta
val room = (maxRoll - abs(roll)).coerceAtLeast(0f)
val ease = (room / ROLL_CAP_BAND_DEGREES).coerceIn(0f, 1f)
val eased = rollDelta * ease * ease * (3f - 2f * ease)
// Never step past the cap in one frame
return eased.coerceIn(-room.toDouble(), room.toDouble())
}
/** Signed degrees from [from] to [to] turning around [axis]. Both have to be perpendicular to it. */
private fun signedAngle(from: Vector3f, to: Vector3f, axis: Vector3f): Float {
return atan2(Vector3f(from).cross(to).dot(axis), from.dot(to)) * Mth.RAD_TO_DEG
}
/** [vector] with the part along [axis] removed, or null if nothing is left. */
private fun flatten(vector: Vector3f, axis: Vector3f): Vector3f? {
val flat = Vector3f(vector).sub(Vector3f(axis).mul(vector.dot(axis)))
return if (flat.lengthSquared() > 0f) flat.normalize() else null
}
/** Rotation whose forward is [forward] and up is [up]. They have to be unit length and perpendicular. */
private fun levelFrame(forward: Vector3f, up: Vector3f): Quaternionf {
val right = Vector3f(forward).cross(up)
val back = Vector3f(forward).negate()
return Quaternionf().setFromNormalized(Matrix3f(right, up, back))
}
override fun asMoLangValue(
settings: BirdSettings,
state: BirdState,
@@ -659,6 +793,39 @@ class BirdSettings : RidingBehaviourSettings {
// This is coerced between 1 and 0 or things would get weird.
var stamDrainPerRateDive: Expression? = null
private set
// Degrees of roll before the camera starts rolling with the bird
var cameraRollEngage: Expression? = null
private set
// Degrees the bird has to roll back under before the camera levels out. Keep it below engage so it doesn't flicker
var cameraRollRelease: Expression? = null
private set
// Seconds the camera roll spring takes to settle. Raise for a lazier camera
var cameraRollHalflife: Expression? = null
private set
// Degrees either side the rider can look while hovering before pushing further turns the bird
var hoverLookYawLimit: Expression? = null
private set
// Roll in degrees where turning tops out and mouse pitch stops being world up and down
var turnRollLimit: Expression? = null
private set
// Furthest the bird can roll from level in degrees. Keep cameraRollEngage just above it so the camera
// stays level through normal flying and only rolls in loops and flips. 180 lets it roll all the way over
var maxRoll: Expression? = null
private set
// Within this many degrees of level the wings settle back to level once the rider lets go of the mouse
var rollSettleAngle: Expression? = null
private set
// Share of the bird's roll the first person hands lean with, 0 to 1
var handRollShare: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -677,6 +844,14 @@ class BirdSettings : RidingBehaviourSettings {
buffer.writeNullableExpression(stamDrainPercRateGlide)
buffer.writeNullableExpression(stamDrainPerRateDive)
buffer.writeNullableExpression(timeToRollCorrect)
buffer.writeNullableExpression(cameraRollEngage)
buffer.writeNullableExpression(cameraRollRelease)
buffer.writeNullableExpression(cameraRollHalflife)
buffer.writeNullableExpression(hoverLookYawLimit)
buffer.writeNullableExpression(turnRollLimit)
buffer.writeNullableExpression(maxRoll)
buffer.writeNullableExpression(rollSettleAngle)
buffer.writeNullableExpression(handRollShare)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -695,6 +870,14 @@ class BirdSettings : RidingBehaviourSettings {
stamDrainPercRateGlide = buffer.readNullableExpression()
stamDrainPerRateDive = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
cameraRollEngage = buffer.readNullableExpression()
cameraRollRelease = buffer.readNullableExpression()
cameraRollHalflife = buffer.readNullableExpression()
hoverLookYawLimit = buffer.readNullableExpression()
turnRollLimit = buffer.readNullableExpression()
maxRoll = buffer.readNullableExpression()
rollSettleAngle = buffer.readNullableExpression()
handRollShare = buffer.readNullableExpression()
}
}
@@ -705,7 +888,9 @@ class BirdState : RidingBehaviourState() {
var currPitchCorrectionForce = ridingState(0.0, Side.CLIENT)
var noInputTimeRoll = ridingState(0.0, Side.CLIENT)
var noInputTimePitch = ridingState(0.0, Side.CLIENT)
var currUpsideDownForce = ridingState(0.0, Side.CLIENT)
var staminaDiveForce = ridingState(0.0, Side.CLIENT)
// Client only cache of the knobs read every frame, never synced
var frameTuning = ridingState<BirdTuning?>(null, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
@@ -725,6 +910,7 @@ class BirdState : RidingBehaviourState() {
currPitchCorrectionForce.set(0.0, forced = true)
noInputTimeRoll.set(0.0, forced = true)
noInputTimePitch.set(0.0, forced = true)
frameTuning.set(null, forced = true)
}
override fun toString(): String {
@@ -761,3 +947,26 @@ fun Float.angleDifference(other: Float): Float {
while (diff > 180F) diff -= 360F
return diff
}
/** Bird knobs resolved for one tick. */
data class BirdTuning(
val tick: Int,
val rollEngage: Float,
val rollRelease: Float,
val rollHalflife: Float,
val hoverLookYawLimit: Float,
val turnRollLimit: Float,
val maxRoll: Float,
val rollSettleAngle: Float,
val rollSettleDelay: Float,
val handRollShare: Float
)
/** Bird's camera roll smoothing, kept in the camera context between frames. */
class BirdCameraRoll {
var engaged = false
// World space up the camera had last frame, null until the first frame
var cameraUp: Vector3f? = null
// Degrees left between the camera and where it's heading, plus how fast that's closing
val spring = FloatSpring()
}
@@ -0,0 +1,938 @@
/*
* 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.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.DistanceFieldLayer
import com.cobblemon.mod.common.api.riding.util.DistanceFieldTuning
import com.cobblemon.mod.common.api.riding.util.PrimitiveShape
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.api.riding.util.collectPrimitivesFromBlocks
import com.cobblemon.mod.common.api.riding.util.estimateGradient
import com.cobblemon.mod.common.api.riding.util.sampleField
import com.cobblemon.mod.common.api.riding.util.visualizeDistanceField
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.client.Minecraft
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import kotlin.math.sqrt
import net.minecraft.core.Direction
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
class BoardBehaviour : RidingBehaviour<BoardSettings, BoardState> {
companion object {
val KEY = cobblemonResource("air/board")
}
override val key = KEY
val globalBoard: BoardSettings
get() = CobblemonRideSettings.board
override fun getRidingStyle(settings: BoardSettings, state: BoardState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<BoardSettings, BoardState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, vehicle ->
return@PoseOption state.walking.get()
})
override fun isActive(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
// Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
// This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun tick(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (vehicle.level().isClientSide) {
handleSprinting(state, driver)
inAirCheck(state, vehicle)
tickStamina(settings, state, vehicle)
state.walking.set(state.rideVelocity.get().horizontalDistance() > 0.1 || driver.zza != 0.0f)
//surfDistCheck(state, vehicle)
}
//TODO: should be a config somewhere
val debugVisualizeField = true;
if (false && debugVisualizeField && !vehicle.level().isClientSide && vehicle.tickCount % 5 == 0) {
visualizeDistanceField(
referenceEntity = vehicle,
center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0),
layers = listOf(
DistanceFieldLayer(isoValue = 2.5f, color = Vector3f(1f, 0.2f, 0.2f))
),
shape = PrimitiveShape.ELLIPSOID,
horizontalRadius = 4.0,
verticalRange = 4.0,
sampleSpacing = 0.3,
marchStep = 0.15,
tuning = DistanceFieldTuning(smoothingRange = 0.25f)
)
}
}
/**
* Check how far the vehicle is from the "surface"
*/
fun surfDistCheck(
state: BoardState,
vehicle: PokemonEntity,
) {
val center = vehicle.position()
val horizontalRadius = 4.0;
val verticalRange = 4.0;
val level = vehicle.level()
//if (level !is ServerLevel) return
// Take a cubic area around the vehicle and gather SDF primitives from it.
val searchBox = AABB(
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
center.x + horizontalRadius, center.y + verticalRange, center.z + horizontalRadius
)
val primitives = collectPrimitivesFromBlocks(level,
searchBox,
PrimitiveShape.ELLIPSOID,
includeFluids = true
)
val sp = center.toVector3f() // Sample Point
val tuning = DistanceFieldTuning(smoothingRange = 0.25f)
val sd = sampleField(primitives, sp.x, sp.y, sp.z, tuning)
state.surfDist.set(sd)
}
fun inAirCheck(
state: BoardState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(true)
}
fun handleSprinting(
state: BoardState,
driver: Player
) {
val sprintTimerTickLength = 7 // Emulate vanillas tick timer length
var hasDoubleTapped = false
// If you are not sprinting and you just pressed down and theres no
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
state.sprintTickTimer.set(sprintTimerTickLength)
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && state.sprintTickTimer.get() != 0) { // If has double tapped
hasDoubleTapped = true
}else if(!state.sprinting.get() && state.sprintTickTimer.get() > 0) {
state.sprintTickTimer.set(state.sprintTickTimer.get() - 1)
} else {
state.sprintTickTimer.set(0)
}
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
// If stamina runs out or the player is not holding forward then stop sprinting
state.sprinting.set(false)
if (state.stamina.get() <= 0.0f) {
state.sprintToggleable.set(false)
}
} else if (!state.sprinting.get() && !state.sprintToggleable.get() && state.stamina.get() > 0.33f) {
// If you are not sprinting and sprint is not toggleable and you're not trying to sprint and stamina is above 0.3 then enable the toggle
state.sprintToggleable.set(true)
} else if (tryingToSprint && state.sprintToggleable.get()) {
// If you are trying to sprint and the toggle allows it then start sprinting and disable toggle
state.sprinting.set(true)
}
// Take not of whether forward was held last tick
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
}
fun tickStamina(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBoard.infiniteStamina!!)) {
return
}
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBoard.staminaExpr!!)).toFloat() / 20.0f
val newStam = if (state.sprinting.get()) {
max(0.0f,stam - stamDrainRate)
} else {
min(1.0f,stam + stamDrainRate * 4)
}
state.stamina.set(newStam.coerceIn(0F, 1F))
}
override fun updatePassengerRotation(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: LivingEntity
) { return }
override fun clampPassengerRotation(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: LivingEntity
) { }
override fun rotation(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
}
/*
* Calculates the rotation amount given the difference between
* the current player y rot and entity y rot. This gives the
* affect of influencing a rides rotation through the mouse
* without it being instant.
*/
fun calcRotAmount(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoard.speedExpr!!)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBoard.handlingExpr!!)
val walkHandlingBoost = 5
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalBoard.lookYawLimit!!)
// Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
val rotDiffNorm = rotDiff / maxYawDiff//(rotDiff / maxYawDiff).coerceIn(-0.2f, 0.2f)
// Take the square root so that the ride levels out quicker when at lower differences between entity
// y and driver y
// This influences the speed of the turn based on how far in one direction you're looking
val minRotMod = 0.4f // Min speed that the rotation modulation converges towards
val rotDiffMod = ((sqrt(abs(rotDiffNorm)) * (1.0f - minRotMod)) + minRotMod) * rotDiffNorm.sign
// Turn rate should be quick when walking and slower when sprinting.
// Smoothly move between these based on speed
val walkSpeed = getWalkSpeed(vehicle)
val w = max(walkSpeed, vehicle.deltaMovement.horizontalDistance())
val invRelSpeed = (RidingBehaviour.scaleToRange(w, walkSpeed, topSpeed ) - 1.0f) * -1.0f
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * sqrt(abs(invRelSpeed)), 1.0)).toFloat()
// Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
}
private fun spawnGradientDebugParticles(vehicle: PokemonEntity, normal: Vec3) {
val level = vehicle.level()
val headPos = vehicle.position().add(0.0, vehicle.bbHeight.toDouble(), 0.0)
val steps = 20
val length = 2.0
for (i in 0..steps) {
val t = i.toDouble() / steps
val point = headPos.add(normal.scale(length * t))
level.addParticle(ParticleTypes.END_ROD, point.x, point.y, point.z, 0.0, 0.0, 0.0)
}
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalBoard.canSprint!!)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalBoard.canJump!!)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalBoard.jumpExpr!!) * 0.75
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoard.speedExpr!!)
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accelTime = if(state.sprinting.get()) (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBoard.accelerationExpr!!) * 20.0) else 0.5 * 20.0
val accel = topSpeed / accelTime
var activeInput = false
/******************************************************
* Gather the previous velocity and check for horizontal
* collisions
*****************************************************/
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
/******************************************************
* Gather the previous velocity and check for horizontal
* collisions
*****************************************************/
if (vehicle.horizontalCollision) {
var horizontalVec = Vec3(newVelocity.x, 0.0, newVelocity.z)
horizontalVec = horizontalVec.normalize().scale(min(horizontalVec.length(), 0.3))
newVelocity = Vec3(horizontalVec.x, newVelocity.y, horizontalVec.z)
}
/******************************************************
* Speed up and slow down based on input
*****************************************************/
if (driver.zza != 0.0f) {
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
// Also determine how fast to be slowing down based on how far turned you are
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalBoard.lookYawLimit!!)
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
val turnPercThresh = 0.0f
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
val effectiveTopSpeed = if (percOfMaxTurnSpeed > turnPercThresh) topSpeed / max(2.0f*s,1.0f) else topSpeed
val turningSlowDown = s * 0.1
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > effectiveTopSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-effectiveTopSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
// Add extra friction if trying to slow down to turn faster
if (newVelocity.z > effectiveTopSpeed) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(turningSlowDown * newVelocity.z.sign, newVelocity.z))
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
/******************************************************
* Gravity logic
*****************************************************/
// if(state.surfDist.get() > 1.6f) {
// val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
// val terminalVel = 2.0
// newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
// } else if(state.surfDist.get() > 1.4f && state.surfDist.get() <= 1.6f) {
// newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
// } else if(state.surfDist.get() <= 1.4f) {
// val gravity = -(9.8 / ( 20.0)) * 0.2 * 0.6
// val terminalVel = -2.0
// newVelocity = Vec3(newVelocity.x, min(newVelocity.y - gravity, -terminalVel), newVelocity.z)
// }
/******************************************************
* Hover logic - non-predictive: correct against current position
*****************************************************/
val correctionGain = 0.5
val maxCorrectionSpeed = 0.3
val center = vehicle.position()
val horizontalRadius = 4.0;
val verticalRange = 4.0;
val level = vehicle.level()
// Take a cubic area around the vehicle and gather SDF primitives from it.
val searchBox = AABB(
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
center.x + horizontalRadius, center.y + vehicle.bbHeight, center.z + horizontalRadius
)
val primitives = collectPrimitivesFromBlocks(level,
searchBox,
PrimitiveShape.ELLIPSOID,
includeFluids = true
)
val tuning = DistanceFieldTuning(smoothingRange = 0.25f)
val currentPos = vehicle.position()
val currentDist = sampleField(primitives, currentPos.x.toFloat(), currentPos.y.toFloat(), currentPos.z.toFloat(), tuning)
val normal = estimateGradient(primitives, currentPos, tuning)
state.surfaceNormal.set(normal)
//spawnGradientDebugParticles(vehicle, normal) // LETS SEE IT!
// Only correct when under the hover surface.
// Otherwise allow gravity to do its thing
// if (currentDist < targetHoverDist) {
// val error = currentDist - targetHoverDist
// val normalSpeed = newVelocity.dot(normal)
// val tangentialVelocity = newVelocity.subtract(normal.scale(normalSpeed))
//
// val correctionSpeed = (-error * correctionGain).coerceIn(-maxCorrectionSpeed, maxCorrectionSpeed)
// newVelocity = tangentialVelocity.add(normal.scale(correctionSpeed))
// } else {
// val hoverCoolnessLowGravFactor = 0.5
// val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6 * hoverCoolnessLowGravFactor
// val terminalVel = 2.0 * hoverCoolnessLowGravFactor
// newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
// }
/******************************************************
* Hover logic
*****************************************************/
val hoverActivationDist = 3.0f
val targetHoverDist = 1.0f
val hoverCoolnessLowGravFactor = 0.70
val gravity = (9.8 / 20.0) * 0.2 * 0.6 * hoverCoolnessLowGravFactor
val terminalVel = 2.0 * hoverCoolnessLowGravFactor
// val repelStrength = 0.05 // tested and was alright
// val hoverDamping = 0.15
val repelStrength = 0.1
val hoverDamping = 0.2
val surfDist = currentDist
if (surfDist > hoverActivationDist) {
// Free fall - no surface influence yet, same as before.
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
} else {
// Getting close - push back toward targetHoverDist, harder the nearer we are.
val closeness = ((hoverActivationDist - surfDist) / (hoverActivationDist - targetHoverDist)).coerceIn(0f, 1.5f)
val repelForce = closeness * repelStrength
val dampingForce = -newVelocity.y * hoverDamping // resists overshoot/bounce
newVelocity = Vec3(newVelocity.x, newVelocity.y + repelForce + dampingForce - gravity * 0.3, newVelocity.z)
}
/******************************************************
* Ground Friction
// *****************************************************/
// if( (newVelocity.horizontalDistance() > 0 && vehicle.onGround() && !activeInput) || newVelocity.horizontalDistance() > topSpeed) {
// newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
// }
/******************************************************
* Jump Logic
*****************************************************/
if (state.jumpTicks.get() > 0 || (state.jumpTicks.get() >= 0 && driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1)) {
// Spread out jumpforce so that variable height jumps are possible
val jumpInputTicks = 6
if (driver.jumping && (state.jumpTicks.get() >= 0 && state.jumpTicks.get() < jumpInputTicks)) {
val appliedJumpForce = ((jumpForce*1.5) / jumpInputTicks) //* (1 - state.jumpTicks.get() / jumpInputTicks)
newVelocity = Vec3(newVelocity.x, newVelocity.y + appliedJumpForce, newVelocity.z)
state.jumpTicks.set(state.jumpTicks.get() + 1)
} else {
// Set delay before next jump is possible after hitting ground
val tickJumpDelay = 3
state.jumpTicks.set(-tickJumpDelay)
}
} else if (vehicle.onGround() && state.jumpTicks.get() < 0) {
// Tick off the delay once reaching the ground
state.jumpTicks.set(state.jumpTicks.get() + 1)
}
// Zero out lateral velocity possibly picked up from a controller transition
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
return newVelocity
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
val speedModifier = 1.2 * 0.35
return walkspeed * movementSpeed * speedModifier
}
private fun rotateOverTime(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
) {
if (vehicle !is OrientationControllable) return
val controller = (vehicle as OrientationControllable).orientationController
// turning
val turnMaxRate = 5.5
val turnAccel = 1.0
val turnFriction = 0.75
val targetYawRate = -driver.xxa.toDouble() * turnMaxRate
val currentYawRate = state.yawAngularVelocity.get()
val newYawRate = if (abs(targetYawRate) > 0.001) {
moveToward(currentYawRate, targetYawRate, turnAccel * deltaTime * 20.0)
} else {
moveToward(currentYawRate, 0.0, turnFriction * deltaTime * 20.0)
}
state.yawAngularVelocity.set(newYawRate)
controller.applyGlobalYaw((newYawRate * deltaTime * 20.0).toFloat())
// up-vector alignment framerate-independent
val upAlignmentRatePerTick = 0.1 // fraction of the remaining gap closed per tick
val frameBlend = 1.0 - (1.0 - upAlignmentRatePerTick).pow(deltaTime * 20.0)
alignUpToSurfaceNormal(controller, state.surfaceNormal.get(), frameBlend.toFloat())
// lean into turns (cosmetic local roll)
val maxLeanDeg = 35.0
val leanPerYawRate = 4.0 * 3.0
val leanRatePerTick = 0.25 * 1.5
val desiredLean = (newYawRate * leanPerYawRate).coerceIn(-maxLeanDeg, maxLeanDeg)
val leanFrameBlend = 1.0 - (1.0 - leanRatePerTick).pow(deltaTime * 20.0)
val leanError = controller.roll.angleDifference(desiredLean.toFloat())
controller.rotateRoll((leanError * leanFrameBlend).toFloat())
}
private fun moveToward(current: Double, target: Double, maxDelta: Double): Double {
val diff = target - current
return if (abs(diff) <= maxDelta) target else current + maxDelta * diff.sign
}
private fun alignUpToSurfaceNormal(controller: OrientationController, targetUp: Vec3, alignmentBlend: Float) {
if (targetUp.lengthSqr() < 1e-6) return
val targetUpVec = Vector3f(targetUp.x.toFloat(), targetUp.y.toFloat(), targetUp.z.toFloat()).normalize()
controller.updateOrientation { current ->
val currentUp = current.transform(Vector3f(OrientationController.UP), Vector3f())
if (currentUp.lengthSquared() < 1e-6f) return@updateOrientation current
// Full rotation that would exactly align currentUp to targetUp...
val fullCorrection = Quaternionf().rotationTo(currentUp, targetUpVec)
// ...but only take a partial step toward it, so alignment is smooth, not instant.
val steppedCorrection = Quaternionf().slerp(fullCorrection, alignmentBlend)
// Pre-multiply (world-space correction), same pattern applyGlobalYaw uses.
Matrix3f().set(steppedCorrection).mul(current, Matrix3f())
}
}
override fun steer(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
}
private fun mouseRotation(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Float {
return if (state.sprinting.get()) 1.15f else 1.0f
}
override fun inertia(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Double {
return 1.0
}
override fun shouldRoll(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun turnOffOnGround(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun maxUpStep(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Float? {
return if (state.inAir.get()) 0.0f else 1.2f
}
override fun getRideSounds(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BoardSettings) = BoardState()
override fun asMoLangValue(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): ObjectValue<RidingBehaviour<BoardSettings, BoardState>> {
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalBoard.canJump!!)
val value = super.asMoLangValue(settings, state, vehicle)
value.functions.put("sprinting") { DoubleValue(state.sprinting.get()) }
value.functions.put("walking") { DoubleValue(state.walking.get()) }
value.functions.put("in_air") { DoubleValue(state.inAir.get() || (state.rideVelocity.get().y >= 0.0 && (vehicle.controllingPassenger as? Player)?.jumping == true && canJump) || (state.rideVelocity.get().y > 0.0)) }
return value
}
}
class BoardSettings : RidingBehaviourSettings {
override val key = BoardBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
var canJump: Expression? = null
private set
var canSprint: Expression? = null
private set
var lookYawLimit: Expression? = null
private set
var speedExpr: Expression? = null
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression? = null
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression? = null
private set
//Between a one block jump and a six block jump
var jumpExpr: Expression? = null
private set
var handlingExpr: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
buffer.writeNullableExpression(lookYawLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
lookYawLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
}
}
class BoardState : RidingBehaviourState() {
var sprinting = ridingState(false, Side.CLIENT)
var walking = ridingState(false, Side.BOTH)
var sprintToggleable = ridingState(false, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
var jumpTicks = ridingState(0, Side.CLIENT)
var sprintTickTimer = ridingState(0, Side.CLIENT)
var forwardHeldLastTick = ridingState(false, Side.CLIENT)
var surfDist = ridingState(Float.POSITIVE_INFINITY, Side.BOTH)
var surfaceNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
var yawAngularVelocity = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(sprinting.get())
buffer.writeBoolean(walking.get())
buffer.writeBoolean(sprintToggleable.get())
buffer.writeBoolean(inAir.get())
buffer.writeInt(jumpTicks.get())
buffer.writeInt(sprintTickTimer.get())
buffer.writeBoolean(forwardHeldLastTick.get())
buffer.writeFloat(surfDist.get())
buffer.writeVec3(surfaceNormal.get())
buffer.writeDouble(yawAngularVelocity.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
sprinting.set(buffer.readBoolean(), forced = true)
walking.set(buffer.readBoolean(), forced = true)
sprintToggleable.set(buffer.readBoolean(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
jumpTicks.set(buffer.readInt(), forced = true)
sprintTickTimer.set(buffer.readInt(), forced = true)
forwardHeldLastTick.set(buffer.readBoolean(), forced = true)
surfDist.set(buffer.readFloat(), forced = true)
surfaceNormal.set(buffer.readVec3(), forced = true)
yawAngularVelocity.set(buffer.readDouble(), forced = true)
}
override fun reset() {
super.reset()
sprinting.set(false, forced = true)
walking.set(false, forced = true)
sprintToggleable.set(false, forced = true)
inAir.set(false, forced = true)
jumpTicks.set(0, forced = true)
sprintTickTimer.set(0, forced = true)
forwardHeldLastTick.set(false, forced = true)
surfDist.set(Float.POSITIVE_INFINITY, forced = true)
surfaceNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
yawAngularVelocity.set(0.0, forced = true)
}
override fun copy() = BoardState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.sprinting.set(this.sprinting.get(), forced = true)
it.walking.set(this.sprinting.get(), forced = true)
it.sprintToggleable.set(this.sprintToggleable.get(), forced = true)
it.inAir.set(this.inAir.get(), forced = true)
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
it.sprintTickTimer.set(this.sprintTickTimer.get(), forced = true)
it.forwardHeldLastTick.set(this.forwardHeldLastTick.get(), forced = true)
it.surfDist.set(this.surfDist.get(), forced = true)
it.surfaceNormal.set(this.surfaceNormal.get(), forced = true)
it.yawAngularVelocity.set(this.yawAngularVelocity.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BoardState) return false
if (previous.sprinting.get() != sprinting.get()) return true
if (previous.walking.get() != walking.get()) return true
if (previous.sprintToggleable.get() != sprintToggleable.get()) return true
if (previous.inAir.get() != inAir.get()) return true
if (previous.surfDist.get() != surfDist.get()) return true
return super.shouldSync(previous)
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -19,11 +18,11 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -67,7 +66,18 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return Vec2(driver.xRot, driver.yRot)
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: GliderSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: GliderSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
@@ -80,34 +90,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return Vec3(xVector, yVector, zVector)
}
override fun angRollVel(
settings: GliderSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GliderSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
return Vec3(0.0, mouseY, mouseX)
}
override fun canJump(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Boolean {
return false
}
@@ -149,10 +131,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return 1.0f
}
override fun useAngVelSmoothing(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
@@ -20,6 +21,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -91,12 +93,23 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
@@ -120,7 +133,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return Vec3(xVel, yVel, zVel)
}
override fun angRollVel(
private fun rotationOverTime(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
@@ -175,7 +188,18 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return Vec3(0.0, 0.0, 0.0)
}
override fun rotationOnMouseXY(
override fun steer(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
input.rotateSmoothed(rotationOverTime(settings, state, vehicle, driver, input.deltaTime))
}
private fun mouseRotation(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
@@ -241,10 +265,6 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return 1.0f
}
override fun useAngVelSmoothing(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return true
}
override fun inertia(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.1
}
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -26,7 +27,6 @@ import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.ClipContext
@@ -190,12 +190,23 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return rotAmount
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
state.rideVelocity.set(retVel)
@@ -283,31 +294,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return newVelocity
}
override fun angRollVel(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun canJump(
settings: HoverSettings,
state: HoverState,
@@ -355,14 +341,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return if (state.isBoosting.get()) 1.1f else 1.0f
}
override fun useAngVelSmoothing(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(
settings: HoverSettings,
state: HoverState,
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
@@ -28,7 +29,6 @@ import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveDouble
@@ -176,7 +176,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed? orr.. right handed? idk, flip it though
)
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians()) // Unrotate preemptively as this vector gets rotate later down the line in MC logic.
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d()
worldVelVec = Vec3(
worldVelVec.x,
worldVelVec.y * (1 - (state.noStamTickCnt.get() / penaltyDeccel).coerceIn(0.0f, 1.0f)), // Dampen the upwards movement depending on stamina depletion time
@@ -266,7 +266,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun angRollVel(
private fun rotationOverTime(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity,
@@ -292,7 +292,18 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return Vec3(yawForce, 0.0, 0.0)
}
override fun rotationOnMouseXY(
override fun steer(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
input.rotateSmoothed(rotationOverTime(settings, state, vehicle, driver, input.deltaTime))
}
private fun mouseRotation(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity,
@@ -390,10 +401,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
}
override fun useAngVelSmoothing(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
return true
}
override fun inertia(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Double {
return 1.0
}
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -27,7 +28,6 @@ import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -75,15 +75,19 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
vehicle: PokemonEntity,
driver: Player
): Float {
// Use this as a "tick" function and check to see if the driver is "boosting"
if (vehicle.level().isClientSide) {
handleBoosting(settings, state, vehicle, driver)
tickStamina(settings, state, vehicle, driver)
}
return state.rideVelocity.get().length().toFloat()
}
override fun driverTick(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
) {
handleBoosting(settings, state, vehicle, driver)
tickStamina(settings, state, vehicle, driver)
}
fun handleBoosting(
settings: RocketSettings,
state: RocketState,
@@ -208,12 +212,23 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
state.rideVelocity.set(retVel)
@@ -318,37 +333,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
return newVelocity
}
override fun angRollVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: RocketSettings,
state: RocketState,
@@ -403,14 +387,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
}
}
override fun useAngVelSmoothing(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(
settings: RocketSettings,
state: RocketState,
@@ -11,6 +11,10 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.composite
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.CompositeRidingStrategies
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
@@ -22,11 +26,11 @@ import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf
class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
companion object {
@@ -170,16 +174,6 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun useAngVelSmoothing(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.useAngVelSmoothing(behaviourSettings, behaviourState, vehicle)
}
}
override fun rideFovMultiplier(
settings: CompositeSettings,
state: CompositeState,
@@ -236,46 +230,6 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun rotationOnMouseXY(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.rotationOnMouseXY(
behaviourSettings,
behaviourState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
override fun angRollVel(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.angRollVel(behaviourSettings, behaviourState, vehicle, driver, deltaTime)
}
}
override fun velocity(
settings: CompositeSettings,
state: CompositeState,
@@ -288,6 +242,28 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun steer(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.steer(behaviourSettings, behaviourState, vehicle, driver, input)
}
}
override fun velocityRunsTwicePerTick(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.velocityRunsTwicePerTick(behaviourSettings, behaviourState, vehicle)
}
}
override fun rotation(
settings: CompositeSettings,
state: CompositeState,
@@ -310,6 +286,17 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun driverTick(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
) {
chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.driverTick(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun pose(
settings: CompositeSettings,
state: CompositeState,
@@ -361,6 +348,49 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
behaviour.damageOnCollision(behaviourSettings, behaviourState, vehicle, impactVec)
}
}
override fun cameraBase(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
context: RideCameraContext
): Quaternionf {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.cameraBase(behaviourSettings, behaviourState, vehicle, context)
}
}
override fun firstPersonHandRoll(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
context: RideCameraContext
): Float {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.firstPersonHandRoll(behaviourSettings, behaviourState, vehicle, context)
}
}
override fun lookSettings(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): RideLookSettings {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.lookSettings(behaviourSettings, behaviourState, vehicle)
}
}
override fun consumeLook(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
input: RideLookInput
): RideLookTake {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.consumeLook(behaviourSettings, behaviourState, vehicle, input)
}
}
}
open class CompositeSettings : RidingBehaviourSettings {
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.player.Player
@@ -63,7 +64,8 @@ object RunStrategy : CompositeRidingStrategy<CompositeSettings> {
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return driver.isSprinting && input.z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
// Ride velocity is world space so check how much of it is forward
return driver.isSprinting && RideFrame.worldToYaw(input, entity.yRot).z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
}
}
@@ -0,0 +1,797 @@
/*
* 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.behaviour.types.land
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.util.DistanceFieldLayer
import com.cobblemon.mod.common.api.riding.util.FieldPrimitive
import com.cobblemon.mod.common.api.riding.util.PrimitiveShape
import com.cobblemon.mod.common.api.riding.util.collectPrimitivesFromBlocks
import com.cobblemon.mod.common.api.riding.util.estimateGradient
import com.cobblemon.mod.common.api.riding.util.sampleField
import com.cobblemon.mod.common.api.riding.util.visualizeDistanceField
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.DistanceFieldTuning
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.*
class ClimbBehaviour : RidingBehaviour<ClimbSettings, ClimbState> {
companion object {
val KEY = cobblemonResource("land/climb")
}
override val key = KEY
override fun getRidingStyle(settings: ClimbSettings, state: ClimbState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<ClimbSettings, ClimbState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, vehicle ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
override fun isActive(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
// Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
// This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun tick(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
handleSprinting(state, driver)
inAirCheck(state, vehicle)
tickStamina(settings, state, vehicle)
// attachmentNormal: discrete, physics-facing - which face are we touching, used
// for the sticking force (newDownForce) in velocity().
// renderSurfaceNormal: smoothed field-gradient - purely cosmetic, used to slowly
// rotate the model's up vector in rotateOverTime(). The two are intentionally
// separate and should not be conflated.
updateAttachment(state, vehicle)
updateRenderSurfaceNormal(state, vehicle)
//TODO: should be a config somewhere
val debugVisualizeField = true;
if (debugVisualizeField && !vehicle.level().isClientSide && vehicle.tickCount % 5 == 0) {
visualizeDistanceField(
referenceEntity = vehicle,
center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0),
layers = listOf(
DistanceFieldLayer(isoValue = 0.0f, color = Vector3f(1f, 0.2f, 0.2f)),
DistanceFieldLayer(isoValue = 1.0f, color = Vector3f(0.2f, 0.6f, 1f))
),
horizontalRadius = 2.0,
verticalRange = 2.0,
sampleSpacing = 0.35,
marchStep = 0.15,
tuning = DistanceFieldTuning(smoothingRange = 0.5f)
)
}
}
/**
* Computes the smoothed field-gradient normal used for rendering only (up-vector
* alignment in rotateOverTime). Cached here at tick rate since collecting primitives
* and sampling the field is real work - actually applying the rotation happens
* separately at frame rate, same pattern as HorseBehaviour's surfDist/BoardBehaviour's
* surfaceNormal.
*/
fun updateRenderSurfaceNormal(state: ClimbState, vehicle: PokemonEntity) {
val level = vehicle.level()
val center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0)
val horizontalRadius = 1.5
val verticalRange = 1.5
val searchBox = AABB(
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
center.x + horizontalRadius, center.y + verticalRange, center.z + horizontalRadius
)
val primitives: List<FieldPrimitive> = collectPrimitivesFromBlocks(level, searchBox, PrimitiveShape.ELLIPSOID)
if (primitives.isEmpty()) return
val tuning = DistanceFieldTuning(smoothingRange = 0.5f)
val gradient = estimateGradient(primitives, center, tuning)
state.renderSurfaceNormal.set(gradient)
}
/** Debug viz: draws a short line of particles from the vehicle out along the current attachment normal. */
fun spawnAttachmentNormalParticles(vehicle: PokemonEntity, normal: Vec3) {
val level = vehicle.level()
if (!level.isClientSide) return
if (normal.lengthSqr() < 1e-6) return
val origin = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0)
val length = 1.5
val steps = 15
for (i in 0..steps) {
val t = i.toDouble() / steps
val point = origin.add(normal.scale(length * t))
level.addParticle(ParticleTypes.END_ROD, point.x, point.y, point.z, 0.0, 0.0, 0.0)
}
}
fun inAirCheck(
state: ClimbState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
}
fun handleSprinting(
state: ClimbState,
driver: Player
) {
if (state.sprinting.get()) {
state.sprinting.set(driver.isSprinting && state.stamina.get() > 0.0f)
} else {
// Only allow sprinting to start if over x percentage of stamina left
val stamSprintPerc = 25.0f
state.sprinting.set(driver.isSprinting && state.stamina.get() > stamSprintPerc / 100.0f)
}
}
fun tickStamina(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
) {
val stam = state.stamina.get()
var newStam = stam
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
if (state.sprinting.get()) {
newStam = max(0.0f,stam - stamDrainRate)
} else {
newStam = min(1.0f,stam + stamDrainRate * 4)
}
state.stamina.set(newStam)
}
override fun updatePassengerRotation(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: LivingEntity
) { return }
override fun clampPassengerRotation(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: LivingEntity
) { }
override fun rotation(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(vehicle.xRot, vehicle.yRot )
}
/*
* Calculates the rotation amount given the difference between
* the current player y rot and entity y rot. This gives the
* affect of influencing a rides rotation through the mouse
* without it being instant.
*
* THIS IS ADAPTED FOR 3D ORIENTATION
*/
fun calcRotAmount(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val walkHandlingBoost = 5
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit)
// Normalize the current rotation diff
val controller = (vehicle as? OrientationControllable)?.orientationController
if (controller == null || controller.orientation == null) return 0.0f
val rotDiff = Mth.wrapDegrees(Mth.wrapDegrees(driver.yRot) - controller.yaw).coerceIn(-maxYawDiff,maxYawDiff)
val rotDiffNorm = rotDiff / maxYawDiff
// Take the square root so that the ride levels out quicker when at lower differences between entity
// y and driver y
// This influences the speed of the turn based on how far in one direction you're looking
val minRotMod = 0.4f // Min speed that the rotation modulation converges towards
val rotDiffMod = ((sqrt(abs(rotDiffNorm)) * (1.0f - minRotMod)) + minRotMod) * rotDiffNorm.sign
// Turn rate should be quick when walking and slower when sprinting.
// Smoothly move between these based on speed
val walkSpeed = getWalkSpeed(vehicle)
val w = max(walkSpeed, vehicle.deltaMovement.horizontalDistance())
val invRelSpeed = (RidingBehaviour.scaleToRange(w, walkSpeed, topSpeed ) - 1.0f) * -1.0f
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * invRelSpeed, 1.0)).toFloat()
// Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val globalHorse: HorseSettings = CobblemonRideSettings.horse
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalHorse.canSprint!!)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalHorse.canJump!!)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHorse.jumpExpr!!) * 0.75
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accelTime = if(state.sprinting.get()) (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHorse.accelerationExpr!!) * 20.0) else 0.5 * 20.0
val accel = topSpeed / accelTime
var activeInput = false
/******************************************************
* Gather the previous velocity and check for horizontal
* collisions
*****************************************************/
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
/******************************************************
* Gather the previous velocity and check for horizontal
* collisions
*****************************************************/
if (vehicle.horizontalCollision) {
var horizontalVec = Vec3(newVelocity.x, 0.0, newVelocity.z)
horizontalVec = horizontalVec.normalize().scale(min(horizontalVec.length(), 0.3))
newVelocity = Vec3(horizontalVec.x, newVelocity.y, horizontalVec.z)
}
/******************************************************
* Speed up and slow down based on input
*****************************************************/
if (driver.zza != 0.0f) {
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
// Also determine how fast to be slowing down based on how far turned you are
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
val turnPercThresh = 0.0f
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
val effectiveTopSpeed = if (percOfMaxTurnSpeed > turnPercThresh) topSpeed / max(2.0f*s,1.0f) else topSpeed
val turningSlowDown = s * 0.1
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > effectiveTopSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-effectiveTopSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
// Add extra friction if trying to slow down to turn faster
if (newVelocity.z > effectiveTopSpeed) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(turningSlowDown * newVelocity.z.sign, newVelocity.z))
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
/******************************************************
* Gravity logic
*****************************************************/
val isOnTopSurface = state.attachmentNormal.get() == Vec3(0.0, 1.0, 0.0)
if (isOnTopSurface) {
if (!vehicle.onGround() && state.jumpTicks.get() <= 0) {
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
val terminalVel = 2.0
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
} else if(vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
}
} else {
// Wall/ceiling - small constant pull to stay stuck, not real gravity.
val stickForce = 0.08
newVelocity = Vec3(newVelocity.x, newVelocity.y - stickForce, newVelocity.z)
}
/******************************************************
* Ground Friction
*****************************************************/
if( (newVelocity.horizontalDistance() > 0 && vehicle.onGround() && !activeInput) || newVelocity.horizontalDistance() > topSpeed) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
}
/******************************************************
* Jump Logic
*****************************************************/
if (state.jumpTicks.get() > 0 || (state.jumpTicks.get() >= 0 && driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1)) {
// Spread out jumpforce so that variable height jumps are possible
val jumpInputTicks = 6
if (driver.jumping && (state.jumpTicks.get() >= 0 && state.jumpTicks.get() < jumpInputTicks)) {
val appliedJumpForce = ((jumpForce*1.5) / jumpInputTicks) //* (1 - state.jumpTicks.get() / jumpInputTicks)
newVelocity = Vec3(newVelocity.x, newVelocity.y + appliedJumpForce, newVelocity.z)
state.jumpTicks.set(state.jumpTicks.get() + 1)
} else {
// Set delay before next jump is possible after hitting ground
val tickJumpDelay = 3
state.jumpTicks.set(-tickJumpDelay)
}
} else if (vehicle.onGround() && state.jumpTicks.get() < 0) {
// Tick off the delay once reaching the ground
state.jumpTicks.set(state.jumpTicks.get() + 1)
}
// Zero out lateral velocity possibly picked up from a controller transition
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
return newVelocity
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
val speedModifier = 1.2 * 0.35
return walkspeed * movementSpeed * speedModifier
}
private fun rotateOverTime(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
) {
if (vehicle !is OrientationControllable) return
val controller = vehicle.orientationController
// Smoothly rotate the model's up vector toward the (separately computed,
// smoothed) field-gradient normal. Purely cosmetic - does not affect attachment
// or movement, which are driven by state.attachmentNormal instead.
val upAlignmentRatePerTick = 0.2
val frameBlend = 1.0 - (1.0 - upAlignmentRatePerTick).pow(deltaTime * 20.0)
alignUpToSurfaceNormal(controller, state.renderSurfaceNormal.get(), frameBlend.toFloat())
}
/**
* Rotates [controller]'s orientation so its up vector moves a fraction of the way
* toward [targetUp] - partial per call, not instant, so alignment settles smoothly
* rather than snapping every time the target changes.
*/
private fun alignUpToSurfaceNormal(controller: OrientationController, targetUp: Vec3, alignmentBlend: Float) {
if (targetUp.lengthSqr() < 1e-6) return
val targetUpVec = Vector3f(targetUp.x.toFloat(), targetUp.y.toFloat(), targetUp.z.toFloat()).normalize()
controller.updateOrientation { current ->
val currentUp = current.transform(Vector3f(UP), Vector3f())
if (currentUp.lengthSquared() < 1e-6f) return@updateOrientation current
// Full rotation that would exactly align currentUp to targetUp...
val fullCorrection = Quaternionf().rotationTo(currentUp, targetUpVec)
// ...but only take a partial step toward it, so alignment is smooth, not instant.
val steppedCorrection = Quaternionf().slerp(fullCorrection, alignmentBlend)
Matrix3f().set(steppedCorrection).mul(current, Matrix3f())
}
}
override fun steer(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
}
private fun mouseRotation(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val vehicleController = vehicle.orientationController
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed)
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed)
//yaw, pitch, roll
return Vec3(xInput, 0.0, 0.0)
}
override fun canJump(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun inertia(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): Double {
return 1.0
}
override fun shouldRoll(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun turnOffOnGround(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun getRideSounds(
settings: ClimbSettings,
state: ClimbState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: ClimbSettings) = ClimbState()
}
class ClimbSettings : RidingBehaviourSettings {
override val key = ClimbBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
var canSprint = "true".asExpression()
private set
var lookYawLimit = "90.0f".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.4)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', 0.5, 2.0)".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a six block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 1.0, 0.38)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 180.0, 40.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(canSprint)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
canJump = buffer.readExpression()
canSprint = buffer.readExpression()
}
}
class ClimbState : RidingBehaviourState() {
var sprinting = ridingState(false, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
var jumpTicks = ridingState(0, Side.CLIENT)
// Discrete, physics-facing: which of the 6 faces we're attached to right now.
// Drives newDownForce in velocity(). Set by updateAttachment().
var attachmentNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
var attachmentPoint = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
// Smoothed, rendering-facing: the field-gradient normal used only to slowly rotate
// the model's visual up vector in rotateOverTime(). Deliberately separate from
// attachmentNormal - see comments in tick(). Set by updateRenderSurfaceNormal().
var renderSurfaceNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(sprinting.get())
buffer.writeBoolean(inAir.get())
buffer.writeInt(jumpTicks.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
sprinting.set(buffer.readBoolean(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
jumpTicks.set(buffer.readInt(), forced = true)
}
override fun reset() {
super.reset()
sprinting.set(false, forced = true)
inAir.set(false, forced = true)
jumpTicks.set(0, forced = true)
attachmentNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
attachmentPoint.set(Vec3(0.0, 1.0, 0.0), forced = true)
renderSurfaceNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
}
override fun copy() = ClimbState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.sprinting.set(this.sprinting.get(), forced = true)
it.inAir.set(this.inAir.get(), forced = true)
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
it.attachmentNormal.set(this.attachmentNormal.get(), forced = true)
it.attachmentPoint.set(this.attachmentNormal.get(), forced = true)
it.renderSurfaceNormal.set(this.renderSurfaceNormal.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is ClimbState) return false
if (previous.sprinting.get() != sprinting.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
fun updateAttachment(state: ClimbState, vehicle: PokemonEntity) {
val probeMargin = 0.15
val currentFace = AttachmentFace.entries.firstOrNull { it.normal == state.attachmentNormal.get() }
val face = determineAttachmentFace(vehicle, currentFace, probeMargin)
if (face != null) {
state.attachmentNormal.set(face.normal)
}
// face == null means nothing's currently in reach - leave attachmentNormal as
// whatever it last was until the jump/detach state takes over.
}
enum class AttachmentFace(val normal: Vec3) {
UP(Vec3(0.0, 1.0, 0.0)),
DOWN(Vec3(0.0, -1.0, 0.0)),
NORTH(Vec3(0.0, 0.0, -1.0)),
SOUTH(Vec3(0.0, 0.0, 1.0)),
EAST(Vec3(1.0, 0.0, 0.0)),
WEST(Vec3(-1.0, 0.0, 0.0))
}
/** Thin slab test on one face of the vehicle's actual bounding box - true if anything solid is touching that side. */
fun probeFaceContact(vehicle: PokemonEntity, face: AttachmentFace, probeMargin: Double): Boolean {
val box = vehicle.boundingBox
val testBox = when (face) {
AttachmentFace.UP -> AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + probeMargin, box.maxZ)
AttachmentFace.DOWN -> AABB(box.minX, box.minY - probeMargin, box.minZ, box.maxX, box.minY, box.maxZ)
AttachmentFace.NORTH -> AABB(box.minX, box.minY, box.minZ - probeMargin, box.maxX, box.maxY, box.minZ)
AttachmentFace.SOUTH -> AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + probeMargin)
AttachmentFace.EAST -> AABB(box.maxX, box.minY, box.minZ, box.maxX + probeMargin, box.maxY, box.maxZ)
AttachmentFace.WEST -> AABB(box.minX - probeMargin, box.minY, box.minZ, box.minX, box.maxY, box.maxZ)
}
return !vehicle.level().noCollision(vehicle, testBox)
}
/** Stays on the current face if it's still touching, otherwise scans for a new one. */
fun determineAttachmentFace(vehicle: PokemonEntity, currentFace: AttachmentFace?, probeMargin: Double): AttachmentFace? {
if (currentFace != null && probeFaceContact(vehicle, currentFace, probeMargin)) {
return currentFace
}
return AttachmentFace.entries.firstOrNull { probeFaceContact(vehicle, it, probeMargin) }
}
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -44,7 +45,6 @@ import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.player.Player
@@ -267,12 +267,23 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
@@ -398,37 +409,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun angRollVel(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: HorseSettings,
state: HorseState,
@@ -475,14 +455,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return if (state.sprinting.get()) 1.15f else 1.0f
}
override fun useAngVelSmoothing(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(
settings: HorseSettings,
state: HorseState,
@@ -12,7 +12,6 @@ import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -25,6 +24,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -45,7 +45,6 @@ import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -98,7 +97,15 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return driver.deltaMovement.length().toFloat()
}
override fun driverTick(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
) {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit ?: globalMinekart.boostLimit!!)
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
val boostPerSecond = 20.0f
@@ -127,7 +134,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
@@ -197,12 +203,23 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
@@ -308,34 +325,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
return newVelocity
}
override fun angRollVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
return Vec3(0.0, mouseY, mouseX)
}
override fun canJump(
settings: MinekartSettings,
state: MinekartState,
@@ -387,14 +376,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
return fov
}
override fun useAngVelSmoothing(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Double {
return 1.0
}
@@ -12,7 +12,6 @@ import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -25,6 +24,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -44,7 +44,6 @@ import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -98,7 +97,15 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return driver.deltaMovement.length().toFloat()
}
override fun driverTick(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player
) {
if (driver.isShiftKeyDown) {
state.drifting.set(true)
if(driver.zza != 0.0f) {
@@ -112,7 +119,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
@@ -172,12 +178,23 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
@@ -278,34 +295,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return newVelocity
}
override fun angRollVel(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
return Vec3(0.0, mouseY, mouseX)
}
override fun canJump(
settings: VehicleSettings,
state: VehicleState,
@@ -352,14 +341,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return 1.0f
}
override fun useAngVelSmoothing(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Double {
return 1.0
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -23,6 +22,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -40,7 +40,6 @@ import kotlin.math.min
import net.minecraft.core.BlockPos
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -133,12 +132,23 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return Vec2(vehicle.xRot, vehicle.yRot + state.deltaRotation.get().toFloat())
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
applyStrafeRotation(vehicle, driver, settings, state)
@@ -259,34 +269,6 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
vehicle.setYHeadRot(driver.yRot)
}
override fun angRollVel(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
return Vec3(0.0, mouseY, mouseX)
}
override fun canJump(
settings: BoatSettings,
state: BoatState,
@@ -334,10 +316,6 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return if (state.isVehicleSprinting.get()) sprintFov else 1.0f
}
override fun useAngVelSmoothing(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -22,6 +21,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -29,7 +29,6 @@ import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -96,12 +95,23 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val f = driver.xxa * 0.05f
var g = driver.zza * 0.6f
@@ -112,34 +122,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return Vec3(f.toDouble(), 0.0, g.toDouble())
}
override fun angRollVel(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
return Vec3(0.0, mouseX, mouseY)
}
override fun canJump(
settings: BurstSettings,
state: BurstState,
@@ -186,10 +168,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return 1.0f
}
override fun useAngVelSmoothing(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
@@ -54,6 +55,8 @@ import org.joml.Vector3f
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
// The correction forces were tuned as degrees per tenth of a second
private const val CORRECTION_FORCE_TO_DEGREES_PER_SECOND = 10.0
val KEY = cobblemonResource("liquid/dolphin")
}
@@ -201,7 +204,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
state.lastVelocity.set(prevVel)
return state.lastVelocity.get().yRot(vehicle.yRot.toRadians())
return state.lastVelocity.get()
} else {
if(state.hasBreached.get()) {
state.clearMouseSmoothers.set(true)
@@ -238,18 +241,17 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
val worldVelVec = localVelVec.mul(orientation).toVec3d()
state.lastVelocity.set(worldVelVec) // Set last velocity to be used during breaching
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
return worldVelVec.yRot(vehicle.yRot.toRadians())
return worldVelVec
}
override fun angRollVel(
private fun rotateOverTime(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
) {
if (vehicle !is OrientationControllable) return
val controller = (vehicle as OrientationControllable).orientationController
val currSpeed = vehicle.deltaMovement.length()
@@ -267,8 +269,8 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
// Correct orientation when at low speeds
correctOrientation(settings, state, vehicle, controller, driver, deltaTime)
//yaw, pitch, roll
return Vec3(0.0, state.currPitchCorrectionForce.get(), state.currRollCorrectionForce.get())
val correction = deltaTime * CORRECTION_FORCE_TO_DEGREES_PER_SECOND
controller.rotate(0f, (state.currPitchCorrectionForce.get() * correction).toFloat(), (state.currRollCorrectionForce.get() * correction).toFloat())
}
private fun correctOrientation(
@@ -354,7 +356,18 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
}
override fun rotationOnMouseXY(
override fun steer(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
}
private fun mouseRotation(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
@@ -487,10 +500,6 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return if (state.boosting.get()) 1.2f else 1.0f
}
override fun useAngVelSmoothing(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Double {
return if (!vehicle.isInWater && !vehicle.isUnderWater) 1.0 else 0.1
}
@@ -17,14 +17,16 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
@@ -254,29 +256,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
*********************************************/
// If the driver and vehicles eyes are out of the water but the vehicle is still in it then move only horizontally or downwards as you have surfaced
if (state.onSurface.get()) {
val worldVelVec = Vec3(currVel.x, min(0.0, currVel.y), currVel.z)
return worldVelVec
return RideFrame.yawToWorld(Vec3(currVel.x, min(0.0, currVel.y), currVel.z), vehicle.yRot)
} else {
val vertVec = Vec3(0.0, currVel.y, 0.0)
val horzVec = Vec3(currVel.x, 0.0, 0.0)
val horzVec = RideFrame.yawToWorld(Vec3(currVel.x, 0.0, 0.0), vehicle.yRot)
val localZVelVec = Vector3f(0.0f, 0.0f, currVel.z.toFloat() * -1.0f) // Flip the z axis to make this left handed
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
val worldVelVec = localZVelVec.mul(orientation).toVec3d().yRot(vehicle.yRot.toRadians())
val worldVelVec = localZVelVec.mul(orientation).toVec3d()
// Add back the horizontal and vertical components.
return worldVelVec.add(vertVec).add(horzVec)
}
}
override fun angRollVel(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun applyRenderRotation(
settings: SubmarineSettings,
state: SubmarineState,
@@ -323,7 +313,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
}
override fun rotationOnMouseXY(
override fun steer(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
}
private fun mouseRotation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
@@ -432,10 +432,6 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
return 1.0f
}
override fun useAngVelSmoothing(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Double {
return if (!vehicle.isUnderWater) 1.0 else 0.1
}
@@ -452,8 +448,10 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
return false
}
override fun mouseModifiesDriverRotation(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Pair<Boolean, Boolean> {
return Pair(false, state.onSurface.get())
// Surfaced the mouse pitch only moves the look so you can look around without nosing the sub under
override fun consumeLook(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity, input: RideLookInput): RideLookTake {
if (!state.onSurface.get()) return RideLookTake.all(input)
return RideLookTake(input.yawDelta, 0f, recenterYaw = true, recenterPitch = false)
}
override fun shouldRotatePokemonHead(
@@ -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.api.riding.camera
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.FORWARDS
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
import net.minecraft.util.Mth
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.atan2
/**
* What a ride gets to build its camera base from each frame.
* One lives per ride on the client so [data] can keep a ride's own camera state between frames.
*/
class RideCameraContext {
companion object {
// Ride up this close to the view direction has no readable roll
private const val MIN_FLAT_LENGTH_SQ = 1.0E-6f
}
/** Ride orientation exactly as the driver sets it */
val rawOrientation = Quaternionf()
/** Spring smoothed ride orientation the model and seats are drawn with */
val renderOrientation = Quaternionf()
/** True when the local player is the one steering */
var isDriving = false
private set
/** Base the camera used last frame, null on the first frame of a ride */
var previousBase: Quaternionf? = null
private set
/** Seconds since the last frame, 0 while paused */
var frameSeconds = 0f
private set
var partialTicks = 0f
private set
/** Free slot for a ride to keep its own camera state in. Cleared when the ride changes */
var data: Any? = null
/** Final camera view this frame, base with the look on top */
val view = Quaternionf()
/** Drivers hang straight off the ride. Passengers get the smoothed one since raw packets make it jerky */
fun defaultBase(): Quaternionf = Quaternionf(if (isDriving) rawOrientation else renderOrientation)
internal fun update(raw: Quaternionf, render: Quaternionf, isDriving: Boolean, frameSeconds: Float, partialTicks: Float) {
rawOrientation.set(raw)
renderOrientation.set(render)
this.isDriving = isDriving
this.frameSeconds = frameSeconds
this.partialTicks = partialTicks
}
/** Degrees the ride's model is rolled from the camera around the view direction */
fun rideRollAroundView(): Float {
val forward = FORWARDS.rotate(view, Vector3f())
val cameraUp = UP.rotate(view, Vector3f())
val rideUp = UP.rotate(renderOrientation, Vector3f())
rideUp.sub(Vector3f(forward).mul(rideUp.dot(forward)))
if (rideUp.lengthSquared() < MIN_FLAT_LENGTH_SQ) return 0f
return atan2(Vector3f(cameraUp).cross(rideUp).dot(forward), cameraUp.dot(rideUp)) * Mth.RAD_TO_DEG
}
internal fun updateView(view: Quaternionf) {
this.view.set(view)
}
internal fun finishFrame(base: Quaternionf) {
previousBase = (previousBase ?: Quaternionf()).set(base)
}
internal fun reset() {
view.identity()
rawOrientation.identity()
renderOrientation.identity()
isDriving = false
previousBase = null
frameSeconds = 0f
partialTicks = 0f
data = null
}
}
@@ -0,0 +1,24 @@
/*
* 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.camera
/**
* One frame of mouse look in degrees, before the ride takes its share.
* Angles are relative to the camera base.
*/
data class RideLookInput(
/** Degrees the mouse moved the look sideways this frame */
val yawDelta: Float,
/** Degrees the mouse moved the look up or down this frame */
val pitchDelta: Float,
/** Look yaw before this frame's movement */
val yaw: Float,
/** Look pitch before this frame's movement */
val pitch: Float
)
@@ -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.api.riding.camera
/**
* How far a rider can look around the camera base and how much of the base's turning the look follows.
*/
data class RideLookSettings(
/** Degrees the look can pitch either side of the camera base */
val maxPitch: Float = DEFAULT_MAX_PITCH,
/** Degrees the look can yaw either side of the camera base */
val maxYaw: Float = DEFAULT_MAX_YAW,
/** 0 to 1 share of the base's turning the look follows. 1 turns with the base and 0 holds its direction in the world */
val carry: Float = FULL_CARRY
) {
companion object {
// Just short of straight up or down so yaw never goes degenerate
const val DEFAULT_MAX_PITCH = 89f
// A bit past side on so you can look over your shoulder. Raise to let riders look further back
const val DEFAULT_MAX_YAW = 105f
const val FULL_CARRY = 1f
@JvmField
val DEFAULT = RideLookSettings()
}
}
@@ -0,0 +1,31 @@
/*
* 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.camera
/**
* The part of a frame's mouse look a ride takes to steer with, in degrees.
* Whatever isn't taken moves the rider's look instead.
*/
data class RideLookTake(
val yaw: Float,
val pitch: Float,
/** Ease the look back to centre on this axis */
val recenterYaw: Boolean,
val recenterPitch: Boolean
) {
companion object {
/** Ride takes nothing and the look stays where it's put */
@JvmField
val NONE = RideLookTake(0f, 0f, recenterYaw = false, recenterPitch = false)
/** Ride steers with all of it and the look drifts back to centre. How rolling rides have always worked */
@JvmStatic
fun all(input: RideLookInput) = RideLookTake(input.yawDelta, input.pitchDelta, recenterYaw = true, recenterPitch = true)
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.core.BlockPos
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.phys.AABB
import kotlin.math.floor
/**
* Walks every block position inside [searchBox] and turns each solid block into one
* full-cube [FieldPrimitive].
*
* @author Jackson
* @since July 27th, 2026
*/
fun collectPrimitivesFromBlocks(
level: BlockGetter,
searchBox: AABB,
shape: PrimitiveShape,
includeFluids: Boolean = false
): List<FieldPrimitive> {
val primitives = mutableListOf<FieldPrimitive>()
val minX = floor(searchBox.minX).toInt()
val maxX = floor(searchBox.maxX).toInt()
val minY = floor(searchBox.minY).toInt()
val maxY = floor(searchBox.maxY).toInt()
val minZ = floor(searchBox.minZ).toInt()
val maxZ = floor(searchBox.maxZ).toInt()
val pos = BlockPos.MutableBlockPos()
for (x in minX..maxX) {
for (y in minY..maxY) {
for (z in minZ..maxZ) {
pos.set(x, y, z)
val state = level.getBlockState(pos)
val isSolid = !state.getCollisionShape(level, pos).isEmpty
val isFluid = includeFluids && !state.fluidState.isEmpty
if (!isSolid && !isFluid) continue
primitives.add(
FieldPrimitive(
shape = shape,
centerX = x + 0.5f, centerY = y + 0.5f, centerZ = z + 0.5f,
halfWidth = 0.5f, halfHeight = 0.5f, halfDepth = 0.5f
)
)
}
}
}
return primitives
}
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.world.phys.Vec3
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
/**
* Helpful Functions when working with signed distance fields/functions.
* https://iquilezles.org/articles/distfunctions/
*
* @author Jackson
* @since July 27th, 2026
*/
/** Which distance formula a primitive uses. */
enum class PrimitiveShape {
BOX,
ROUNDED_BOX,
ELLIPSOID
}
/** Tunable constants for the field. */
data class DistanceFieldTuning(
val smoothingRange: Float = 0.5f,
val roundingRadius: Float = 0.08f
)
/** A single shape in the field - a center position, half-extents, and which formula to use. */
data class FieldPrimitive(
val shape: PrimitiveShape,
val centerX: Float, val centerY: Float, val centerZ: Float,
val halfWidth: Float, val halfHeight: Float, val halfDepth: Float
)
/** Fast approximate 1/sqrt(x) - the Quake III trick, cheaper than a real sqrt + divide. */
fun fastInverseSqrt(value: Float): Float {
var x = value
val halfX = 0.5f * x
var bits = java.lang.Float.floatToIntBits(x)
bits = 0x5f3759df - (bits shr 1)
x = java.lang.Float.intBitsToFloat(bits)
x *= (1.5f - halfX * x * x)
return x
}
/** Exact distance from a point to an axis-aligned box. Inputs are relative to the box's own center. */
fun boxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
val qx = abs(relX) - halfWidth
val qy = abs(relY) - halfHeight
val qz = abs(relZ) - halfDepth
val ox = max(qx, 0f)
val oy = max(qy, 0f)
val oz = max(qz, 0f)
val outside = sqrt(ox * ox + oy * oy + oz * oz)
val inside = min(max(qx, max(qy, qz)), 0f)
return outside + inside
}
/** Same as boxDistance but with rounded corners/edges https://iquilezles.org/articles/distfunctions/ */
fun roundedBoxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float, roundingRadius: Float): Float =
boxDistance(relX, relY, relZ, halfWidth - roundingRadius, halfHeight - roundingRadius, halfDepth - roundingRadius) - roundingRadius
/** Approximate (lower-bound) distance to an ellipsoid https://iquilezles.org/articles/ellipsoids/. */
fun ellipsoidDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
val px = relX / halfWidth
val py = relY / halfHeight
val pz = relZ / halfDepth
val inverseMagnitude = fastInverseSqrt(px * px + py * py + pz * pz)
val trueDistance = sqrt(relX * relX + relY * relY + relZ * relZ)
return trueDistance * (1.0f - inverseMagnitude)
}
/** Distance from a point to one primitive, dispatched by its shape. */
fun distanceToPrimitive(primitive: FieldPrimitive, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
val relX = sampleX - primitive.centerX
val relY = sampleY - primitive.centerY
val relZ = sampleZ - primitive.centerZ
return when (primitive.shape) {
PrimitiveShape.BOX -> boxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
PrimitiveShape.ROUNDED_BOX -> roundedBoxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth, tuning.roundingRadius)
PrimitiveShape.ELLIPSOID -> ellipsoidDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
}
}
///** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
//fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
// if (smoothingRange <= 0.001f) return min(a, b)
// val k = smoothingRange * 4.0f
// val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
// return b + (a - b) * h - k * h * (1f - h)
//}
//
///** Folds every primitive's distance together into one field value at a point. */
//fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
// var result = Float.POSITIVE_INFINITY
// for (primitive in primitives) {
// val distance = distanceToPrimitive(primitive, sampleX, sampleY, sampleZ, tuning)
// result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
// }
// return result
//}
/** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
if (smoothingRange <= 0.001f) return min(a, b)
val k = smoothingRange * 4.0f
val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
return b + (a - b) * h - k * h * (1f - h)
}
/** Folds nearby primitive distances together into one field value at a point. */
fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
if (primitives.isEmpty()) return Float.POSITIVE_INFINITY
val distances = FloatArray(primitives.size) { distanceToPrimitive(primitives[it], sampleX, sampleY, sampleZ, tuning) }
val minDistance = distances.min()
if (tuning.smoothingRange <= 0.001f) return minDistance
// Anything more than a few blend-widths past the closest primitive is cut - its
// contribution would be negligible in a correct blend anyway, but left in, it still
// drags the chained result down the more of them there are.
val cutoff = tuning.smoothingRange * 3f
var result = Float.POSITIVE_INFINITY
for (distance in distances) {
if (distance - minDistance > cutoff) continue
result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
}
return result
}
/** Finite-difference gradient of the field at a point - points away from the surface, magnitude ~1 for a clean field. */
fun estimateGradient(primitives: List<FieldPrimitive>, point: Vec3, tuning: DistanceFieldTuning, epsilon: Float = 0.05f): Vec3 {
val px = point.x.toFloat()
val py = point.y.toFloat()
val pz = point.z.toFloat()
val dx = sampleField(primitives, px + epsilon, py, pz, tuning) - sampleField(primitives, px - epsilon, py, pz, tuning)
val dy = sampleField(primitives, px, py + epsilon, pz, tuning) - sampleField(primitives, px, py - epsilon, pz, tuning)
val dz = sampleField(primitives, px, py, pz + epsilon, tuning) - sampleField(primitives, px, py, pz - epsilon, tuning)
val gradient = Vec3(dx.toDouble(), dy.toDouble(), dz.toDouble())
return if (gradient.lengthSqr() > 1e-8) gradient.normalize() else Vec3(0.0, 1.0, 0.0)
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
/**
* Turns ride velocities between world space and a ride's yaw local space,
* where x is strafe, y is up and z is forward along the ride's yaw.
*/
object RideFrame {
/** Yaw local to world. Same maths PokemonEntity.travel always used so rides come out identical. */
@JvmStatic
fun yawToWorld(local: Vec3, yawDegrees: Float): Vec3 {
val sin = Mth.sin(yawDegrees.toRadians()).toDouble()
val cos = Mth.cos(yawDegrees.toRadians()).toDouble()
return Vec3(local.x * cos - local.z * sin, local.y, local.z * cos + local.x * sin)
}
/** World to yaw local. Exact inverse of [yawToWorld]. */
@JvmStatic
fun worldToYaw(world: Vec3, yawDegrees: Float): Vec3 {
val sin = Mth.sin(yawDegrees.toRadians()).toDouble()
val cos = Mth.cos(yawDegrees.toRadians()).toDouble()
return Vec3(world.x * cos + world.z * sin, world.y, world.z * cos - world.x * sin)
}
}
@@ -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)
}
@@ -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()
}
}
@@ -42,8 +42,8 @@ abstract class AreaTypeSpawningCondition<T : AreaSpawnablePosition> : SpawningCo
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
super.copyFrom(other, merger)
if (other is AreaTypeSpawningCondition) {
merger.mergeSingle(minHeight, other.minHeight)
merger.mergeSingle(maxHeight, other.maxHeight)
minHeight = merger.mergeSingle(minHeight, other.minHeight)
maxHeight = merger.mergeSingle(maxHeight, other.maxHeight)
neededNearbyBlocks = merger.merge(neededNearbyBlocks, other.neededNearbyBlocks)?.toMutableList()
}
}
@@ -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()}")
}
}
@@ -46,7 +46,7 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawnablePosition> :
super.copyFrom(other, merger)
if (other is SubmergedTypeSpawningCondition) {
minDepth = merger.mergeSingle(minDepth, other.minDepth)
maxDepth = merger.mergeSingle(minDepth, other.minDepth)
maxDepth = merger.mergeSingle(maxDepth, other.maxDepth)
fluidIsSource = merger.mergeSingle(fluidIsSource, other.fluidIsSource)
fluid = merger.mergeSingle(fluid, other.fluid)
}
@@ -39,7 +39,7 @@ abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawnablePosition> : Area
super.copyFrom(other, merger)
if (other is SurfaceTypeSpawningCondition) {
minDepth = merger.mergeSingle(minDepth, other.minDepth)
maxDepth = merger.mergeSingle(minDepth, other.minDepth)
maxDepth = merger.mergeSingle(maxDepth, other.maxDepth)
fluid = merger.mergeSingle(fluid, other.fluid)
}
}
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.AlphaLevelMatchingSensor
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
@@ -76,7 +77,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
?: return super.affectWeight(detail, spawnablePosition, weight)
val detailSpecies = pokemonDetail.pokemon.species
?.let { PokemonSpecies.getByName(it) }
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return super.affectWeight(detail, spawnablePosition, weight)
val formData = detailSpecies.getForm(pokemonDetail.pokemon.aspects)
@@ -27,6 +27,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.isBoxLoaded
import com.cobblemon.mod.common.util.removeIf
import com.cobblemon.mod.common.util.squeezeWithinBounds
import com.cobblemon.mod.common.util.toBlockPos
import com.cobblemon.mod.common.util.toVec3f
import com.cobblemon.mod.common.util.weightedSelection
import kotlin.math.max
@@ -190,74 +191,61 @@ interface Spawner {
}
fun isValidStartPoint(world: Level, chunk: ChunkAccess, startPos: BlockPos.MutableBlockPos): Boolean {
val y = startPos.y
if (!world.isLoaded(startPos) || !world.isLoaded(startPos.setY(y + 1))) {
val abovePos = startPos.above()
if (!world.isLoaded(startPos) || !world.isLoaded(abovePos)) {
return false
}
val mid = chunk.getBlockState(startPos)
val above = chunk.getBlockState(abovePos)
val mid = chunk.getBlockState(startPos.setY(y))
val above = chunk.getBlockState(startPos.setY(y + 1))
// Above must be non-solid
if (!above.isPathfindable(PathComputationType.AIR)) {
return false
}
// Position must be non-air
if (mid.isAir) {
return false
}
return true
return above.isPathfindable(PathComputationType.AIR) && mid.isAir
}
fun constrainArea(area: SpawningZoneInput): SpawningZoneInput? {
val basePos = BlockPos.MutableBlockPos(area.baseX, area.baseY, area.baseZ)
val originalY = area.baseY
val (chunkX, chunkZ) = Pair(SectionPos.blockToSectionCoord(area.baseX), SectionPos.blockToSectionCoord(area.baseZ))
val world = area.world
val centerPos = area.getCenter().toBlockPos()
val (centerChunkX, centerChunkZ) = Pair(SectionPos.blockToSectionCoord(centerPos.x), SectionPos.blockToSectionCoord(centerPos.z))
// if the chunk isn't loaded, we don't want to go further & we don't want the getChunk function below to load/create the chunk.
if (!area.world.areEntitiesLoaded(ChunkPos.asLong(chunkX, chunkZ))) return null
if (!world.areEntitiesLoaded(ChunkPos.asLong(centerChunkX, centerChunkZ))) return null
val chunk = area.world.getChunk(chunkX, chunkZ, ChunkStatus.FULL) ?: return null
val chunk = world.getChunk(centerChunkX, centerChunkZ, ChunkStatus.FULL) ?: return null
var valid = isValidStartPoint(area.world, chunk, basePos)
val probePos = centerPos.mutable()
var validY = if (isValidStartPoint(world, chunk, probePos)) centerPos.y else null
if (!valid) {
var offset = 1
do {
if (isValidStartPoint(area.world, chunk, basePos.setY(originalY + offset))) {
valid = true
basePos.y = originalY + offset
if (validY == null) {
for (offset in 1..Cobblemon.config.maxVerticalCorrectionBlocks) {
if (isValidStartPoint(world, chunk, probePos.set(centerPos.x, centerPos.y + offset, centerPos.z))) {
validY = centerPos.y + offset
break
} else if (isValidStartPoint(area.world, chunk, basePos.setY(originalY - offset))) {
valid = true
basePos.y = originalY - offset
} else if (isValidStartPoint(world, chunk, probePos.set(centerPos.x, centerPos.y - offset, centerPos.z))) {
validY = centerPos.y - offset
break
}
offset++
} while (offset <= Cobblemon.config.maxVerticalCorrectionBlocks)
}
if (valid) {
val min = area.world.squeezeWithinBounds(basePos)
val max = area.world.squeezeWithinBounds(basePos.move(area.length, area.height, area.width))
if (area.world.isLoaded(min) && area.world.isLoaded(max) &&
min.x < max.x && min.y < max.y && min.z < max.z
) {
return SpawningZoneInput(
cause = area.cause,
world = area.world,
baseX = min.x,
baseY = min.y,
baseZ = min.z,
length = max.x - min.x,
height = max.y - min.y,
width = max.z - min.z
)
}
}
val targetY = validY ?: return null
val newBaseY = (targetY - (area.height / 2)).coerceIn(world.minBuildHeight, world.maxBuildHeight)
val minPos = world.squeezeWithinBounds(BlockPos(area.baseX, newBaseY, area.baseZ))
val maxPos = world.squeezeWithinBounds(BlockPos(area.baseX + area.length, newBaseY + area.height, area.baseZ + area.width))
val finalBox = AABB(
minPos.x.toDouble(), minPos.y.toDouble(), minPos.z.toDouble(),
maxPos.x.toDouble(), maxPos.y.toDouble(), maxPos.z.toDouble()
)
if (minPos.x < maxPos.x && minPos.y < maxPos.y && minPos.z < maxPos.z && world.isBoxLoaded(finalBox)) {
return SpawningZoneInput(
cause = area.cause,
world = world,
baseX = minPos.x,
baseY = minPos.y,
baseZ = minPos.z,
length = maxPos.x - minPos.x,
height = maxPos.y - minPos.y,
width = maxPos.z - minPos.z
)
}
return null
}
@@ -120,7 +120,7 @@ object CobblemonItemTags {
@JvmField val DESTINY_KNOT = create("held/destiny_knot")
@JvmField val EVERSTONE = create("held/everstone")
@JvmField val EXPERIENCE_SHARE = create("held/experience_share")
@JvmField val IS_FRIENDSHIP_BOOSTER = create("is_friendship_booster")
@JvmField val IS_FRIENDSHIP_BOOSTER = create("held/is_friendship_booster")
@JvmField val ANY_HELD_ITEM = create("held/is_held_item")
@JvmField val BLACKLISTED_ITEMS_TO_HOLD = create("held/blacklisted_items_to_hold")
@JvmField val WHITELISTED_ITEMS_TO_HOLD = create("held/whitelisted_items_to_hold")
@@ -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)
}
@@ -117,6 +117,9 @@ abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
data class MoveActionResponse(var moveName: String, var targetPnx: String? = null, var gimmickID: String? = null): ShowdownActionResponse(ShowdownActionResponseType.MOVE) {
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
// Ensure that the gimmick ID the player is sending is valid
gimmickID?.let { if (it !in VALID_GIMMICK_IDS) return false }
if (forceSwitch || showdownMoveSet == null) {
return false
}
@@ -168,6 +171,10 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
gimmickID = buffer.readNullable { buffer.readString() }
return this
}
companion object {
private val VALID_GIMMICK_IDS = ShowdownMoveset.Gimmick.entries.map { it.id }.toSet()
}
}
data class HealItemActionResponse(var item: String) : ShowdownActionResponse(ShowdownActionResponseType.FORCE_PASS) {
@@ -183,7 +190,8 @@ data class HealItemActionResponse(var item: String) : ShowdownActionResponse(Sho
}
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
return !forceSwitch
// Previously checked for !forceSwitch, excepts in the case of a forceSwitch, this couldn't legally be sent anyway.
return false
}
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
@@ -37,7 +37,6 @@ import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
import net.minecraft.world.entity.LivingEntity
/**
* Format: |move|POKEMON|MOVE|TARGET
@@ -77,7 +76,7 @@ class MoveInstruction(
val pokemonName = userPokemon.getName()
ShowdownInterpreter.lastCauser[battle.battleId] = message
// For Spread targets the message data only gives the pnx strings. So we can't know what pokemon are actually targeted until the previous messages have been interpreted
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull() { poke -> poke.getPNX() == it }?.battlePokemon }
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull { poke -> poke.getPNX() == it }?.battlePokemon }
val targetPokemonEntity = targetPokemon?.entity
if (targetPokemonEntity != null) {
@@ -137,10 +136,20 @@ class MoveInstruction(
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
val faintedTargets = subsequentInstructions.filterIsInstance<FaintInstruction>().map { it.faintingPokemon }
val failed = subsequentInstructions.any { it is FailInstruction }
val failedTargets = subsequentInstructions.filterIsInstance<FailInstruction>().mapNotNull { it.target }
val hitCountInstruction = subsequentInstructions.filterIsInstance<HitCountInstruction>().firstOrNull()
runtime.environment.query.addFunction("target_fainted") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(faintedTargets.isNotEmpty())
} else {
val entityUUID = params.getString(0)
return@addFunction DoubleValue(faintedTargets.any { it.entity?.stringUUID == entityUUID })
}
}
runtime.environment.query.addFunction("missed") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(missedTargets.isNotEmpty())
@@ -83,7 +83,8 @@ open class BattlePokemon(
"max_health" to Function { DoubleValue(maxHealth.toDouble()) },
"ivs" to Function { effectedPokemon.ivs.struct },
"nature" to Function { StringValue(effectedPokemon.nature.name.toString()) },
"moveset" to Function { effectedPokemon.moveSet.toStruct() }
"moveset" to Function { effectedPokemon.moveSet.toStruct() },
"has_faced_opponents" to Function { DoubleValue(facedOpponents.isNotEmpty()) }
))
val uuid: UUID
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.decor
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class ActivatableDecorationBlock(settings: Properties, val type: ActivatableDecorationBlockType) : HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
enum class ActivatableDecorationBlockType: StringRepresentable {
//For basic use, also ignore camelcase errors, it's just dumb
CENTERED_8x,
//For specific items
METRONOME;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val CODEC: MapCodec<ActivatableDecorationBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
ActivatableDecorationBlockType.CODEC.fieldOf("type").forGetter(ActivatableDecorationBlock::type)
).apply(it, ::ActivatableDecorationBlock) }
val ACTIVE = BooleanProperty.create("active")
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
val METRONOME_HITBOX = Shapes.box(0.25, 0.0, 0.375, 0.75, 0.8125, 0.75)
fun getShape(type: ActivatableDecorationBlockType): VoxelShape {
return when (type) {
ActivatableDecorationBlockType.METRONOME -> METRONOME_HITBOX
else -> CENTERED_8x
}
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(ACTIVE, false)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<ActivatableDecorationBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, ACTIVE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
return rotateShape(Direction.NORTH, direction, getShape(type))
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return getCollisionShape(state, level, pos, context).singleEncompassing()
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if (level.isClientSide) {
level.setBlockAndUpdate(pos, state.cycle(ACTIVE))
return InteractionResult.SUCCESS
} else {
this.push(state, level, pos, null as Player?)
return InteractionResult.CONSUME
}
}
fun push(state: BlockState, level: Level, pos: BlockPos?, player: Player?) {
var state = state
state = state.cycle(ACTIVE)
level.setBlock(pos!!, state, 3)
level.gameEvent(player, if (state.getValue(ACTIVE)) GameEvent.BLOCK_ACTIVATE else GameEvent.BLOCK_DEACTIVATE, pos)
}
override fun hasAnalogOutputSignal(state: BlockState): Boolean {
return true
}
//TODO: Replace this forced 4 with a value that can be set by the block, only relevant when more blocks of this class are added.
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
return if (level.getBlockState(pos).getValue(ACTIVE) as Boolean) 4 else 0
}
}
Loaded 100 of 3306 files, more files were not shown because too many files have changed in this diff. Show more