Compare commits

...
Author SHA1 Message Date
Landon Wills c921496b50 some shader stuff 2025-08-31 11:37:55 -04:00
Landon Wills a9d1000384 testing 2025-08-28 01:11:03 -04:00
Landon Wills 225721aec4 Add tropius 2025-08-26 16:58:05 -04:00
Landon Wills ae9475959d Add sound scraping 2025-08-26 16:57:52 -04:00
Landon Wills 0ecb4d32cf Add new riding mounts 2025-08-20 13:03:49 -04:00
Landon Wills 52a9e30dc1 Merge branch 'implement_pokemon' into riding-scraper 2025-08-20 12:59:43 -04:00
Instrafe 4e417c0b19 Implemented locators for Remoraid & Octillery 2025-08-20 11:40:17 -04:00
Kenji_64 7aba5dc962 Fixed Altaria's cry sound event 2025-08-20 16:52:13 +02:00
Landon Wills 1198bad42e Add readme 2025-08-20 10:50:44 -04:00
Landon Wills aa18d9f90e Scrape remaining files 2025-08-20 10:30:33 -04:00
Landon Wills 3833bcf7c1 Add script dependency file 2025-08-20 09:46:54 -04:00
Landon Wills d85dc372a7 Scrape from riding sheet 2025-08-20 09:44:35 -04:00
Instrafe a5126f2158 Implemented locators for the Elemental Monkeys 2025-08-19 22:44:04 -04:00
Landon ca8bcd966f Merge branch 'playericonfix' into 'main'
don't touch my mixins if you're not going to bother testing your changes

See merge request cable-mc/cobblemon!1789
2025-08-19 19:06:49 +00:00
Segfault Guy 12c7318625 don't touch my mixins if you're not going to bother testing your changes 2025-08-19 09:42:44 -04:00
Hiroku 4f917b94e8 Merge branch 'narrator-dev-fix' into 'main'
Fixed narrator-related GUI crashes on NeoForge

See merge request cable-mc/cobblemon!1788
2025-08-19 12:03:57 +00:00
gruxor 9e82cb2516 Fixed narrator-related GUI crashes on NeoForge 2025-08-19 07:37:27 -04:00
Polymeta bbf85d5804 Merge branch 'riding-fix-failing-asserts' into 'main'
Move and rename the scaleToRange function and place it in the RidingBehaviour...

See merge request cable-mc/cobblemon!1783
2025-08-19 09:20:17 +00:00
Jackson BowesandPolymeta 9daa85750b Move and rename the scaleToRange function and place it in the RidingBehaviour... 2025-08-19 09:20:17 +00:00
Polymeta 6f7c3b0681 Merge branch 'riding-species-sprint-and-jet-crash-fix' into 'main'
Add ability to disable sprinting per species for horse and fix unneeded assert in a Jet function.

See merge request cable-mc/cobblemon!1780
2025-08-19 08:38:00 +00:00
Polymeta d262bb6136 Merge branch 'sound' into 'main'
Tatami, Hearty Grains and cooking sounds

See merge request cable-mc/cobblemon!1784
2025-08-19 08:37:18 +00:00
Polymeta 7509934394 Merge branch 'cooking-pot-ui-tweaks' into 'main'
Cooking Pot UI Tweaks + Bugfix

See merge request cable-mc/cobblemon!1785
2025-08-19 08:35:32 +00:00
GertimoshkaandPolymeta a0ae226506 Cooking Pot UI Tweaks + Bugfix 2025-08-19 08:35:32 +00:00
Sterrezo 0ca9f3a3ad Finalized wing flap audio assets 2025-08-18 15:11:49 +02:00
violet c5c10e0eec i forgot to update the changelog 🫶 2025-08-18 02:48:32 -04:00
violet 274ce14e0f added clouds ☁️🕊️ 2025-08-18 02:38:36 -04:00
Hiroku 0dde8d27e6 Merge branch 'ihatebugtypes' into 'main'
kill all bug type pokemon

Closes #1541

See merge request cable-mc/cobblemon!1786
2025-08-18 05:32:06 +00:00
Segfault Guy 559e728bc3 Deprecate ContextManager. 2025-08-18 00:34:12 -04:00
Segfault Guy dcea1bc67f Merge remote-tracking branch 'origin/main' into ihatebugtypes 2025-08-18 00:29:52 -04:00
Sterrezo 7dc451f344 Ambient sound now stops when broken, volume adjustments 2025-08-17 23:28:53 +02:00
Sterrezo b6ea6a6850 Merge branch 'main' into sound 2025-08-17 22:37:13 +02:00
Kenji_64 9ad9c4643b Some fixes to Porygon's portrait animations acting up. This should hopefully work now 2025-08-17 21:37:33 +02:00
Kenji_64 a7065e5c06 Fixed a tiny oopsie with posers I did 2025-08-17 20:33:15 +02:00
Kenji_64 edf74d9ef4 Implemented attack animations for the Porygon line + made them no longer need air to breathe 2025-08-17 17:02:26 +02:00
Hiroku 768a6df247 Merge branch 'target-selection-fixes' into 'main'
Fix target selection for gimmick moves

See merge request cable-mc/cobblemon!1781
2025-08-17 08:14:39 +00:00
Landon Wills 5c9c8b0b3b Fix target selection for gimmick moves 2025-08-17 03:34:20 -04:00
jackowes 085972f2d3 Add ability to disable sprinting per species for horse and fix unneeded assert in a Jet function. 2025-08-17 01:51:35 -05:00
Landon ddefd1aa24 Merge branch 'booooooo' into 'main'
Fix ghost pokemon that occur due to riding

See merge request cable-mc/cobblemon!1779
2025-08-17 06:04:58 +00:00
JPAKx4 53f441884e Fix ghost pokemon that occur due to riding 2025-08-16 23:47:46 -05:00
Landon 954c5a1027 Merge branch 'riding-transition-changes' into 'main'
Riding transition changes

See merge request cable-mc/cobblemon!1778
2025-08-17 04:35:10 +00:00
Landon Wills c26ab8ff60 Add license 2025-08-17 00:12:39 -04:00
Landon Wills 9ea9f9b307 Fix issue with passenger ejection and survival double jump 2025-08-16 23:29:24 -04:00
Paul fc5ba24e76 removed some debug printlns 2025-08-17 10:07:59 +10:00
Landon Wills 0fe496a290 Fix up outstanding TODOs 2025-08-16 04:43:06 -04:00
Landon Wills df0eb21a9e Change some of the json files to new system 2025-08-16 04:36:52 -04:00
Landon Wills 79ed7da316 Sync behavours between client and server 2025-08-16 04:15:30 -04:00
Landon Wills 908a8b89a6 Add logic for if pokemon is always hovering 2025-08-16 03:34:35 -04:00
Landon Wills 83d4d6f7e9 Change logic for transitions 2025-08-16 03:20:56 -04:00
Hiroku a588906f43 Merge branch 'showdown-datapack' into 'main'
Datapackable abilities/moves; refactoring

See merge request cable-mc/cobblemon!1686
2025-08-15 22:10:03 +00:00
Polymeta e890fd3e9e adjust mochi EV increase per previous discussion
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-15 21:16:46 +01:00
Test e7c95ac72c Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2025-08-15 13:15:08 -04:00
Test 245765c6af fix partial match filtering, +lang support in PC 2025-08-15 13:14:51 -04:00
Paul f8ae4dcecd Removed battler_cooldown stuff since it was not really fully implemented, fixed NPCs getting autoconfigured with pokemon behaviours 2025-08-15 21:12:54 +10:00
Test f694c7c821 Merge branch 'whispering-whismur' into 'main'
Implement a vibration-dampening behaviour for Pokémon living in the Deep Dark

See merge request cable-mc/cobblemon!1745
2025-08-15 03:26:36 +00:00
Test a4824b6766 Merge branch 'riding-fix-reverse-animation-input' into 'main'
amend what has been broken :(

See merge request cable-mc/cobblemon!1775
2025-08-15 03:22:15 +00:00
Test 834533aa6a Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2025-08-14 23:04:05 -04:00
Test 0c9cf2c83d update snack POI key 2025-08-14 23:03:33 -04:00
jackowes fbffeff8dc amend what has been broken :( 2025-08-14 21:39:51 -05:00
Paul 8532ca2d38 Added fluid sleeping, fixed non-water sleepers sleeping in water 2025-08-15 11:38:27 +10:00
Paul 722bf5ba7b Added q.delete_variable(struct, name) and q.delete_variables(struct) 2025-08-15 10:07:21 +10:00
Instrafe eb0365d7ce Implemented Escavalier remodel 2025-08-14 16:52:32 -04:00
gruxor ca1cf72d86 semicolon 2025-08-14 08:24:52 -04:00
gruxor e12936fb66 Refactoring; review resolution 2025-08-14 08:24:52 -04:00
gruxor 9b85742c00 Datapackable abilities/moves; refactoring 2025-08-14 08:24:52 -04:00
Kenji_64 97c2b46192 Implemented Togedemaru! 2025-08-14 13:59:40 +02:00
Kenji_64 847e5d7647 Tiny interpolation fix on Ceruledge's battle_cry 2025-08-14 13:28:38 +02:00
Kenji_64 43e05ef7e7 Urgent Dunsparce line poser fixes that caused a crash (so sorry) 2025-08-14 13:28:19 +02:00
Hiroku aceaef14c5 Merge branch 'tag-im-it' into 'main'
Errors Be Gone

See merge request cable-mc/cobblemon!1774
2025-08-14 11:28:17 +00:00
Kenji_64 b14eed17eb G.Corsola walk speed fixes, sound fixes and tiny adjustments to the icons 2025-08-14 13:27:41 +02:00
dhi_holo c168364ca4 Errors Be Gone
Fixed an error I made in a previous branch
Fixed some tag names erroring on NeoForge
Fixed some items not being properly scrubbed from the mod
Fixed some tags not having translations, causing errors in the log
2025-08-14 20:45:45 +10:00
violet 069d9df3be Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-08-14 05:37:47 -04:00
violet 7c40d4298d added meditite and medicham <3 2025-08-14 05:32:56 -04:00
Kenji_64 1a2c5851f4 Fixed the clipping of the ectoplasm overlay for G. Corsola + Cursola 2025-08-14 11:31:58 +02:00
RedGenesectNinja 5bf962897f Implemented animations for Hoppip, Skiploom and Jumpluff 2025-08-14 00:59:04 -05:00
Polymeta db6533795b Merge branch 'Adjust-Generation-of-New-Flora-1.7' into 'main'
Adjusted generation chances/rates for all new 1.7 flora

See merge request cable-mc/cobblemon!1773
2025-08-13 22:45:52 +00:00
GenotypeXD-Dev 08c49d36db Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-08-13 17:43:35 -05:00
GenotypeXD-Dev 74cd758a0b Implemented Galarian Mr.Mime 2025-08-13 17:43:02 -05:00
Slippy 596c0cd8c3 Adjusted generation chances/rates for all new 1.7 flora 2025-08-13 15:55:40 -05:00
Kenji_64 9af0879359 Added animations and placeholder riding data for the Dunsparce line 2025-08-13 22:16:53 +02:00
Kenji_64 c42e6bb963 Added animations and sounds to G. Corsola + Cursola 2025-08-13 22:13:44 +02:00
Kenji_64 c12e6fd3ed Implemented Klink line retexture + locators 2025-08-13 22:12:42 +02:00
Kenji_64 78f97984c7 Added implemented: true tag and scales to a whole bunch of Mons 2025-08-13 19:23:20 +02:00
Polymeta b65cec2916 fix codec error
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-13 16:39:42 +01:00
Polymeta 63ef406058 Merge branch 'cooking-still-needs-fixes' into 'main'
Even More Cooking Fixes

See merge request cable-mc/cobblemon!1771
2025-08-13 13:42:14 +00:00
GertimoshkaandPolymeta 4856e9feee Even More Cooking Fixes 2025-08-13 13:42:14 +00:00
Polymeta e839813c2a Merge branch 'food-check' into 'main'
Food Balancing Tweaks and Asset Updates

See merge request cable-mc/cobblemon!1772
2025-08-13 11:05:29 +00:00
dhi_holoandPolymeta c70f2a6684 Food Balancing Tweaks and Asset Updates 2025-08-13 11:05:28 +00:00
Paul 860ec50071 Fixed the lerping between poses for pitch tilting 2025-08-13 20:44:54 +10:00
Polymeta 93e26fb31f Merge branch 'snack' into 'main'
🍰

See merge request cable-mc/cobblemon!1768
2025-08-13 06:09:45 +00:00
TestandPolymeta 23e2e18019 🍰 2025-08-13 06:09:45 +00:00
Polymeta a0d5a1fbad Merge branch 'flx-the-big-oops' into 'main'
Fix the oopsie

See merge request cable-mc/cobblemon!1770
2025-08-13 06:08:27 +00:00
JPAKx4 b1b5ef7236 Fix the oopsie 2025-08-12 22:30:00 -05:00
RedGenesectNinja ef3623534c Updated changelog 2025-08-12 20:09:21 -05:00
Instrafe e4554faa4f Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-08-12 21:02:58 -04:00
Instrafe 0e32b20bbd Implemented the remodel for Shelmet 2025-08-12 21:01:02 -04:00
Polymeta 6ab2aa3413 fix game crashes if lure enchantment is not present
Closes #1646

Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-12 21:51:33 +01:00
Polymeta 16819bb0e7 * Updated Ponigiri Textures
* Updated Sinister Tea Textures, as well as added alternate recipes with altered component outputs (does encounter a slight bug with Minecraft's inherent behaviour, will be fixed naturally when we upgrade versions)
* Cleaned up a lot of recipe files
* Tweaked some food colours
* Made berries smeltable into their various dyes
* Updated the Apricorn crop models and hitboxes
* New Campfire Pot advancement
* Hearty Rice now has variable growth from bone meal
2025-08-12 20:17:45 +00:00
dhi_holoandPolymeta 0bf3488a01 Foods 2025-08-12 20:17:44 +00:00
Polymeta 1f3d3e90cf Merge branch 'even-more-cooking-fixes' into 'main'
Additional Fixes to Cooking Pots

See merge request cable-mc/cobblemon!1763
2025-08-12 20:16:09 +00:00
Konoha b1135e334c Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-08-12 11:23:17 -04:00
Konoha 6cdeea39c3 Implement Chewtle and Drednaw 2025-08-12 11:19:15 -04:00
Polymeta 8cf58d22f9 Merge branch 'main' into implement_pokemon 2025-08-11 22:16:32 +01:00
Konoha 6eccc0e3e3 Use those pesky unused Rotom form assets 2025-08-11 10:30:07 -04:00
Gertimoshka 59d3475b31 Recalculate then Seasoning Slots also change 2025-08-11 17:09:58 +03:00
Konoha efa8f6a81e Conform texture references for Alolan Grimer line 2025-08-11 09:09:45 -04:00
Konoha 9081dc3a19 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-08-09 22:49:07 -04:00
Konoha c2b6421ff9 Source data error corrections (G-Weezing is not base stage and should not receive Aromatherapy as an egg move, Silvally is not limited to Grass Pledge outside of a coding error) 2025-08-09 22:48:03 -04:00
Konoha 99af478165 Pumpkaboo Line Portrait/Profile Size Variation 2025-08-09 22:39:10 -04:00
RedGenesectNinja 873028d8e8 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-08-09 21:02:53 -05:00
RedGenesectNinja 2db8728560 Implemented Toedscool and Toedscruel 2025-08-09 21:00:01 -05:00
Tyzillion d0dbbe7850 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-08-09 17:38:25 -04:00
Tyzillion a6280d223a Implemented Alolan Grimer line, Sewaddle line, Tadbulb line, and Capsakid line. 2025-08-09 17:37:52 -04:00
GenotypeXD-Dev 4187b31daf gogoat poser fix+changelog 2025-08-09 14:05:30 -05:00
GenotypeXD-Dev ea33d86dbd Implemented Tynamo line and Skorupi Line 2025-08-09 13:45:05 -05:00
Tyzillion 8dc5c19210 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into implement_pokemon 2025-08-09 14:15:11 -04:00
GenotypeXD-Dev 3499111c92 Changelog 2025-08-09 00:35:41 -05:00
GenotypeXD-Dev 28cd404bef Implemented Espurr line and Morpeko, Poser fixes for hisuian braviary and galarian weezing 2025-08-09 00:34:41 -05:00
GenotypeXD-Dev 407c4baff2 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-08-08 21:20:12 -05:00
GenotypeXD-Dev d0653c5b13 Implemented Weezing-Galar, Braviary-Hisui, Mr. Rime. Added the implemented tag to some of the elemental monkeys, skiddo line, and munna line. Fixed Toxtricity Battle Pose, Fixed Skiddo Line posers 2025-08-08 21:19:29 -05:00
RedGenesectNinja 12212605cd Implemented Hoppip, Skiploom and Jumpluff 2025-08-04 19:01:29 -05:00
Gertimoshka ea49ea7818 Made Cooking pot drop/put crafting remainder items from pots facing direction, as well as unhardcoded the remainder item calculation 2025-08-04 21:18:54 +03:00
Gertimoshka 725b8bc614 Made client not calculate preview item every render, and instead on crafting slot changes 2025-08-01 20:58:12 +03:00
Gertimoshka 875cd2c1b9 Remove Depriciated CookingPotPreviewSlot class 2025-07-31 22:26:18 +03:00
Gertimoshka db972aba4a Fixed Preview Slot being dropped on breaking and showing by removing the fake slot and remaking how fake item render works 2025-07-31 21:36:24 +03:00
Kenji_64 dfc130cf55 Killed the oceans - added G. Corsola and Cursola 2025-07-31 15:13:26 +02:00
Kenji_64 7a7eed9c45 Implemented remodels for Mudkip, Marshtomp and Swampert 2025-07-31 15:12:35 +02:00
Tyzillion 37c4d4c7fe Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into implement_pokemon 2025-07-29 15:07:22 -04:00
Tyzillion 2b5cac4361 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-07-29 15:07:03 -04:00
GenotypeXD-Dev c19a94a2d3 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-07-27 21:17:33 -05:00
GenotypeXD-Dev fbe6328f97 Implemented Skiddo and gogoat 2025-07-27 21:13:43 -05:00
GenotypeXD-Dev d205650373 deleted accidentally duplicated files, added skwovet resolver 2025-07-27 14:36:30 -05:00
GenotypeXD-Dev eb4fa676ca Added Elektrike, Manectric, Yungoos, Gumshoos, Skwovet, Greedent, Toxel, Toxtricity Amped/Lowkey 2025-07-27 14:36:29 -05:00
GenotypeXD-Dev 2ec6ba382c deleted accidentally duplicated files, added skwovet resolver 2025-07-27 14:27:22 -05:00
GenotypeXD-Dev 65203b6d57 Added Elektrike, Manectric, Yungoos, Gumshoo, Skwovet, Greedent, Toxel, Toxtricity Amped/Lowkey 2025-07-27 14:22:39 -05:00
Hiroku 1e573f80bb Merge branch 'cvnty_gyarados' into 'implement_pokemon'
Fixed issue with gyarados_female.geo.json where it was a duplicate of gyarados_male.geo.json, now allowing for correct Implementation of Female Gyarados!

See merge request cable-mc/cobblemon!1747
2025-07-18 06:30:46 +00:00
SamIr0n 819630a8db Gyarados can be female again 👀 2025-07-17 23:19:18 +01:00
Nadja Reitzenstein 714412246b Implement a vibration-dampening behaviour for Pokémon living in the Deep Dark 2025-07-17 20:01:27 +02:00
Konoha 583e4360cf Stub base form aspects for Dudunsparce and Squawkabilly's shiny
Fix texture reference error on Heatmor shiny
2025-07-17 09:18:35 -04:00
Konoha 7972662537 Fix Marill shiny texture path 2025-07-16 20:16:04 -04:00
Konoha a4fd86fc95 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-07-16 18:19:05 -04:00
Kenji_64 47417a309f Fixed Quaxly's animations showing hidden sets of wings 2025-07-16 15:30:24 +02:00
Konoha 4d11bd0ed8 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-07-11 16:47:06 -04:00
Sterrezo db37721e9f Merge branch 'main' into sound 2025-07-08 17:18:03 +02:00
Sterrezo c373d24fe1 Campfire Pot now plays the break sound when the campfire is broken 2025-07-08 17:17:50 +02:00
Sterrezo 2770cbbe35 Added Campfire Pot sounds 2025-07-08 17:09:26 +02:00
Sterrezo 699a47c633 Updated Hearty Grains sounds and added Hearty Grain Bale sounds 2025-07-06 17:51:45 +02:00
Sterrezo 47ebc6c6ca Merge branch 'main' into sound 2025-07-06 17:37:57 +02:00
Tyzillion 5a7da67d67 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into implement_pokemon 2025-07-05 21:26:47 -04:00
Tyzillion 60feb21e49 Removed Drowzee line hard coded posers. 2025-07-05 19:03:10 -04:00
Tyzillion 00d7db6f48 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-07-05 19:02:12 -04:00
Carmendar 5f7990a899 Implemented Azurizz line and fixed Tropius, Shellder line and Shroomish animations. 2025-07-05 16:23:06 -05:00
Sterrezo bdb47532b9 let's not import everything there is 2025-07-05 18:50:31 +02:00
Sterrezo b5a40adcdf Merge branch 'main' into sound 2025-07-05 18:12:18 +02:00
Sterrezo 955422aab7 Updated Hearty Grains sounds and added water variation 2025-07-05 17:55:30 +02:00
Tyzillion d112475c97 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into implement_pokemon 2025-07-02 15:54:57 -04:00
Tyzillion b652235791 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-07-02 15:54:16 -04:00
Sterrezo cfa55095aa Added sounds for Hearty Grains, updated Tatami sounds 2025-06-30 19:41:26 +02:00
Polymeta 00e695a0e7 Merge branch 'legacy_and_event_moves' into 'implement_pokemon'
Legacy and event move support

See merge request cable-mc/cobblemon!1512
2025-06-29 23:13:15 +00:00
Konoha d933adfaf1 Merge remote-tracking branch 'origin/legacy_and_event_moves' into implement_pokemon 2025-06-25 21:21:12 -04:00
Tyzillion cb86c8d4dd Implemented Drowzee Line Remodels. 2025-06-23 17:55:46 -04:00
Sterrezo bcac005fb1 Merge branch 'main' into sound 2025-06-23 14:03:31 +02:00
Sterrezo aa6536b57c Added sounds for Tatami blocks and mats 2025-06-23 12:22:06 +02:00
Segfault Guy 58a5412602 Shift arbitrary battle lead to front of party while preserving order. 2025-05-02 00:48:05 -04:00
1543 changed files with 125445 additions and 22117 deletions
+80 -12
View File
@@ -42,16 +42,31 @@
- Added new optional property `attachment_options` for most EmitterShapes to be attached to the locator/entities scale, rotation, and/or position. Position is true by default.
- Galarica Nut bushes now generate on beaches.
- Some Pokémon now pitch their bodies in the direction they're moving.
- Added new advancements Didn't Stop To Think, We Need To Cook, and Pokémon Jockey!.
- Berries can now be smelted into their respective dyes.
- Added Syrupy Apples.
- Added `/runmolang <molang> [<npc>|<player>|<pokemon>]` command that executes a MoLang expression with the provided options as environment variables, as well as the entity (as `q.entity`) that executed the command.
### Pokémon Added
#### Gen 2
- Marill
- Azumarill
- Hoppip
- Skiploom
- Jumpluff
- Dunsparce
#### Gen 3
- Azurill
- Meditite
- Medicham
- Electrike
- Manectric
- Spoink
- Grumpig
- Swablu
- Altaria
- Snorunt
- Glalie
- Latias
@@ -65,6 +80,8 @@
- Froslass
- Glameow
- Purugly
- Skorupi
- Drapion
#### Gen 5
- Pansage
@@ -87,10 +104,20 @@
- Solosis
- Duosion
- Reuniclus
- Tynamo
- Eelektrik
- Eelektross
- Mienfoo
- Mienshao
- Sewaddle
- Swadloon
- Leavanny
#### Gen 6
- Skiddo
- Gogoat
- Espurr
- Meowstic
- Inkay
- Malamar
- Hawlucha
@@ -99,13 +126,25 @@
- Noivern
#### Gen 7
- Yungoos
- Gumshoos
- Drampa
- Alolan Grimer
- Alolan Muk
- Togedemaru
#### Gen 8
- Skwovet
- Greedent
- Toxel
- Toxtricity
- Morpeko
- Silicobra
- Sandaconda
- Sinistea
- Polteageist
- Galarian Corsola
- Cursola
#### Gen 9
- Smoliv
@@ -118,6 +157,12 @@
- Cyclizar
- Poltchageist
- Sinistcha
- Capsakid
- Scovillain
- Tadbulb
- Bellibolt
- Toedscool
- Toedscruel
### Animation updates for the following Pokémon
- Garchomp
@@ -139,6 +184,13 @@
- Dratini
- Dragonair
- Dragonite
- G. Corsola
- Cursola
- Dunsparce
- Dudunsparce (both forms)
- Porygon
- Porygon2
- Porygon-Z
### Model updates for the following Pokémon
- Gyarados
@@ -186,12 +238,21 @@
- Cacturne
- Taillow
- Swellow
- Added Syrupy Apples.
- Seel
- Dewgong
- Honedge
- Doublade
- Aegislash
- Drowzee
- Hypno
- Mudkip
- Marshtomp
- Swampert
- Shelmet
- Escavalier
- Klink
- Klank
- Klinklang
### Changes
- Changed pokemon caught and seen count to update based on the current pokedex being looked
@@ -245,6 +306,7 @@
- Updated Pokémon state icons shown in party interfaces
- MoLang `face` function can now be run on any `PosableEntity` (Like Pokémon!) instead of just NPCs.
- MoLang `run_action_effect` now works on Pokémon.
- Changed MoLang entity function `is_standing_on` to allow for block tags in the list.
- Changed MoLang entity function `is_standing_on` to allow for block tags in the list.
- Added entity (as `q.entity`) that executed the command to the `executemolangscript` command.
@@ -290,8 +352,8 @@
- Fix issue where locator X axis was not aligned with blockbench.
- Fix issue where particle effects that play on frame 1 on sendout would never play (Gastly)
- The Wiglett line will now sink in water again.
- Fixed the Sprigatito line's portraits being zoomed in too far.
- Fixed Floragato's battle cry animation from breaking.
- Fixed the Sprigatito line's portraits being zoomed in too far.
- Fixed Floragato's battle cry animation from breaking.
- Fixed the block texture name for the Pep-Up Flower being inaccurate
- Fixed issue where the restoration tank would not accept valid items from a hopper.
- Fixed energy root not being shearable
@@ -309,8 +371,12 @@
- Improved flyers avoiding getting stuck on fence posts.
- Fixed air balloon battle text not correctly displaying the Pokémon or item name
- Fixed an issue where items retrieved from a Display case would disappear if a player's inventory is full
- Fixed Pokédex Scanner not respecting the "Invert Mouse" option
- Fixed recoil eyes on Shellder & Cloyster.
- Fixed Shroomish spinning at lightning speed when fainting (no longer allowed to let it rip.)
- Fixed Pokédex Scanner not respecting the "Invert Mouse" option.
- Fixed a crash due to a ConcurrentModificationException that could occur during world generation.
- Fixed Quaxly's animations showing hidden wing sets
- Fixed Moon Ball moon phase logic to actually work correctly
- Fixed `/pokedex printcalculations` to now show the correct percentage completed of the Pokedex
- Fixed mod incompatibility with the `Raised` mod
@@ -327,7 +393,7 @@
- Renamed Cobblemon's creative tabs to start with "Cobblemon: " to distinguish Cobblemon's tabs from tabs for other mods.
- Various items now have a rarity value.
- Reworked observable handling in `Pokemon.kt` to cut down on RAM usage and clarify the file.
- Note: This will break mods that used our observable functionality there or in MoveSet, IVs, EVs, or BenchedMoves.
- Note: This will break mods that used our observable functionality there or in MoveSet, IVs, EVs, or BenchedMoves.
- Using `Pokemon#onChange()` is now the way to mark a Pokémon as needing a save.
- Using `[Pokemon].changeObservable` is now the way to get an `Observable` for any save-worthy changes.
- Updated NPCEntity beam positioning to properly account for the baseScale property.
@@ -358,7 +424,7 @@
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
- `ElementalType` now implements `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
- Pokemon no longer have a change observable
### MoLang & Datapacks
- The following usages for item predicates can now use item conditions like advancements do, you can learn about them in the [Minecraft wiki](https://minecraft.wiki/w/Advancement_definition#minecraft:filled_bucket)
- The `requiredContext` for an item interaction evolution
@@ -392,6 +458,8 @@
- Added Molang functions for Pokémon: `pokeball`, `held_item`, `remove_held_item`, `hyper_train_iv`, `validate_moveset`, `initialize_moveset`, and `add_exp`.
- Added Molang functions for Pokémon: `aspects`, `form_aspects`, `unlearn_move`, `teach_learnable_moves`, `cosmetic_item`, and `remove_cosmetic_item`.
- Added Molang functions for Pokémon: `ability`, `set_iv`, `set_ev`, `teach_move`, and `can_learn_move`.
- Added Molang function `q.delete_variable(<struct>, <variable_name>)` to delete a value from a variable structure in MoLang data.
- Added Molang function `q.delete_variables(<struct>)` to delete all values from a variable structure in MoLang data.
- Adds Flows for `STARTER_CHOSEN`, `SHOULDER_MOUNTED`, `EV_GAINED`, `POKEMON_RELEASED`, `POKEMON_NICKNAMED`, `HELD_ITEM`, and `TRADE_COMPLETED` events
- Adds Flows for `POKEMON_HEALED`, `POKEMON_SCANNED`, `BERRY_HARVEST`, `LOOT_DROPPED`, `POKEMON_SEEN`, `COLLECT_EGG`, `HATCH_EGG`, and `EXPERIENCE_GAINED`.
- Adds Flows for `POKEMON_CATCH_RATE`, `BAIT_SET`, `BAIT_SET_PRE`, `BAIT_CONSUMED`, `POKEROD_CAST_PRE`, `POKEROD_CAST_POST`, `POKEROD_REEL`, and `BOBBER_SPAWN_POKEMON_PRE`.
@@ -418,7 +486,7 @@
- Added optional box argument to the /pc command.
- Pokédex and Dialogue screens now close when the inventory keybind is pressed.
- Added config setting `maxPokedexScanningDetectionRange` to control from what distance the player can scan Pokémon using the Pokédex.
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added debug renderer for posable entity locators.
- Added crossover paintings from Close Combat, Premonition, Altar, Slumber, and Nomad.
- Added optional box argument to the /pc command
@@ -572,7 +640,7 @@
- Fixed Vivichoke Dip and Leek & Potato Stew not returning a bowl upon consumption.
- Fixed Fossil Restoration Tank not accepting Hay Bales as organic material.
- Fixed Potion items applying double their intended healing value.
- Fixed Fast Ball capture bonuses applying to all Pokémon, making it the Best Ball instead of the Mediocre Edge-Case Ball.
- Fixed Fast Ball capture bonuses applying to all Pokémon, making it the Best Ball instead of the Mediocre Edge-Case Ball.
- Fixed "learned new move" messages appearing for already-known moves on Pokémon evolutions.
- Fixed Pokémon Item Models breaking shadows nearby when being placed in Display Cases or Item Frames.
- Fixed berries not giving bonus yields when planted in their preferred biomes. I'm sure we've fixed that 5 times now.
@@ -581,7 +649,7 @@
- Fixed players with shouldered Pokémon not being able to rejoin their 1.5.2 worlds using 1.6.
- Fixed `PokemonProperties` utilizing `ability=<some ability>` being treated as a forced ability even when it is a legal ability for the Pokémon.
- Fixed type formatting in Pokédex scanner mode when dual types require two lines.
- Fixed trading sometimes crashing the game or server.
- Fixed trading sometimes crashing the game or server.
- Fixed Wild shiny sounds not respecting the `shinyNoticeParticlesDistance` config setting.
- Fixed Pokémon being able to evolve mid-battle.
- Fixed NPC held items being able to be stolen by players. Don't be a thief!
@@ -675,7 +743,7 @@
- Stonjourner Henge Ruins, Luna Henge Ruins, and Sol Henge Ruins.
- Submerged Shipwreck Cove and Lush Shipwreck Cove.
- Pokémon Centers to all 5 village types.
### Pokémon Added
#### Gen 1
- Alola Bias Cubone (built-in resource pack)
@@ -998,7 +1066,7 @@
- Big Roots and Energy Roots now share the same sounds as intended.
- Fixed sounds made by blocks playing at a lower pitch than intended.
- Fixed Chimchar and Monferno comically T posing whenever they sleep.
- Fixed Cetitan's cry breaking its walk and sleep animations.
- Fixed Cetitan's cry breaking its walk and sleep animations.
- Fixed the Magby line not having any placeholder walk animations.
- Fixed Duskull and Dusclops using skylight levels for their nether spawn data. There is no sun in the Nether!
- Fixed Hisuian Zoroark using base Zoroark stats.
@@ -1014,7 +1082,7 @@
- Fixed Trevenant T-posing after 8 seconds of sleeping. I also do that.
- Fixed Bewear T-posing for a bit if a wild one faints where you can see.
- Fixed Noctowl's placeholder fly animation.
- Corrected Lotad blinking animation.
- Corrected Lotad blinking animation.
- Fixed reviving items causing errors when used in battle.
- Fixed messages for Focus Sash, Confusion, Mummy, Ice Face, Own Tempo, and Revive.
- Improve error handling when loading spawn-sets to ensure invalid configurations don't crash the server.
@@ -1104,7 +1172,7 @@
- Added a new universal locator called "top".
- Added `eggs_collected` and `eggs_hatched` Advancement triggers.
- Added missing `minYaw` configuration to the `q.look()` function for JSON posers.
- Some pose condition names have been changed:
- Some pose condition names have been changed:
- `isTouchingWaterOrRain` has been changed to `isInWaterOrRain`
- `isSubermegedInWater` has been changed to `isUnderWater`
@@ -0,0 +1,13 @@
/*
* 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;
public interface DoubleJump {
boolean isDoubleJumping();
}
@@ -88,8 +88,8 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Shadow @Final private static Map<Pose, EntityDimensions> POSES;
@Shadow @Final public static EntityDimensions STANDING_DIMENSIONS;
@Unique
private final OrientationController cobblemon$orientationController = new OrientationController(this);
@Shadow protected int jumpTriggerTime;
@Unique private final OrientationController cobblemon$orientationController = new OrientationController(this);
protected PlayerMixin(EntityType<? extends LivingEntity> p_20966_, Level p_20967_) {
super(p_20966_, p_20967_);
@@ -63,13 +63,15 @@ public abstract class ServerEntityMixin {
private void cobblemon$sendRidingStateChanges() {
if (!(this.entity instanceof PokemonEntity pokemonEntity)) return;
if (pokemonEntity.getRidingState() == null) return;
if (pokemonEntity.getRiding() == null) return;
var ridingBehaviour = pokemonEntity.getRiding();
var ridingState = pokemonEntity.getRidingState();
if (pokemonEntity.getRidingController() == null) return;
var ridingController = pokemonEntity.getRidingController();
if (ridingController.getContext() == null) return;
var context = ridingController.getContext();
var ridingBehaviour = context.getBehaviour();
var ridingState = context.getState();
var previousRidingState = pokemonEntity.getPreviousRidingState();
if (previousRidingState != null && !ridingState.shouldSync(previousRidingState)) return;
cobblemon$broadcast(new ClientboundUpdateRidingStatePacket(pokemonEntity.getId(), ridingBehaviour.getKey(), pokemonEntity.getRidingState(), null));
cobblemon$broadcast(new ClientboundUpdateRidingStatePacket(pokemonEntity.getId(), ridingBehaviour, ridingState, null));
}
private void cobblemon$sendOrientationChanges() {
@@ -10,7 +10,10 @@ package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -43,4 +46,10 @@ public abstract class ServerPlayerMixin {
controllable.getOrientationController().setActive(false);
}
//TODO: Switch to sending out on load instead of preventing saving
@ModifyExpressionValue(method = "addAdditionalSaveData", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hasExactlyOnePlayerPassenger()Z"))
private boolean cobblemon$cancelSavingPokemonMounts(boolean original, @Local(ordinal=0) Entity entity) {
return original && !(entity instanceof PokemonEntity);
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.DoubleJump;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Seat;
@@ -26,6 +27,7 @@ import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
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.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
@@ -49,13 +51,23 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable{
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable, DoubleJump {
@Unique Matrix3f cobblemon$lastOrientation;
@Unique boolean cobblemon$isDoubleJumping = false;
@Shadow
private float jumpRidingScale;
@Shadow public Input input;
@Shadow private int autoJumpTime;
@Unique private int cobblemon$survivalJumpTriggerTime;
@Unique private boolean cobblemon$isJumping;
public LocalPlayerMixin(ClientLevel clientLevel, GameProfile gameProfile) {
super(clientLevel, gameProfile);
}
@@ -135,6 +147,33 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
}
}
@Inject(method = "aiStep", at = @At("HEAD"))
private void cobblemon$aiStep(CallbackInfo ci) {
this.cobblemon$isDoubleJumping = false;
this.cobblemon$isJumping = this.input.jumping;
}
@Inject(method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isSprinting()Z", shift = At.Shift.BEFORE))
private void cobblemon$updateDoubleJumping(CallbackInfo ci) {
if (!this.cobblemon$isJumping && this.input.jumping && this.autoJumpTime <= 0 && !this.isSwimming()) {
if (this.cobblemon$survivalJumpTriggerTime != 0) {
this.cobblemon$isDoubleJumping = true;
this.cobblemon$survivalJumpTriggerTime = 0;
}
else {
this.cobblemon$survivalJumpTriggerTime = 7;
}
}
else if (this.cobblemon$survivalJumpTriggerTime > 0) {
this.cobblemon$survivalJumpTriggerTime--;
}
}
@Override
public boolean isDoubleJumping() {
return this.cobblemon$isDoubleJumping;
}
@Override
public HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
@@ -19,8 +19,6 @@ import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -33,9 +31,9 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
super(context, model, shadowRadius);
}
@Inject(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "TAIL"))
public void cobblemon$clientPlayerIcon(LivingEntity entity, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, CallbackInfo ci) {
ClientPlayerIcon.Companion.onRenderPlayer((Player) entity);
@Inject(method = "render", at = @At(value = "TAIL"))
public void renderPlayer(AbstractClientPlayer player, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci) {
ClientPlayerIcon.Companion.onRenderPlayer(player);
}
@WrapOperation(method = "getRenderOffset(Lnet/minecraft/client/player/AbstractClientPlayer;F)Lnet/minecraft/world/phys/Vec3;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isCrouching()Z"))
@@ -89,6 +89,7 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
override fun reload(manager: ResourceManager) {
autoPokemonBehaviours.clear()
autoNPCBehaviours.clear()
val data = mutableMapOf<ResourceLocation, CobblemonBehaviour>()
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
resource.open().use { stream ->
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.entity.PokeCakeBlockEntity
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
import com.cobblemon.mod.common.block.entity.PCBlockEntity
@@ -88,35 +87,23 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
)
@JvmField
val INCENSE_SWEET: BlockEntityType<SweetIncenseBlockEntity> = this.create("incense_sweet",
BlockEntityType.Builder.of(::SweetIncenseBlockEntity, CobblemonBlocks.INCENSE_SWEET).build(null)
)
// TODO after 1.7
// @JvmField
// val INCENSE_SWEET: BlockEntityType<SweetIncenseBlockEntity> = this.create("incense_sweet",
// BlockEntityType.Builder.of(::SweetIncenseBlockEntity, CobblemonBlocks.INCENSE_SWEET).build(null)
// )
@JvmField
val CAMPFIRE: BlockEntityType<CampfireBlockEntity> = this.create("campfire_pot",
BlockEntityType.Builder.of(::CampfireBlockEntity, CobblemonBlocks.CAMPFIRE).build(null)
BlockEntityType.Builder.of(::CampfireBlockEntity,
CobblemonBlocks.CAMPFIRE,
CobblemonBlocks.SOUL_CAMPFIRE
).build(null)
)
/*@JvmField
val LURE_CAKE: BlockEntityType<LureCakeBlockEntity> =
BlockEntityType.Builder.of({ pos, state -> LureCakeBlockEntity(pos, state) },
CobblemonBlocks.LURE_CAKE
).build(null)*/
@JvmField
val LURE_CAKE: BlockEntityType<LureCakeBlockEntity> = this.create(
"lure_cake",
BlockEntityType.Builder.of(::LureCakeBlockEntity, CobblemonBlocks.LURE_CAKE).build(null)
)
@JvmField
val POKE_CAKE: BlockEntityType<PokeCakeBlockEntity> = this.create(
"poke_cake",
BlockEntityType.Builder.of(::PokeCakeBlockEntity, CobblemonBlocks.POKE_CAKE).build(null)
)
@JvmField
val CANDLE_POKE_CAKE: BlockEntityType<CandlePokeCakeBlockEntity> = this.create(
"candle_poke_cake",
BlockEntityType.Builder.of(::CandlePokeCakeBlockEntity, CobblemonBlocks.CANDLE_POKE_CAKE).build(null)
val POKE_SNACK: BlockEntityType<PokeSnackBlockEntity> = this.create(
"poke_snack",
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
)
}
@@ -40,6 +40,7 @@ import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockSetType
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BlockStateProperties.LIT
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.block.state.properties.WoodType
@@ -230,8 +231,10 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val MEDICINAL_LEEK = this.create("medicinal_leek", MedicinalLeekBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.CRIMSON_NYLIUM).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MEDICINAL_LEEK_SOUNDS)))
@JvmField
val BUGWORT = this.create("bugwort", BugwortBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.COLOR_PURPLE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MEDICINAL_LEEK_SOUNDS)))
// TODO after 1.7
// @JvmField
// val BUGWORT = this.create("bugwort", BugwortBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.COLOR_PURPLE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MEDICINAL_LEEK_SOUNDS)))
@JvmField
val ENERGY_ROOT = this.create("energy_root", EnergyRootBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.DIRT).noCollission().randomTicks().instabreak().sound(CobblemonSounds.ENERGY_ROOT_SOUNDS)))
@JvmField
@@ -240,11 +243,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).mapColor(MapColor.PLANT).ignitedByLava().noCollission().instabreak().sound(CobblemonSounds.REVIVAL_HERB_SOUNDS)))
@JvmField
val LURE_CAKE = this.create("lure_cake", LureCakeBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY)))
val POKE_CAKE = this.create("poke_cake", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(LIT)) 3 else 0 }, false))
@JvmField
val POKE_CAKE = this.create("poke_cake", PokeCakeBlock(BlockBehaviour.Properties.ofFullCopy(LURE_CAKE)))
@JvmField
val CANDLE_POKE_CAKE = this.create("candle_poke_cake", CandlePokeCakeBlock(BlockBehaviour.Properties.ofFullCopy(LURE_CAKE).lightLevel { blockState -> if (blockState.getValue(LIT) == true) 3 else 0 }))
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(LIT)) 3 else 0 }, true))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@@ -449,7 +450,10 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
)
@JvmField
val CAMPFIRE = create("campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel { if ((it.getValue(CampfireBlock.SOUL) as Boolean)) 9 else 14 }))
val CAMPFIRE = create("campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel{14}, false))
@JvmField
val SOUL_CAMPFIRE = create("soul_campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel{9}, true))
@JvmField
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
@@ -492,8 +496,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val DISPLAY_CASE = create("display_case", DisplayCaseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F).requiresCorrectToolForDrops()))
@JvmField
val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3f)))
// TODO after 1.7
// @JvmField
// val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3f)))
@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 }))
@@ -517,7 +522,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@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().sound(CobblemonSounds.VIVICHOKE_SOUNDS)))
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)))
@@ -540,11 +545,11 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
@JvmField
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(SoundType.GRASS)))
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(CobblemonSounds.HEARTY_GRAIN_BALE_SOUNDS)))
@JvmField
val TATAMI_BLOCK = this.create("tatami_block", HorizontalRotationalBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(SoundType.MOSS_CARPET)))
val TATAMI_BLOCK = this.create("tatami_block", HorizontalRotationalBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(CobblemonSounds.TATAMI_BLOCK_SOUNDS)))
@JvmField
val TATAMI_MAT = this.create("tatami_mat", HorizontalRotationCarpetBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(SoundType.MOSS_CARPET)))
val TATAMI_MAT = this.create("tatami_mat", HorizontalRotationCarpetBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(CobblemonSounds.TATAMI_MAT_SOUNDS)))
/**
* Returns a map of all the blocks that can be stripped with an axe in the format of input - output.
@@ -349,15 +349,15 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SACCHARINE_SAPLING = compostableBlockItem("saccharine_sapling", CobblemonBlocks.SACCHARINE_SAPLING, 0.30f)
@JvmField
val BUGWORT = compostableBlockItem("bugwort", CobblemonBlocks.BUGWORT)
// @JvmField
// val BUGWORT = compostableBlockItem("bugwort", CobblemonBlocks.BUGWORT) // TODO after 1.7
@JvmField
val POKE_BAIT = noSettingsItem("poke_bait")
@JvmField
val LURE_CAKE = blockItem("lure_cake", CobblemonBlocks.LURE_CAKE)
val POKE_CAKE = this.create("poke_cake", BlockItem(CobblemonBlocks.POKE_CAKE, Item.Properties().stacksTo(1)))
@JvmField
val POKE_CAKE = blockItem("poke_cake", CobblemonBlocks.POKE_CAKE)
val POKE_SNACK = this.create("poke_snack", BlockItem(CobblemonBlocks.POKE_SNACK, Item.Properties().stacksTo(16)))
val aprijuices = mutableListOf<AprijuiceItem>()
@JvmField
@@ -386,31 +386,31 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// FOODS
@JvmField
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
val SWEET_HEART = noSettingsItem("sweet_heart") // TODO make a SweetHeartItem class for breeding purposes
@JvmField
val TASTY_TAIL = create("tasty_tail", foodItem(3, 0.3f))
@JvmField
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 7, 0.343f, false)
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 64, 7, 0.343f, false)
@JvmField
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 8, 0.2f, false)
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 64, 8, 0.2f, false)
@JvmField
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 7, 0.343f, false)
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 64, 2, 0.3f, false)
@JvmField
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 9, 0.1335f, false)
val OLD_GATEAU = regionalFoodItem("old_gateau", 64, 9, 0.1335f, false)
@JvmField
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 5, .28f, false)
val CASTELIACONE = regionalFoodItem("casteliacone", 64, 5, .28f, false)
@JvmField
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 8, 0.2f, false)
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 64, 4, 0.4f, false)
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 7, 0.343f, false)
val BIG_MALASADA = regionalFoodItem("big_malasada", 64, 7, 0.343f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 8, 0.2f, false)
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 64, 8, 0.2f, false)
@JvmField
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 13, 0.5f, false)
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 64, 13, 0.5f, false)
// todo we might need to wait on these for later? These impact battles and may be harder to do
/*@JvmField
@@ -421,20 +421,20 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val TWICE_SPICED_BEETROOT = noSettingsItem("twice_spiced_beetroot") // todo make a TwiceSpiceBeetrootItem class for battle purposes
*/
@JvmField
val POTATO_MOCHI = create("potato_mochi", CobblemonItem(Properties().stacksTo(16)
val POTATO_MOCHI = create("potato_mochi", CobblemonItem(Properties().stacksTo(64)
.food(FoodProperties.Builder()
.nutrition(8)
.saturationModifier(0.2375F)
.nutrition(4)
.saturationModifier(0.4F)
.build())))
@JvmField
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(16)
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(64)
.food(FoodProperties.Builder()
.nutrition(6)
.saturationModifier(0.2335F)
.usingConvertsTo(Items.STICK)
.build())))
@JvmField
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(16)
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(64)
.food(FoodProperties.Builder()
.nutrition(5)
.saturationModifier(0.22F)
@@ -460,8 +460,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
@JvmField
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
@JvmField
val INCENSE_SWEET = blockItem("incense_sweet", CobblemonBlocks.INCENSE_SWEET)
// @JvmField
// val INCENSE_SWEET = blockItem("incense_sweet", CobblemonBlocks.INCENSE_SWEET) // TODO after 1.7
// Evolution items
@@ -695,7 +695,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 0.85f)
@JvmField
val MOOMOO_MILK = noSettingsItem("moomoo_milk")
val MOOMOO_MILK = create("moomoo_milk", CobblemonItem(Item.Properties().craftRemainder(Items.GLASS_BOTTLE)))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION))
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
@@ -78,6 +79,7 @@ import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidP
import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
@@ -409,6 +411,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SpawnSnowstormEntityParticlePacket.ID, SpawnSnowstormEntityParticlePacket::decode, SpawnSnowstormEntityParticleHandler))
list.add(PacketRegisterInfo(RunPosableMoLangPacket.ID, RunPosableMoLangPacket::decode, RunPosableMoLangHandler))
list.add(PacketRegisterInfo(SaccharineLogBlockParticlesPacket.ID, SaccharineLogBlockParticlesPacket::decode, SaccharineLogBlockParticlesHandler))
list.add(PacketRegisterInfo(PokeSnackBlockParticlesPacket.ID, PokeSnackBlockParticlesPacket::decode, PokeSnackBlockParticlesHandler))
// Hax
list.add(PacketRegisterInfo(UnvalidatedPlaySoundS2CPacket.ID, UnvalidatedPlaySoundS2CPacket::decode, UnvalidatedPlaySoundS2CPacketHandler))
@@ -26,11 +26,12 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
@JvmField
val NURSE_KEY = createKey("nurse", CobblemonBlocks.HEALING_MACHINE, 1, 1)
@JvmField
val LURE_CAKE_KEY = createKey("lure_cake", CobblemonBlocks.LURE_CAKE, 0, 1)
val POKE_SNACK_KEY = createKey("poke_snack", CobblemonBlocks.POKE_SNACK, 0, 1)
@JvmField
val SACCHARINE_LOG_SLATHERED_KEY = createKey("saccharine_log_slathered", CobblemonBlocks.SACCHARINE_LOG_SLATHERED, 0, 1)
@JvmField
val INCENSE_SWEET_KEY = createKey("incense_sweet", CobblemonBlocks.INCENSE_SWEET, 0, 1)
// TODO after 1.7
// @JvmField
// val INCENSE_SWEET_KEY = createKey("incense_sweet", CobblemonBlocks.INCENSE_SWEET, 0, 1)
private fun createKey(string: String, block: Block, maxTickets: Int, validRange: Int ): ResourceKey<PoiType> =
ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string)).also { resourceKey ->
@@ -204,6 +204,19 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val EVOLUTION_STONE_BLOCK_STEP = this.create("block.evolution_stone_block.step")
@JvmField
val TATAMI_BLOCK_BREAK = this.create("block.tatami.break")
@JvmField
val TATAMI_BLOCK_HIT = this.create("block.tatami.hit")
@JvmField
val TATAMI_BLOCK_PLACE = this.create("block.tatami.place")
@JvmField
val TATAMI_BLOCK_STEP = this.create("block.tatami.step")
@JvmField
val TATAMI_MAT_BREAK = this.create("block.tatami_mat.break")
@JvmField
val TATAMI_MAT_PLACE = this.create("block.tatami_mat.place")
@JvmField
val GIMMIGHOUL_GIVE_ITEM = this.create("pokemon.gimmighoul.give_item")
@JvmField
@@ -230,6 +243,24 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val VIVICHOKE_PLACE = this.create("block.vivichoke.place")
@JvmField
val HEARTY_GRAIN_BALE_BREAK = this.create("block.hearty_grain_bale.break")
@JvmField
val HEARTY_GRAIN_BALE_PLACE = this.create("block.hearty_grain_bale.place")
@JvmField
val HEARTY_GRAIN_BALE_HIT = this.create("block.hearty_grain_bale.hit")
@JvmField
val HEARTY_GRAIN_BALE_STEP = this.create("block.hearty_grain_bale.step")
@JvmField
val HEARTY_GRAINS_BREAK = this.create("block.hearty_grains.break")
@JvmField
val HEARTY_GRAINS_PLACE = this.create("block.hearty_grains.place")
@JvmField
val HEARTY_GRAINS_BREAK_WATER = this.create("block.hearty_grains.break_water")
@JvmField
val HEARTY_GRAINS_PLACE_WATER = this.create("block.hearty_grains.place_water")
@JvmField
val MINT_BREAK = this.create("block.mint.break")
@JvmField
@@ -324,8 +355,8 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val IMPACT_WATER = this.create("impact.water")
// Note to self or whoever's peeking, block sounds have to be at 1.1f pitch
// For some reason, the game pitches them down and compensating for it here returns them to normal
@JvmField
val TUMBLESTONE_SOUNDS = SoundType(1f, 1.1f,
TUMBLESTONE_BREAK,
@@ -343,6 +374,7 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
TUMBLESTONE_HIT,
TUMBLESTONE_STEP
)
@JvmField
val EVOLUTION_STONE_BLOCK_SOUNDS = SoundType(1f, 1.1f,
EVOLUTION_STONE_BLOCK_BREAK,
@@ -352,6 +384,23 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
EVOLUTION_STONE_BLOCK_STEP
)
@JvmField
val TATAMI_BLOCK_SOUNDS = SoundType(1f, 1.1f,
TATAMI_BLOCK_BREAK,
TATAMI_BLOCK_STEP,
TATAMI_BLOCK_PLACE,
TATAMI_BLOCK_HIT,
TATAMI_BLOCK_STEP
)
@JvmField
val TATAMI_MAT_SOUNDS = SoundType(1f, 1.1f,
TATAMI_MAT_BREAK,
TATAMI_BLOCK_STEP,
TATAMI_MAT_PLACE,
TATAMI_BLOCK_HIT,
TATAMI_BLOCK_STEP
)
@JvmField
val BERRY_BUSH_SOUNDS = SoundType(0.8f, 1.1f,
@@ -398,6 +447,33 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
SoundEvents.GRASS_FALL
)
@JvmField
val HEARTY_GRAIN_BALE_SOUNDS = SoundType(0.6f, 1.1f,
HEARTY_GRAIN_BALE_BREAK,
HEARTY_GRAIN_BALE_STEP,
HEARTY_GRAIN_BALE_PLACE,
HEARTY_GRAIN_BALE_HIT,
HEARTY_GRAIN_BALE_STEP
)
@JvmField
val HEARTY_GRAINS_SOUNDS = SoundType(0.6f, 1.1f,
HEARTY_GRAINS_BREAK,
HEARTY_GRAIN_BALE_STEP,
HEARTY_GRAINS_PLACE,
HEARTY_GRAIN_BALE_HIT,
HEARTY_GRAIN_BALE_STEP
)
@JvmField
val HEARTY_GRAINS_WATER_SOUNDS = SoundType(0.6f, 1.1f,
HEARTY_GRAINS_BREAK_WATER,
SoundEvents.GRASS_STEP,
HEARTY_GRAINS_PLACE_WATER,
SoundEvents.GRASS_HIT,
SoundEvents.GRASS_FALL
)
@JvmField
val MINT_SOUNDS = SoundType(0.6f, 1.1f,
MINT_BREAK,
@@ -437,7 +513,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
val DISPLAY_CASE_PLACE = this.create("block.display_case.place")
@JvmField
val DISPLAY_CASE_STEP = this.create("block.display_case.step")
@JvmField
val DISPLAY_CASE_SOUNDS = SoundType(1f, 1f,
DISPLAY_CASE_BREAK,
@@ -448,7 +523,7 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
)
@JvmField
val CAMPFIRE_POT_PLACE_CAMPFIRE = this.create("block.campfire_pot.place_campfire")
val CAMPFIRE_POT_SET = this.create("block.campfire_pot.set")
@JvmField
val CAMPFIRE_POT_RETRIEVE = this.create("block.campfire_pot.retrieve")
@JvmField
@@ -456,12 +531,14 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val CAMPFIRE_POT_CLOSE = this.create("block.campfire_pot.close")
@JvmField
val CAMPFIRE_POT_USE = this.create("block.campfire_pot.use")
val CAMPFIRE_POT_TAKE_ITEM = this.create("block.campfire_pot.take_item")
@JvmField
val CAMPFIRE_POT_ACTIVE = this.create("block.campfire_pot.active")
@JvmField
val CAMPFIRE_POT_AMBIENT = this.create("block.campfire_pot.ambient")
@JvmField
val CAMPFIRE_POT_COOK = this.create("block.campfire_pot.cook")
@JvmField
val CAMPFIRE_POT_CRAFT = this.create("block.campfire_pot.craft")
@JvmField
val CAMPFIRE_POT_PLACE = this.create("block.campfire_pot.place")
@JvmField
val CAMPFIRE_POT_BREAK = this.create("block.campfire_pot.break")
@@ -15,9 +15,12 @@ import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.AbilityRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbilityType
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
import java.io.File
import kotlin.collections.set
/**
* Registry for all known Abilities
@@ -31,15 +34,31 @@ object Abilities : DataRegistry {
val DUMMY = AbilityTemplate(name = "dummy")
private val abilityMap = mutableMapOf<String, AbilityTemplate>()
internal val abilityScripts = mutableMapOf<String, String>() // abilityId to JavaScript
override fun reload(manager: ResourceManager) {
PotentialAbility.types.clear()
PotentialAbility.types.add(CommonAbilityType)
PotentialAbility.types.add(HiddenAbilityType)
this.abilityMap.clear()
val abilitiesJson = ShowdownService.service.getAbilityIds()
this.abilityScripts.clear()
ShowdownService.service.resetRegistryData("ability")
manager.listResources("abilities") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
val js = reader.readText()
abilityScripts[resolvedIdentifier.path] = js
}
}
}
ShowdownService.service.sendRegistryData(abilityScripts, "ability")
val abilitiesJson = ShowdownService.service.getRegistryData("ability")
for (i in 0 until abilitiesJson.size()) {
val id = abilitiesJson[i].asString
val jsonAbility = abilitiesJson[i].asJsonObject
val id = jsonAbility.get("id").asString
val ability = AbilityTemplate(id)
this.register(ability)
}
@@ -95,6 +95,7 @@ class Berry(
val fruitModelIdentifier: ResourceLocation,
val fruitTexture: ResourceLocation,
val stageOnePositioning: GrowthPoint,
val pokeSnackPositionings: Array<GrowthPoint>,
val weight: Float,
val boneMealChance: Float
) {
@@ -293,6 +294,14 @@ class Berry(
buffer.writeDouble(stageOnePositioning.rotation.x)
buffer.writeDouble(stageOnePositioning.rotation.y)
buffer.writeDouble(stageOnePositioning.rotation.z)
buffer.writeCollection(this.pokeSnackPositionings.toList()) { writer, value ->
writer.writeDouble(value.position.x)
writer.writeDouble(value.position.y)
writer.writeDouble(value.position.z)
writer.writeDouble(value.rotation.x)
writer.writeDouble(value.rotation.y)
writer.writeDouble(value.rotation.z)
}
buffer.writeFloat(boneMealChance)
}
@@ -360,9 +369,12 @@ class Berry(
val flowerTexture = buffer.readIdentifier()
val fruitModelIdentifier = buffer.readIdentifier()
val fruitTexture = buffer.readIdentifier()
val stageOneYPos = GrowthPoint(Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()), Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()))
val stageOnePos = GrowthPoint(Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()), Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()))
val pokeSnackPos = buffer.readList { reader ->
GrowthPoint(Vec3(reader.readDouble(), reader.readDouble(), reader.readDouble()), Vec3(reader.readDouble(), reader.readDouble(), reader.readDouble()))
}.toTypedArray()
val boneMealChance = buffer.readFloat()
return Berry(identifier, baseYield, emptyList(), growthTime, refreshRate, favMulchs, emptySet(), emptyList(), growthPoints, randomizedGrowthPoints, mutations, sproutShapeBoxes, matureShapeBoxes, flavors, colour, tintIndexes, flowerModelIdentifier, flowerTexture, fruitModelIdentifier, fruitTexture, stageOneYPos, 0F, boneMealChance)
return Berry(identifier, baseYield, emptyList(), growthTime, refreshRate, favMulchs, emptySet(), emptyList(), growthPoints, randomizedGrowthPoints, mutations, sproutShapeBoxes, matureShapeBoxes, flavors, colour, tintIndexes, flowerModelIdentifier, flowerTexture, fruitModelIdentifier, fruitTexture, stageOnePos, pokeSnackPos, 0F, boneMealChance)
}
}
@@ -12,6 +12,7 @@ import com.bedrockk.molang.runtime.MoLangEnvironment
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.struct.ArrayStruct
import com.bedrockk.molang.runtime.struct.MoStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
@@ -157,6 +158,17 @@ object MoLangFunctions {
val message = params.get<MoValue>(0).asString()
Cobblemon.LOGGER.info(message)
},
"delete_variable" to java.util.function.Function { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
struct.map.remove(variable)
DoubleValue.ONE
},
"delete_variables" to java.util.function.Function { params ->
val struct = params.get<VariableStruct>(0)
struct.map.clear()
DoubleValue.ONE
},
"set_query" to java.util.function.Function { params ->
val variable = params.getString(0)
val value = params.get<MoValue>(1)
@@ -21,9 +21,12 @@ import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
import java.io.File
import kotlin.collections.set
/**
* Registry for all known Moves
@@ -36,11 +39,26 @@ object Moves : DataRegistry {
private val allMoves = mutableMapOf<String, MoveTemplate>()
private val idMapping = mutableMapOf<Int, MoveTemplate>()
internal val moveScripts = mutableMapOf<String, String>() // moveId to JavaScript
override fun reload(manager: ResourceManager) {
this.allMoves.clear()
this.idMapping.clear()
val movesJson = ShowdownService.service.getMoves()
this.moveScripts.clear()
ShowdownService.service.resetRegistryData("move")
manager.listResources("moves") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
val js = reader.readText()
moveScripts[resolvedIdentifier.path] = js
}
}
}
ShowdownService.service.sendRegistryData(moveScripts, "move")
val movesJson = ShowdownService.service.getRegistryData("move")
for (i in 0 until movesJson.size()) {
val jsMove = movesJson[i].asJsonObject
val id = jsMove.get("id").asString
@@ -42,6 +42,7 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
@@ -76,6 +77,7 @@ import net.minecraft.world.item.Item
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.level.material.Fluid
import net.minecraft.world.phys.AABB
object PokemonSpecies : JsonDataRegistry<Species> {
@@ -123,6 +125,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.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(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
@@ -157,7 +160,8 @@ object PokemonSpecies : JsonDataRegistry<Species> {
}
this.species.forEach(Species::resolveEvolutionMoves)
Cobblemon.showdownThread.queue {
it.registerSpecies()
it.resetRegistryData("species")
it.sendRegistryData(allShowdownSpecies(), "species")
it.indicateSpeciesInitialized()
// Reload this with the mod
CobblemonHeldItemManager.load()
@@ -312,4 +316,18 @@ object PokemonSpecies : JsonDataRegistry<Species> {
return "${species.resourceIdentifier.namespace}:${species.name}"
}
internal fun allShowdownSpecies(): Map<String, String> {
val result = mutableMapOf<String, String>()
this.species.forEach {species ->
val baseSpecies = ShowdownSpecies(species, null)
result[baseSpecies.name] = this.gson.toJson(baseSpecies)
species.forms.forEach { form ->
if (form != species.standardForm) {
val formSpecies = ShowdownSpecies(species, form)
result[formSpecies.name] = this.gson.toJson(formSpecies)
}
}
}
return result
}
}
@@ -8,8 +8,11 @@
package com.cobblemon.mod.common.api.pokemon.aspect
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.spawning.influence.SaccharineLogSlatheredInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.LurePokeSnackDetector
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.resources.ResourceLocation
@@ -18,7 +21,12 @@ import net.minecraft.resources.ResourceLocation
* Used by [PokemonClientDelegate.spawnAspectParticle] to spawn associated particles for aspects
*/
val aspectParticleMap: Map<String, ParticleData> = mapOf(
SaccharineLogSlatheredInfluence.HONEY_DRENCHED_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_HONEY, 0.075, 1)
SaccharineLogSlatheredInfluence.HONEY_DRENCHED_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_HONEY, 0.075, 1),
LurePokeSnackDetector.POKE_SNACK_CRUMBED_ASPECT to ParticleData.MinecraftParticle(
BlockParticleOption(
ParticleTypes.BLOCK,
CobblemonBlocks.POKE_SNACK.defaultBlockState()
), 0.05, 3),
)
sealed class ParticleData {
@@ -18,11 +18,4 @@ import net.minecraft.world.entity.PlayerRideableJumping
*
* @since 1.7.0
*/
interface Rideable : PlayerRideableJumping {
/**
* Denotes the manager responsible for handling any instance of riding taking place on an entity
*/
val riding: RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>?
}
interface Rideable : PlayerRideableJumping
@@ -18,58 +18,41 @@ import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import net.minecraft.network.RegistryFriendlyByteBuf
class RidingProperties(
val stats: MutableMap<RidingStat, RidingStatDefinition> = mutableMapOf(),
val seats: List<Seat> = listOf(),
val conditions: List<Expression> = listOf(),
val behaviour: RidingBehaviourSettings? = null
val behaviours: Map<RidingStyle, RidingBehaviourSettings>? = null
) {
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RidingProperties {
val stats: MutableMap<RidingStat, RidingStatDefinition> = buffer.readMap(
{ buffer.readEnum(RidingStat::class.java) },
{ RidingStatDefinition.decode(buffer) }
).toMutableMap()
val seats: List<Seat> = buffer.readList { _ -> Seat.decode(buffer) }
val conditions = buffer.readList { buffer.readString().asExpression() }
val behaviour = buffer.readNullable { _ ->
val key = buffer.readResourceLocation()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance() ?: error("Unknown controller key: $key")
settings.decode(buffer)
return@readNullable settings
val behaviours = buffer.readNullable { _ ->
buffer.readMap(
{ buffer.readEnumConstant(RidingStyle::class.java) },
{
val key = buffer.readResourceLocation()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
settings.decode(buffer)
settings
}
)
}
return RidingProperties(stats = stats, seats = seats, conditions = conditions, behaviour = behaviour)
return RidingProperties(seats = seats, conditions = conditions, behaviours = behaviours)
}
}
internal fun updateStatRange(stat: RidingStat, style: RidingStyle, min: Int, max: Int) {
if (!Cobblemon.config.enableDebugKeys) return
if (stats[stat] == null) {
stats[stat] = RidingStatDefinition()
}
stats[stat]!!.ranges[style] = min..max
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeMap(
stats,
{ _, stat -> buffer.writeEnum(stat) },
{ _, stat -> stat.encode(buffer) }
)
buffer.writeCollection(seats) { _, seat -> seat.encode(buffer) }
buffer.writeCollection(conditions) { _, condition -> buffer.writeString(condition.getString()) }
buffer.writeNullable(behaviour) { _, behaviour ->
behaviour.encode(buffer)
buffer.writeNullable(behaviours) { _, v ->
buffer.writeMap(
v,
{ _, style -> buffer.writeEnumConstant(style) },
{ _, behaviour -> behaviour.encode(buffer) }
)
}
}
fun calculate(stat: RidingStat, style: RidingStyle, boosts: Int): Float {
val definitions = stats[stat] ?: return 0F
return definitions.calculate(style, boosts)
}
fun hasStat(stat: RidingStat, style: RidingStyle): Boolean {
return stats[stat]?.ranges?.containsKey(style) == true
}
}
@@ -101,4 +101,19 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
* Calculate the damage a horizontal collision will do to a ridden Pokémon. This is only relevant to fast flying behaviours, fall damage is separate!
*/
fun damageOnCollision(settings: Settings, state: State, vehicle: PokemonEntity, impactVec: Vec3): Boolean = false
/**
* Internal helpers to help behaviour specific calculations
*/
companion object {
/**
* Scales a given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
internal fun scaleToRange(x: Double, min: Double, max: Double): Double {
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
}
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
@@ -23,4 +25,13 @@ import net.minecraft.world.phys.Vec3
*/
interface RidingBehaviourSettings: Encodable, Decodable {
val key: ResourceLocation
val stats: MutableMap<RidingStat, IntRange>
fun calculate(stat: RidingStat, boosts: Int): Float {
val range = stats[stat] ?: return 0F
val boostQuotient = boosts / 255F
return range.first + (range.last - range.first) * boostQuotient
}
fun hasStat(stat: RidingStat) = stats.containsKey(stat)
}
@@ -41,7 +41,7 @@ object RidingBehaviours {
fun register(key: ResourceLocation, behaviour: RidingBehaviour<out RidingBehaviourSettings, out RidingBehaviourState>) {
if (behaviours.contains(key)) error("Behaviour already registered to key $key")
behaviours[key] = RidingController(behaviour) as RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>
behaviours[key] = behaviour as RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>
}
fun get(key: ResourceLocation): RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState> {
@@ -8,206 +8,119 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.DoubleJump
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.tags.FluidTags
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 kotlin.collections.get
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
*
* @author landonjw
*/
class RidingController<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(val behaviour: RidingBehaviour<Settings, State>) : RidingBehaviour<Settings, State> {
class RidingController(
val entity: PokemonEntity,
val behaviours: Map<RidingStyle, RidingBehaviourSettings>) {
override val key = behaviour.key
var lastTransitionAge: Int = 0
private set
override fun getRidingStyle(settings: Settings, state: State): RidingStyle {
return behaviour.getRidingStyle(settings, state)
var context: ActiveRidingContext? = null
private set
fun changeBehaviour(behaviour: ResourceLocation) {
if (entity.form.riding.behaviours == null) return
val style = entity.form.riding.behaviours!!.filter { it.value.key == behaviour }.keys.firstOrNull() ?: return
val behaviourSettings = entity.pokemon.riding.behaviours?.get(style) ?: return
val newState = RidingBehaviours.get(behaviourSettings.key).createDefaultState(behaviourSettings)
context?.let { newState.stamina.set(it.state.stamina.get(), forced = true) }
context = ActiveRidingContext(behaviourSettings.key, behaviourSettings, newState, style)
lastTransitionAge = entity.ticksLived
}
override fun isActive(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.isActive(settings, state, vehicle)
fun tick() {
if (entity.ticksLived - lastTransitionAge < 10) return
val newTransition = checkForNewTransition(entity.controllingPassenger)
val behaviourSettings = entity.pokemon.riding.behaviours?.get(newTransition)
if (newTransition != context?.style) {
if (newTransition != null && behaviourSettings != null) {
val newState = RidingBehaviours.get(behaviourSettings.key).createDefaultState(behaviourSettings)
context?.let { newState.stamina.set(it.state.stamina.get(), forced = true) }
context = ActiveRidingContext(behaviourSettings.key, behaviourSettings, newState, newTransition)
lastTransitionAge = entity.ticksLived
} else {
context = null
}
}
override fun tick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3) =
behaviour.tick(settings, state, vehicle, driver, input)
override fun pose(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.pose(settings, state, vehicle)
override fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 0.0F
return behaviour.speed(settings, state, vehicle, driver)
if (context == null && !this.entity.level().isClientSide) {
entity.ejectPassengers()
}
}
override fun updatePassengerRotation(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: LivingEntity
) {
if (!isActive(settings, state, vehicle)) return
return behaviour.updatePassengerRotation(settings, state, vehicle, driver)
fun getBehaviour(): RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>? {
if (context == null) return null
return RidingBehaviours.get(context!!.behaviour)
}
override fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {
if (!isActive(settings, state, vehicle)) return
return behaviour.clampPassengerRotation(settings, state, vehicle, driver)
private fun checkForNewTransition(driver: LivingEntity?): RidingStyle? {
when (context?.style) {
RidingStyle.AIR -> {
if (canTransitionToLand()) return RidingStyle.LAND
if (canTransitionToLiquid()) return RidingStyle.LIQUID
return RidingStyle.AIR
}
RidingStyle.LIQUID -> {
if (canTransitionToAir(driver)) return RidingStyle.AIR
if (canTransitionToLand()) return RidingStyle.LAND
return RidingStyle.LIQUID
}
RidingStyle.LAND -> {
if (canTransitionToAir(driver)) return RidingStyle.AIR
if (canTransitionToLiquid()) return RidingStyle.LIQUID
return RidingStyle.LAND
}
null -> {
if (canTransitionToAir(driver)) return RidingStyle.AIR
if (canTransitionToLiquid()) return RidingStyle.LIQUID
if (canTransitionToLand()) return RidingStyle.LAND
return null
}
}
}
override fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2 {
if (!isActive(settings, state, vehicle)) return driver.rotationVector
return behaviour.rotation(settings, state, vehicle, driver)
private fun canTransitionToLand(): Boolean {
if (entity.isInLiquid || entity.isUnderWater) return false
return entity.onGround()
}
override fun velocity(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.velocity(settings, state, vehicle, driver, input)
private fun canTransitionToLiquid(): Boolean {
return entity.isInLiquid || entity.isUnderWater
}
override fun angRollVel(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.angRollVel(settings, state, vehicle, driver, deltaTime)
private fun canTransitionToAir(driver: LivingEntity?): Boolean {
if (driver != null) {
if (driver !is DoubleJump) return false
// check if style is water and player is submerged
val isEyeInLiquid = entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA)
if (context?.style == RidingStyle.LIQUID && isEyeInLiquid) return false
return (driver as DoubleJump).isDoubleJumping
}
else {
return !entity.onGround() && !(entity.isInLiquid || entity.isUnderWater)
}
}
override fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.rotationOnMouseXY(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
override fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.canJump(settings, state, vehicle, driver)
}
override fun shouldRotateRiderHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotateRiderHead(settings, state, vehicle)
}
override fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePokemonHead(settings, state, vehicle)
}
override fun dismountOnShift(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.dismountOnShift(settings, state, vehicle)
}
override fun turnOffOnGround(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.turnOffOnGround(settings, state, vehicle)
}
override fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRoll(settings, state, vehicle)
}
override fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double {
if (!isActive(settings, state, vehicle)) return 0.5
return behaviour.inertia(settings, state, vehicle)
}
override fun useRidingAltPose(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): ResourceLocation {
if (!isActive(settings, state, vehicle)) return cobblemonResource("no_pose")
return behaviour.useRidingAltPose(settings, state, vehicle, driver)
}
override fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return true
return behaviour.useAngVelSmoothing(settings, state, vehicle)
}
override fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 1.0F
return behaviour.rideFovMultiplier(settings, state, vehicle, driver)
}
override fun gravity(settings: Settings, state: State, vehicle: PokemonEntity, regularGravity: Double): Double {
if (!isActive(settings, state, vehicle)) return regularGravity
return behaviour.gravity(settings, state, vehicle, regularGravity)
}
override fun jumpForce(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.jumpForce(settings, state, vehicle, driver, jumpStrength)
}
override fun setRideBar(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 0.0F
return behaviour.setRideBar(settings, state, vehicle, driver)
}
override fun maxUpStep(settings: Settings, state: State, vehicle: PokemonEntity): Float? {
if (!isActive(settings, state, vehicle)) return null
return behaviour.maxUpStep(settings, state, vehicle)
}
override fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList {
if (!isActive(settings, state, vehicle)) return RideSoundSettingsList()
return behaviour.getRideSounds(settings, state, vehicle)
}
override fun createDefaultState(settings: Settings): State {
return behaviour.createDefaultState(settings)
}
override fun damageOnCollision(
settings: Settings,
state: State,
vehicle: PokemonEntity,
impactVec: Vec3
): Boolean {
return behaviour.damageOnCollision(settings, state, vehicle, impactVec)
}
}
class ActiveRidingContext(
val behaviour: ResourceLocation,
val settings: RidingBehaviourSettings,
val state: RidingBehaviourState,
val style: RidingStyle
)
@@ -17,6 +17,7 @@ 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.sound.RideSoundSettingsList
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.*
@@ -242,7 +243,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//This rotation limit will be used to know if the ride has exceeded it and its needs correcting.
val rotMin = 15.0
val rotLimit = max(handling * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
val rotLimit = max(handling * sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
var yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //sin(Math.toRadians(controller.roll.toDouble())) * abs(cos(Math.toRadians(controller.pitch.toDouble())))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))
@@ -298,7 +299,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(90 * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
val rotLimit = max(90 * sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
@@ -314,7 +315,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)))
val yawForce = xInput * ( 1.0 - sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)))
//Yaw locally a bit when up or down so that its more intuitive to make it out of a dive or a straight vertical
//climb
@@ -342,16 +343,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return Vec3(0.0, 0.0, rollForce)
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: BirdSettings,
state: BirdState,
@@ -399,7 +390,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
val normalizedGlideSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
@@ -470,6 +461,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
class BirdSettings : RidingBehaviourSettings {
override val key = BirdBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteAltitude: Expression = "false".asExpression()
private set
@@ -491,6 +483,7 @@ class BirdSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
@@ -503,6 +496,7 @@ class BirdSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
@@ -17,6 +17,7 @@ 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.sound.RideSoundSettingsList
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.*
@@ -228,7 +229,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//This rotation limit will be used to know if the ride has exceeded it and its needs correcting.
val rotMin = 15.0
val rotLimit = max(handling * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
val rotLimit = max(handling * sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
var yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //sin(Math.toRadians(controller.roll.toDouble())) * abs(cos(Math.toRadians(controller.pitch.toDouble())))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))
@@ -282,7 +283,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - min(sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
val yawForce = xInput * ( 1.0 - min(sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
@@ -302,16 +303,6 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
return Vec3.ZERO
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: BirdNoRollSettings,
state: BirdNoRollState,
@@ -359,7 +350,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
val normalizedGlideSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
@@ -430,6 +421,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
class BirdNoRollSettings : RidingBehaviourSettings {
override val key = BirdNoRollBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteAltitude: Expression = "false".asExpression()
private set
@@ -451,6 +443,7 @@ class BirdNoRollSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
@@ -463,6 +456,7 @@ class BirdNoRollSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
@@ -75,8 +75,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
val xVector = if (vehicle.runtime.resolveBoolean(settings.canStrafe)) driver.xxa.toDouble() else 0.0
val yVector = -vehicle.runtime.resolveDouble(settings.glideSpeed)
val zVector = driver.zza.toDouble()
val statSpeed = vehicle.rideProp.calculate(RidingStat.SPEED, RidingStyle.AIR, 0 )
val speedStat = settings.calculate(RidingStat.SPEED, 0)
return Vec3(xVector, yVector, zVector)
}
@@ -197,6 +196,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
class GliderSettings : RidingBehaviourSettings {
override val key = GliderBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var glideSpeed: Expression = "0.1".asExpression()
private set
@@ -211,6 +211,7 @@ class GliderSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
@@ -218,6 +219,7 @@ class GliderSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -287,6 +288,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
class HelicopterSettings : RidingBehaviourSettings {
override val key = HelicopterBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "1.0".asExpression()
private set
@@ -304,6 +306,7 @@ class HelicopterSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
@@ -312,6 +315,7 @@ class HelicopterSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
@@ -14,6 +14,7 @@ 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.sound.RideSoundSettingsList
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.*
@@ -103,7 +104,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
val rotAmount = 10.0f
//Take the inverse so that you turn more at higher speeds
val normSpeed = 1.0f - 0.5f*normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
val normSpeed = 1.0f - 0.5f*RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
@@ -163,7 +164,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
val rotDiffMod = (sqrt(abs(rotDiffNorm)) * rotDiffNorm.sign)
//Take the inverse so that you turn less at higher speeds
val normSpeed = 1.0f // = 1.0f - 0.5f*normalizeVal(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val normSpeed = 1.0f // = 1.0f - 0.5f*RidingBehaviour.scaleToRange(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val turnRate = (handling.toFloat() / 20.0f)
@@ -307,15 +308,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: HoverSettings,
state: HoverState,
@@ -472,6 +464,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
class HoverSettings : RidingBehaviourSettings {
override val key = HoverBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var rideSound: ResourceLocation = "ride.loop.saucer".asIdentifierDefaultingNamespace()
var volumeExpr: Expression = "math.pow(math.min(q.ride_velocity() / 0.8, 1.0),2)".asExpression()
@@ -503,6 +496,7 @@ class HoverSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
@@ -512,6 +506,7 @@ class HoverSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -79,7 +80,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
// //Calculate stamina loss due to speed
// //At max speed it will tick down 0.1 a second so the stamina will last ten seconds
// //There has got to be a better way to express this equation. It interpolates between 0.5 and 1.0
// var staminaRate = (normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed))
// var staminaRate = (RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), minSpeed, topSpeed))
//
// //interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
// staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
@@ -94,16 +95,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return state.rideVelocity.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun rotation(
settings: JetSettings,
state: JetState,
@@ -159,7 +150,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
val accelMod = max(-(RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -177,7 +168,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
)
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
val deccelMod = max((RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(
@@ -323,7 +314,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
val normalizedSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
@@ -398,6 +389,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
class JetSettings : RidingBehaviourSettings {
override val key = JetBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "0".asExpression()
private set
@@ -428,6 +420,7 @@ class JetSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
@@ -441,6 +434,7 @@ class JetSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
@@ -23,6 +23,7 @@ import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -79,16 +80,6 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
return state.rideVelocity.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun rotation(
settings: JetNoRollSettings,
state: JetNoRollState,
@@ -145,7 +136,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
val accelMod = max(-(RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -163,7 +154,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
)
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
val deccelMod = max((RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(
@@ -307,7 +298,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
val normalizedSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
@@ -382,6 +373,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
class JetNoRollSettings : RidingBehaviourSettings {
override val key = JetNoRollBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "0".asExpression()
private set
@@ -412,6 +404,7 @@ class JetNoRollSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
@@ -425,6 +418,7 @@ class JetNoRollSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
@@ -15,6 +15,7 @@ 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.sound.RideSoundSettingsList
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.*
@@ -284,15 +285,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: RocketSettings,
state: RocketState,
@@ -373,7 +365,7 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
//val exceededSpeed = max(vehicle.deltaMovement.length() - topSpeed, 0.0)
//TODO: Remove this magic number and get a better comparison for boost top speed
val normalizedBoostSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
val normalizedBoostSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
return 1.0f
@@ -458,6 +450,7 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
class RocketSettings : RidingBehaviourSettings {
override val key = RocketBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 0.65, 0.3) * 0.25".asExpression()
private set
@@ -485,6 +478,7 @@ class RocketSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
@@ -494,6 +488,7 @@ class RocketSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
@@ -12,10 +12,13 @@ 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.sound.RideSoundSettingsList
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.adapters.RidingBehaviourSettingsAdapter
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.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -380,8 +383,10 @@ open class CompositeSettings : RidingBehaviourSettings {
private set
override val key: ResourceLocation = CompositeBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
buffer.writeResourceLocation(key)
buffer.writeResourceLocation(transitionStrategy)
defaultBehaviour.encode(buffer)
@@ -389,6 +394,7 @@ open class CompositeSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
transitionStrategy = buffer.readResourceLocation()
val defaultBehaviourKey = buffer.readResourceLocation()
defaultBehaviour = RidingBehaviourSettingsAdapter.types[defaultBehaviourKey]?.getConstructor()?.newInstance()
@@ -14,6 +14,7 @@ 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.sound.RideSoundSettingsList
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.*
@@ -173,7 +174,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val normSpeed = 1.0f
// TurnRate should always be quick if not sprinting
val invRelSpeed = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 1.0f) * -1.0f
val invRelSpeed = (RidingBehaviour.scaleToRange(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 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.
@@ -201,19 +202,17 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
val walkSpeed = getWalkSpeed(vehicle)
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 1.3
val topSpeed = if(state.sprinting.get()) rideTopSpeed else walkSpeed
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 2
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z).normalize().scale(vehicle.deltaMovement.length())
//var newVelocity = vehicle.deltaMovement.normalize()
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
@@ -236,9 +235,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.8
val terminalVel = 2.0
@@ -252,18 +248,12 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 1.0
val velMag = newVelocity.length()
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
val test = 0.0
//Ensure this doesn't add unwanted forward velocity
//val mag = if(newVelocity.length() < rideTopSpeed) newVelocity.length() else rideTopSpeed
//newVelocity = newVelocity.normalize().scale(mag)
}
//Zero out lateral velocity possibly picked up from a controller transition
@@ -272,15 +262,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return newVelocity
}
/*
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
@@ -449,10 +430,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
class HorseSettings : RidingBehaviourSettings {
override val key = HorseBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
var canSprint = "true".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
@@ -476,6 +461,7 @@ class HorseSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
@@ -485,6 +471,7 @@ class HorseSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
@@ -17,6 +17,7 @@ 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.sound.RideSoundSettingsList
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.*
@@ -146,7 +147,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val driftInput = turnInput * 0.25f
val lowSpeedTurnBoost = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 2.0f) * -1.0f
val lowSpeedTurnBoost = (RidingBehaviour.scaleToRange(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 2.0f) * -1.0f
val forcedDriftTurnMomentum = (80.0f / 20.0f) * lowSpeedTurnBoost
val maxTurnMomentum = (60.0f / 20.0f) * lowSpeedTurnBoost
val maxDriftMomentum = forcedDriftTurnMomentum * 0.6f
@@ -292,15 +293,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
return newVelocity
}
/*
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: MinekartSettings,
state: MinekartState,
@@ -455,6 +447,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
@@ -484,12 +477,14 @@ class MinekartSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(boostLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
boostLimit = buffer.readExpression()
@@ -17,6 +17,7 @@ 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.sound.RideSoundSettingsList
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.*
@@ -220,9 +221,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
@@ -414,6 +412,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
class VehicleSettings : RidingBehaviourSettings {
override val key = VehicleBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
@@ -456,6 +455,7 @@ class VehicleSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(speed)
@@ -466,6 +466,7 @@ class VehicleSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
speed = buffer.readExpression()
@@ -354,6 +354,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
class BoatSettings : RidingBehaviourSettings {
override val key = BoatBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
var terminalVelocity = "-2.0".asExpression()
@@ -364,11 +365,13 @@ class BoatSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(terminalVelocity)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
terminalVelocity = buffer.readExpression()
}
@@ -15,10 +15,13 @@ 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.sound.RideSoundSettingsList
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.blockPositionsAsListRounded
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.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -236,6 +239,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
class BurstSettings : RidingBehaviourSettings {
override val key = BurstBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var dashSpeed = 1F
private set
@@ -244,11 +248,13 @@ class BurstSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeFloat(dashSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
dashSpeed = buffer.readFloat()
}
@@ -17,6 +17,7 @@ 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.sound.RideSoundSettingsList
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.*
@@ -194,7 +195,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
// Yaw
// TODO:move all magic numbers to handling expression
val yawSpeed = handling * deltaTime * 0.25
val yawForce = sin(controller.roll.toRadians()) * yawSpeed * ( 1.0 - min(sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
val yawForce = sin(controller.roll.toRadians()) * yawSpeed * ( 1.0 - min(sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat())
@@ -210,16 +211,6 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return Vec3.ZERO
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: DolphinSettings,
state: DolphinState,
@@ -328,6 +319,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
class DolphinSettings : RidingBehaviourSettings {
override val key = DolphinBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
@@ -357,6 +349,7 @@ class DolphinSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
@@ -369,6 +362,7 @@ class DolphinSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
@@ -63,9 +63,9 @@ class RideSoundManager(
}
fun getActiveRideKey(): ResourceLocation {
val key = ride.ridingBehaviourSettings?.key ?: cobblemonResource("no_key")
val key = ride.ridingController?.context?.settings?.key ?: cobblemonResource("no_key")
if (key == CompositeBehaviour.KEY) {
return (ride.ridingState as? CompositeState)?.activeBehaviour?.get() ?: cobblemonResource("no_key")
return (ride.ridingController?.context?.state as? CompositeState)?.activeBehaviour?.get() ?: cobblemonResource("no_key")
} else {
return key
}
@@ -44,10 +44,12 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawnablePosition>() {
if (minLureLevel != null) { // check for the lureLevel of the rod
val pokerodStack = spawnablePosition.rodStack
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(
spawnablePosition.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
pokerodStack
)
val lureLevel = spawnablePosition.enchantmentRegistry.getHolder(Enchantments.LURE).map {
EnchantmentHelper.getItemEnchantmentLevel(
it,
pokerodStack
)
}.orElse(0)
if (lureLevel < minLureLevel!!) {
return false
} else if (maxLureLevel != null && lureLevel > maxLureLevel!!) {
@@ -29,13 +29,13 @@ import net.minecraft.world.entity.Entity
* @author Hiroku, Plastered_Crab
* @since March 18th, 2025
*/
class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time: Int) -> Unit = {}) : SpawningInfluence {
open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time: Int, entity: PokemonEntity?) -> Unit = { _, _ -> }) : SpawningInfluence {
var usedTimes = 0
private fun markUsed() {
private fun markUsed(entity: PokemonEntity? = null) {
usedTimes++
onUsed(usedTimes)
onUsed(usedTimes, entity)
}
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
@@ -43,7 +43,7 @@ class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time:
val merged = SpawnBaitUtils.mergeEffects(effects)
merged.forEach { effect ->
if (Math.random() <= effect.chance) {
markUsed()
markUsed(entity)
Effects.getEffectFunction(effect.type)?.invoke(entity, effect)
}
}
@@ -17,7 +17,7 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.state.BlockState
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.prospecting.IncenseSweetDetector
import com.cobblemon.mod.common.api.spawning.prospecting.LureCakeDetector
import com.cobblemon.mod.common.api.spawning.prospecting.LurePokeSnackDetector
import com.cobblemon.mod.common.api.spawning.prospecting.SaccharineLogSlatheredDetector
/**
@@ -31,7 +31,7 @@ interface SpawningInfluenceDetector {
companion object {
@JvmStatic
val detectors = mutableSetOf<SpawningInfluenceDetector>(
LureCakeDetector,
LurePokeSnackDetector,
SaccharineLogSlatheredDetector,
IncenseSweetDetector
)
@@ -37,18 +37,19 @@ object IncenseSweetDetector : SpawningInfluenceDetector {
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val centerPos = input.getCenter().toBlockPos()
val sweetIncensePositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.INCENSE_SWEET_KEY) },
{ true },
centerPos,
searchRange,
PoiManager.Occupancy.ANY
).toList()
for (pos in sweetIncensePositions) {
val influence = SpatialSpawningZoneInfluence(pos, radius = RANGE.toFloat(), IncenseSweetInfluence(pos))
listOfInfluences.add(influence)
}
// TODO after 1.7
// val sweetIncensePositions = world.poiManager.findAll(
// { holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.INCENSE_SWEET_KEY) },
// { true },
// centerPos,
// searchRange,
// PoiManager.Occupancy.ANY
// ).toList()
//
// for (pos in sweetIncensePositions) {
// val influence = SpatialSpawningZoneInfluence(pos, radius = RANGE.toFloat(), IncenseSweetInfluence(pos))
// listOfInfluences.add(influence)
// }
return listOfInfluences
}
@@ -1,104 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpatialSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.UnconditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.block.LureCakeBlock
import com.cobblemon.mod.common.block.entity.LureCakeBlockEntity
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.state.BlockState
object LureCakeDetector : SpawningInfluenceDetector {
@JvmField
val RANGE: Int = 48
override fun detectFromInput(spawner: Spawner, input: SpawningZoneInput): MutableList<SpawningZoneInfluence> {
val world = input.world
val listOfInfluences = mutableListOf<SpawningZoneInfluence>()
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val lureCakePositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.LURE_CAKE_KEY) },
{ true },
input.getCenter().toBlockPos(),
searchRange,
PoiManager.Occupancy.ANY
).toList()
var highestLureTier = 0
for (lureCakePos in lureCakePositions) {
val blockState = world.getBlockState(lureCakePos)
if (blockState.block !is LureCakeBlock) continue
val blockEntity = world.getBlockEntity(lureCakePos) as? LureCakeBlockEntity ?: continue
val baitEffects = blockEntity.getBaitEffectsFromLureCake()
highestLureTier = maxOf(highestLureTier, baitEffects.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }.maxOfOrNull { it.value }?.toInt() ?: 0)
listOfInfluences.add(
SpatialSpawningZoneInfluence(
pos = lureCakePos,
radius = RANGE.toFloat(),
influence = SpawnBaitInfluence(
effects = baitEffects,
onUsed = { time ->
if (time == 1) {
tryBiteCake(world, lureCakePos)
}
}
)
)
)
}
if (highestLureTier > 0) {
listOfInfluences.add(UnconditionalSpawningZoneInfluence(influence = BucketNormalizingInfluence(tier = highestLureTier)))
}
return listOfInfluences
}
private fun tryBiteCake(world: ServerLevel, lureCakePos: BlockPos) {
if (world.isLoaded(lureCakePos)) {
val blockEntity = world.getBlockEntity(lureCakePos) as? LureCakeBlockEntity ?: return
blockEntity.bites++
if (blockEntity.bites >= blockEntity.maxBites) {
world.removeBlock(lureCakePos, false)
} else {
blockEntity.setChanged()
world.sendBlockUpdated(lureCakePos, blockEntity.blockState, blockEntity.blockState, 3)
}
}
}
override fun detectFromBlock(
world: ServerLevel,
pos: BlockPos,
blockState: BlockState
) = emptyList<SpawningZoneInfluence>()
}
@@ -0,0 +1,258 @@
/*
* 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.spawning.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpatialSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.UnconditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import com.cobblemon.mod.common.util.toVec3d
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import kotlin.math.abs
object LurePokeSnackDetector : SpawningInfluenceDetector {
@JvmField
val RANGE: Int = 48
@JvmField
val POKE_SNACK_CRUMBED_ASPECT = "poke_snack_crumbed"
override fun detectFromInput(spawner: Spawner, input: SpawningZoneInput): MutableList<SpawningZoneInfluence> {
val world = input.world
val listOfInfluences = mutableListOf<SpawningZoneInfluence>()
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val lurePokeSnackPositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.POKE_SNACK_KEY) },
{ true },
input.getCenter().toBlockPos(),
searchRange,
PoiManager.Occupancy.ANY
).toList()
var highestLureTier = 0
for (lurePokeSnackPos in lurePokeSnackPositions) {
val blockState = world.getBlockState(lurePokeSnackPos)
if (blockState.block !is PokeSnackBlock) continue
val blockEntity = world.getBlockEntity(lurePokeSnackPos) as? PokeSnackBlockEntity ?: continue
val baitEffects = blockEntity.getBaitEffects()
highestLureTier = maxOf(highestLureTier, baitEffects.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }.maxOfOrNull { it.value }?.toInt() ?: 0)
listOfInfluences.add(
SpatialSpawningZoneInfluence(
pos = lurePokeSnackPos,
radius = RANGE.toFloat(),
influence = SpawnBaitInfluence(
effects = baitEffects,
onUsed = { usedAmount, entity ->
entity?.let {
// Prevent triggering twice on same entity
if (usedAmount % 2 == 0) {
tryBite(world, lurePokeSnackPos)
moveAndApplyAspect(world, lurePokeSnackPos, entity)
}
}
}
)
)
)
}
if (highestLureTier > 0) {
listOfInfluences.add(UnconditionalSpawningZoneInfluence(influence = BucketNormalizingInfluence(tier = highestLureTier)))
}
return listOfInfluences
}
private fun tryBite(level: ServerLevel, blockPos: BlockPos) {
if (level.isLoaded(blockPos)) {
val blockState = level.getBlockState(blockPos)
val block = blockState.block as? PokeSnackBlock ?: return
block.eat(level, blockPos, blockState, null)
}
}
private fun moveAndApplyAspect(level: ServerLevel, blockPos: BlockPos, entity: PokemonEntity?) {
entity?.let {
entity.pokemon.forcedAspects += POKE_SNACK_CRUMBED_ASPECT
attemptSafeMove(level, blockPos, entity)
}
}
fun attemptSafeMove2(level: ServerLevel, startPos: BlockPos, entity: PokemonEntity, ) {
val inWater = entity.isInWater
// Random offset in a cardinal direction (north, south, east, west)
val randomDirection = listOf(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST).random()
val initialOffsetPos = startPos.relative(randomDirection)
// Variables to track the best (closest) safe position
var safePos: BlockPos? = null
var bestDistanceSq = Double.MAX_VALUE
val maxRadius = 10
// Search within a sphere around the initial offset position
for (dx in -maxRadius..maxRadius) {
for (dy in -maxRadius..maxRadius) {
for (dz in -maxRadius..maxRadius) {
val candidatePos = initialOffsetPos.offset(dx, dy, dz)
val distanceSq = startPos.distSqr(candidatePos)
// Check if within sphere radius
if (dx*dx + dy*dy + dz*dz <= maxRadius*maxRadius) {
// Check collision at candidate position
val currentBox = entity.boundingBox.move(
candidatePos.x - entity.x,
candidatePos.y - entity.y,
candidatePos.z - entity.z
)
if (level.noCollision(entity, currentBox)) {
val blockBelowPos = candidatePos.below()
val blockState = level.getBlockState(blockBelowPos)
val isWater = blockState.block == Blocks.WATER
if (inWater) {
if (isWater) {
if (distanceSq < bestDistanceSq) {
bestDistanceSq = distanceSq
safePos = candidatePos
}
}
} else {
if (!blockState.isAir) {
if (distanceSq < bestDistanceSq) {
bestDistanceSq = distanceSq
safePos = candidatePos
}
}
}
}
}
}
}
}
if (safePos != null) {
entity.moveTo(safePos.toVec3d())
PokeSnackBlockParticlesPacket(startPos, safePos).sendToPlayersAround(
startPos.x.toDouble(),
startPos.y.toDouble(),
startPos.z.toDouble(),
64.0,
level.dimension()
)
}
}
fun attemptSafeMove(level: ServerLevel, startPos: BlockPos, entity: PokemonEntity) {
val inWater = entity.isInWater
val maxDistance = 10 // maximum search radius
val maxPositions = 10 // maximum number of positions at the closest distance
var foundPositions: List<BlockPos> = emptyList()
// Search layer by layer, starting from the closest
for (distance in 1..maxDistance) {
val positionsAtThisDistance = mutableListOf<BlockPos>()
for (dx in -distance..distance) {
for (dy in -distance..distance) {
for (dz in -distance..distance) {
// Consider only positions on the surface of the cube at current distance
if (listOf(dx, dy, dz).count { abs(it) != 0 } == distance) {
val candidatePos = startPos.offset(dx, dy, dz)
if (!candidatePos.equals(startPos.relative(Direction.UP))) {
// Check collision
val currentBox = entity.boundingBox.move(
candidatePos.x - entity.x,
candidatePos.y - entity.y,
candidatePos.z - entity.z
)
if (level.noCollision(entity, currentBox)) {
val blockBelowPos = candidatePos.below()
val blockState = level.getBlockState(blockBelowPos)
val isWater = blockState.block == Blocks.WATER
if (inWater) {
if (isWater) {
positionsAtThisDistance.add(candidatePos)
}
} else {
if (!blockState.isAir) {
positionsAtThisDistance.add(candidatePos)
}
}
}
}
}
}
}
}
if (positionsAtThisDistance.isNotEmpty()) {
// Limit to 10 nearest positions (all at this distance)
foundPositions = if (positionsAtThisDistance.size > maxPositions) {
positionsAtThisDistance.shuffled().take(maxPositions)
} else {
positionsAtThisDistance
}
break // Found the nearest layer with safe positions, stop expanding
}
}
if (foundPositions.isNotEmpty()) {
val chosenPos = foundPositions.random()
val centerX = chosenPos.x + 0.5
val centerZ = chosenPos.z + 0.5
entity.moveTo(Vec3(centerX, chosenPos.y.toDouble(), centerZ))
PokeSnackBlockParticlesPacket(startPos, chosenPos).sendToPlayersAround(
startPos.x.toDouble(),
startPos.y.toDouble(),
startPos.z.toDouble(),
64.0,
level.dimension()
)
}
}
override fun detectFromBlock(level: ServerLevel, pos: BlockPos, blockState: BlockState) = emptyList<SpawningZoneInfluence>()
}
@@ -25,7 +25,6 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.server
import com.google.gson.JsonObject
import java.util.Collections
import java.util.UUID
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
@@ -259,9 +258,15 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
BattlePokemon.playerOwned(it)
}.also { if (healPokemon) it.effectedPokemon.heal() }
}.toMutableList()
if (leadingPokemon != null) {
Collections.rotate(result, result.size - this.indexOfFirst { it.uuid == leadingPokemon })
// reposition lead to front of the party
if (leadingPokemon != null && result.first().uuid != leadingPokemon) {
result.find { it.uuid == leadingPokemon }?.let { lead ->
result.remove(lead)
result.add(0, lead)
}
}
return result
}
fun clearParty() {
@@ -38,6 +38,8 @@ class Search(
filter = filter.substring(1)
}
filter = filter.lowercase(Locale.ROOT)
val pokemonFilter: PokemonFilter = when (filter) {
"holding", "helditem", "held_item" -> PokemonFilter { pokemon -> !pokemon.heldItem().isEmpty }
"fainted" -> PokemonFilter { pokemon -> pokemon.isFainted() }
@@ -45,14 +47,16 @@ class Search(
"mythical" -> PokemonFilter { pokemon -> pokemon.isMythical() }
"ultrabeast", "ultra_beast" -> PokemonFilter { pokemon -> pokemon.isUltraBeast() }
else -> {
val nameFilter = filter.lowercase(Locale.ROOT)
PokemonFilter { pokemon ->
// search by partial name
if (pokemon.species.resourceIdentifier.path.contains(nameFilter)) {
return@PokemonFilter true
if (filter.isEmpty()) {
true
} else {
val species = pokemon.species.translatedName.string.lowercase(Locale.ROOT)
val name = pokemon.getDisplayName().string.lowercase(Locale.ROOT)
val props = PokemonProperties.parse(filter)
species.contains(filter) || name.contains(filter) || (props.matches(pokemon) && props.asString().isNotEmpty())
}
// search by exactly match in different props
return@PokemonFilter PokemonProperties.parse(filter).matches(pokemon)
}
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
@@ -40,17 +39,13 @@ object BagItems : DataRegistry {
val bagItems = PrioritizedList<BagItemLike>()
internal val bagItemsScripts = mutableMapOf<String, String>() // itemId to JavaScript
init {
observable.subscribe {
Cobblemon.showdownThread.queue(ShowdownService::registerBagItems)
}
}
fun getConvertibleForStack(stack: ItemStack): BagItemLike? {
return bagItems.firstOrNull { it.getBagItem(stack) != null }
}
override fun reload(manager: ResourceManager) {
this.bagItemsScripts.clear()
ShowdownService.service.resetRegistryData("bagItem")
manager.listResources("bag_items") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
@@ -60,6 +55,8 @@ object BagItems : DataRegistry {
}
}
}
ShowdownService.service.sendRegistryData(bagItemsScripts, "bagItem")
this.observable.emit(this)
}
}
@@ -127,7 +127,8 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
// No PP or disabled or something
return false
}
val availableTargets = (gimmickMove?.target ?: move.target).targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val availableTargets = (if (gimmickID != null && validGimmickMove) gimmickMove.target else move.target)
.targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val pnx = targetPnx ?: return false // If the targets list is non-null then they need to have specified a target
val (_, targetPokemon) = activeBattlePokemon.actor.battle.getActorAndActiveSlotFromPNX(pnx)
@@ -16,6 +16,9 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
* @author Segfault Guy
* @since April 10th, 2023
*/
@Deprecated(message =
"This was designed before Doubles and Multi battle formats were introduce and has not been maintained since." +
"Accurate context for formats other than Singles is not guaranteed. This will be replaced in the near future.")
class ContextManager {
/** [BattleContext.Type] buckets containing [BattleContexts] of that type. */
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
import com.cobblemon.mod.common.battles.runner.socket.SocketShowdownService
import com.google.gson.JsonArray
import java.util.UUID
@@ -27,14 +28,13 @@ interface ShowdownService {
fun closeConnection()
fun startBattle(battle: PokemonBattle, messages: Array<String>)
fun send(battleId: UUID, messages: Array<String>)
fun getAbilityIds(): JsonArray
fun getMoves(): JsonArray
fun getItemIds(): JsonArray
fun registerSpecies()
fun registerBagItems()
fun getRegistryData(type: String): JsonArray
fun sendRegistryData(data: Map<String, String>, type: String)
fun sendRegistryEntry(data: String, type: String)
fun resetRegistryData(type: String)
fun resetAllRegistries()
fun indicateSpeciesInitialized() {}
companion object {
val service: ShowdownService by lazy { GraalShowdownService() }
}
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.battles.runner.graal
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.google.gson.Gson
@@ -33,6 +31,7 @@ import org.graalvm.polyglot.HostAccess.Export
import org.graalvm.polyglot.PolyglotAccess
import org.graalvm.polyglot.Value
import org.graalvm.polyglot.io.FileSystem
import kotlin.text.replace
/**
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
@@ -134,49 +133,38 @@ class GraalShowdownService : ShowdownService {
sendToShowdown(battleId, messages)
}
override fun getAbilityIds(): JsonArray {
val getCobbledAbilityIdsFn = context.getBindings("js").getMember("getCobbledAbilityIds")
val arrayResult = getCobbledAbilityIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
override fun getRegistryData(type: String): JsonArray {
val func = context.getBindings("js").getMember("getData")
val result = func.execute(type).asString()
return gson.fromJson(result, JsonArray::class.java)
}
override fun getMoves(): JsonArray {
val getCobbledMovesFn = context.getBindings("js").getMember("getCobbledMoves")
val arrayResult = getCobbledMovesFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun getItemIds(): JsonArray {
val getCobbledItemIdsFn = context.getBindings("js").getMember("getCobbledItemIds")
val arrayResult = getCobbledItemIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun registerSpecies() {
val receiveSpeciesDataFn = this.context.getBindings("js").getMember("receiveSpeciesData")
val jsArray = this.context.eval("js", "new Array();")
var index = 0L
PokemonSpecies.species.forEach { species ->
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownSpecies(species, null)))
species.forms.forEach { form ->
if (form != species.standardForm) {
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownSpecies(species, form)))
}
}
override fun sendRegistryData(data: Map<String, String>, type: String) {
val payload = data.entries.joinToString(prefix = "{", postfix = "}") { (k, v) ->
val newV = v.replace(Regex("[\r\n]+"), " ")
"\"$k\": $newV"
}
receiveSpeciesDataFn.execute(jsArray)
val func = this.context.getBindings("js").getMember("receiveData")
func.execute(payload, type)
}
override fun sendRegistryEntry(data: String, type: String) {
val payload = data.replace(Regex("[\r\n]+"), " ")
val func = this.context.getBindings("js").getMember("receiveData")
func.execute(payload, type)
}
override fun resetRegistryData(type: String) {
this.context.getBindings("js").getMember("resetData").execute(type)
}
override fun resetAllRegistries() {
this.context.getBindings("js").getMember("resetAll").execute()
}
override fun indicateSpeciesInitialized() {
val afterCobbledSpeciesInitFn = this.context.getBindings("js").getMember("afterCobbledSpeciesInit")
afterCobbledSpeciesInitFn.execute()
}
override fun registerBagItems() {
val receiveBagItemDataFn = this.context.getBindings("js").getMember("receiveBagItemData")
for ((itemId, js) in BagItems.bagItemsScripts) {
receiveBagItemDataFn.execute(itemId, js.replace("\n", " "))
}
val func = this.context.getBindings("js").getMember("afterSpeciesInit")
func.execute()
}
private fun sendToShowdown(battleId: UUID, messages: Array<String>) {
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.battles.runner.socket
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.pokemon.FormData
@@ -102,48 +101,44 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
ShowdownInterpreter.interpretMessage(battleId, message)
}
override fun getAbilityIds(): JsonArray {
writer.write(">getCobbledAbilityIds")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun sendRegistryData(data: Map<String, String>, type: String) {
data.forEach { (_, value) -> sendRegistryEntry(value, type) }
// The code for sending bulk data is commented out below, because:
// 1) while debugging it's useful to have individual entries, and socket is only used for debug atm
// 2) the species JSONs are way too big to be handled as one line on the JS side (maybe both sides?)
// this can eventually be remedied if socket is intended to be used for any actual servers/etc.
// alternatively, you could get around this limitation by just having ONLY PokemonSpecies send individually,
// but it's likely that with enough custom data for moves/abilities/etc. you'd run into the same issue
override fun getMoves(): JsonArray {
writer.write(">getCobbledMoves\n")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun getItemIds(): JsonArray {
writer.write(">getCobbledItemIds")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
private fun sendSpeciesData(species: Species, form: FormData?) {
writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n")
acknowledge()
}
private fun sendBagItem(itemId: String, js: String) {
writer.write(">receiveBagItemData $itemId $js")
acknowledge { Cobblemon.LOGGER.error("Failed to send bag item to Showdown: $itemId") }
}
override fun registerSpecies() {
writer.write(">resetSpeciesData\n")
acknowledge()
PokemonSpecies.species.forEach { species ->
sendSpeciesData(species, null)
species.forms.forEach { form ->
if (form != species.standardForm) {
sendSpeciesData(species, form)
}
}
/*val payload = data.entries.joinToString(prefix = "{", postfix = "}") { (k, v) ->
val newV = v.replace(Regex("[\r\n]+"), " ")
"\"$k\": $newV"
}
writer.write(">receiveData $type payload")
acknowledge { Cobblemon.LOGGER.error("Failed to send $type data to Showdown: $data") }*/
}
override fun sendRegistryEntry(data: String, type: String) {
val payload = data.replace(Regex("[\r\n]+"), " ")
writer.write(">receiveEntry $type $payload")
acknowledge { Cobblemon.LOGGER.error("Failed to send $type data to Showdown: $payload") }
}
override fun getRegistryData(type: String): JsonArray {
writer.write(">getData $type")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun resetRegistryData(type: String) {
writer.write(">resetData $type")
acknowledge()
}
override fun resetAllRegistries() {
writer.write(">resetAll")
acknowledge()
}
fun acknowledge(ifFails: () -> Unit = {}) {
@@ -155,14 +150,8 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
}
}
override fun registerBagItems() {
for ((itemId, js) in BagItems.bagItemsScripts) {
sendBagItem(itemId, js.replace("\n", " "))
}
}
override fun indicateSpeciesInitialized() {
writer.write(">afterCobbledSpeciesInit")
writer.write(">afterSpeciesInit")
acknowledge()
}
@@ -226,115 +226,99 @@ class ApricornBlock(settings: Properties, val apricorn: Apricorn) : HorizontalDi
const val MIN_AGE = 0
// North
// Stage 0
private val NORTH_STAGE_0 = Shapes.box(0.40625, 0.5, 0.0, 0.59375, 0.6875, 0.1875)
private val NORTH_CAP_STAGE_0 = Shapes.box(0.4375, 0.65625, 0.03125, 0.5625, 0.71875, 0.15625)
// Stage 1
private val NORTH_TOP_STAGE_1 = Block.box(7.0, 11.0, 0.5, 9.0, 11.5, 2.5)
private val NORTH_BODY_STAGE_1 = Block.box(6.5, 9.0, 0.0, 9.5, 11.0, 3.0)
private val NORTH_BOTTOM_STAGE_1 = Block.box(7.0, 8.5, 0.5, 9.0, 9.0, 2.5)
private val NORTH_STAGE_1 = Shapes.box(0.375, 0.40625, 0.0, 0.625, 0.65625, 0.25)
private val NORTH_CAP_STAGE_1 = Shapes.box(0.40625, 0.625, 0.03125, 0.59375, 0.6875, 0.21875)
// Stage 2
private val NORTH_TOP_STAGE_2 = Block.box(6.5, 10.5, 0.5, 9.5, 11.0, 3.5)
private val NORTH_BODY_STAGE_2 = Block.box(6.0, 7.5, 0.0, 10.0, 10.5, 4.0)
private val NORTH_BOTTOM_STAGE_2 = Block.box(6.5, 7.0, 0.5, 9.5, 7.5, 3.5)
private val NORTH_STAGE_2 = Shapes.box(0.34375, 0.296875, 0.0, 0.65625, 0.609375, 0.3125)
private val NORTH_CAP_STAGE_2 = Shapes.box(0.375, 0.59375, 0.03125, 0.625, 0.65625, 0.28125)
// Stage 3
private val NORTH_TOP_STAGE_3 = Block.box(6.0, 9.75, 0.5, 10.0, 10.5, 4.5)
private val NORTH_BODY_STAGE_3 = Block.box(5.5, 5.75, 0.0, 10.5, 9.75, 5.0)
private val NORTH_BOTTOM_STAGE_3 = Block.box(6.0, 5.0, 0.5, 10.0, 5.75, 4.5)
// Full fruit
private val NORTH_TOP_FRUIT = Block.box(6.0, 9.0, 1.0, 10.0, 10.0, 5.0)
private val NORTH_BODY_FRUIT = Block.box(5.0, 4.0, 0.0, 11.0, 9.0, 6.0)
private val NORTH_BOTTOM_FRUIT = Block.box(5.5, 3.0, 0.5, 10.5, 4.0, 5.5)
// Stage 3 - Full Fruit
private val NORTH_STAGE_3 = Shapes.box(0.3125, 0.1875, 0.0, 0.6875, 0.5625, 0.375)
private val NORTH_CAP_STAGE_3 = Shapes.box(0.375, 0.5625, 0.0625, 0.625, 0.625, 0.3125)
private val NORTH_AABB = arrayOf(
Shapes.or(NORTH_BODY_STAGE_1, NORTH_TOP_STAGE_1, NORTH_BOTTOM_STAGE_1),
Shapes.or(NORTH_BODY_STAGE_2, NORTH_TOP_STAGE_2, NORTH_BOTTOM_STAGE_2),
Shapes.or(NORTH_BODY_STAGE_3, NORTH_TOP_STAGE_3, NORTH_BOTTOM_STAGE_3),
Shapes.or(NORTH_BODY_FRUIT, NORTH_TOP_FRUIT, NORTH_BOTTOM_FRUIT)
Shapes.or(NORTH_STAGE_0, NORTH_CAP_STAGE_0),
Shapes.or(NORTH_STAGE_1, NORTH_CAP_STAGE_1),
Shapes.or(NORTH_STAGE_2, NORTH_CAP_STAGE_2),
Shapes.or(NORTH_STAGE_3, NORTH_CAP_STAGE_3)
)
// South
// Stage 0
private val SOUTH_STAGE_0 = Shapes.box(0.40625, 0.5, 0.8125, 0.59375, 0.6875, 1.0)
private val SOUTH_CAP_STAGE_0 = Shapes.box(0.4375, 0.65625, 0.84375, 0.5625, 0.71875, 0.96875)
// Stage 1
private val SOUTH_TOP_STAGE_1 = Block.box(7.0, 11.0, 13.5, 9.0, 11.5, 15.5)
private val SOUTH_BODY_STAGE_1 = Block.box(6.5, 9.0, 13.0, 9.5, 11.0, 16.0)
private val SOUTH_BOTTOM_STAGE_1 = Block.box(7.0, 8.5, 13.5, 9.0, 9.0, 15.5)
private val SOUTH_STAGE_1 = Shapes.box(0.375, 0.40625, 0.75, 0.625, 0.65625, 1.0)
private val SOUTH_CAP_STAGE_1 = Shapes.box(0.40625, 0.625, 0.78125, 0.59375, 0.6875, 0.96875)
// Stage 2
private val SOUTH_TOP_STAGE_2 = Block.box(6.5, 10.5, 12.5, 9.5, 11.0, 15.5)
private val SOUTH_BODY_STAGE_2 = Block.box(6.0, 7.5, 12.0, 10.0, 10.5, 16.0)
private val SOUTH_BOTTOM_STAGE_2 = Block.box(6.5, 7.0, 12.5, 9.5, 7.5, 15.5)
private val SOUTH_STAGE_2 = Shapes.box(0.34375, 0.296875, 0.6875, 0.65625, 0.609375, 1.0)
private val SOUTH_CAP_STAGE_2 = Shapes.box(0.375, 0.59375, 0.71875, 0.625, 0.65625, 0.96875)
// Stage 3
private val SOUTH_TOP_STAGE_3 = Block.box(6.0, 9.75, 11.5, 10.0, 10.5, 15.5)
private val SOUTH_BODY_STAGE_3 = Block.box(5.5, 5.75, 11.0, 10.5, 9.75, 16.0)
private val SOUTH_BOTTOM_STAGE_3 = Block.box(6.0, 5.0, 11.5, 10.0, 5.75, 15.5)
// Full fruit
private val SOUTH_TOP_FRUIT = Block.box(6.0, 9.0, 11.0, 10.0, 10.0, 15.0)
private val SOUTH_BODY_FRUIT = Block.box(5.0, 4.0, 10.0, 11.0, 9.0, 16.0)
private val SOUTH_BOTTOM_FRUIT = Block.box(5.5, 3.0, 10.5, 10.5, 4.0, 15.5)
// Stage 3 - Full Fruit
private val SOUTH_STAGE_3 = Shapes.box(0.3125, 0.1875, 0.625, 0.6875, 0.5625, 1.0)
private val SOUTH_CAP_STAGE_3 = Shapes.box(0.375, 0.5625, 0.6875, 0.625, 0.625, 0.9375)
private val SOUTH_AABB = arrayOf(
Shapes.or(SOUTH_BODY_STAGE_1, SOUTH_TOP_STAGE_1, SOUTH_BOTTOM_STAGE_1),
Shapes.or(SOUTH_BODY_STAGE_2, SOUTH_TOP_STAGE_2, SOUTH_BOTTOM_STAGE_2),
Shapes.or(SOUTH_BODY_STAGE_3, SOUTH_TOP_STAGE_3, SOUTH_BOTTOM_STAGE_3),
Shapes.or(SOUTH_BODY_FRUIT, SOUTH_TOP_FRUIT, SOUTH_BOTTOM_FRUIT)
Shapes.or(SOUTH_STAGE_0, SOUTH_CAP_STAGE_0),
Shapes.or(SOUTH_STAGE_1, SOUTH_CAP_STAGE_1),
Shapes.or(SOUTH_STAGE_2, SOUTH_CAP_STAGE_2),
Shapes.or(SOUTH_STAGE_3, SOUTH_CAP_STAGE_3)
)
// East
// Stage 0
private val EAST_STAGE_0 = Shapes.box(0.8125, 0.5, 0.40625, 1.0, 0.6875, 0.59375)
private val EAST_CAP_STAGE_0 = Shapes.box(0.84375, 0.65625, 0.4375, 0.96875, 0.71875, 0.5625)
// Stage 1
private val EAST_TOP_STAGE_1 = Block.box(13.5, 11.0, 7.0, 15.5, 11.5, 9.0)
private val EAST_BODY_STAGE_1 = Block.box(13.0, 9.0, 6.5, 16.0, 11.0, 9.5)
private val EAST_BOTTOM_STAGE_1 = Block.box(13.5, 8.5, 7.0, 15.5, 9.0, 9.0)
private val EAST_STAGE_1 = Shapes.box(0.75, 0.40625, 0.375, 1.0, 0.65625, 0.625)
private val EAST_CAP_STAGE_1 = Shapes.box(0.78125, 0.625, 0.40625, 0.96875, 0.6875, 0.59375)
// Stage 2
private val EAST_TOP_STAGE_2 = Block.box(12.5, 10.5, 6.5, 15.5, 11.0, 9.5)
private val EAST_BODY_STAGE_2 = Block.box(12.0, 7.5, 6.0, 16.0, 10.5, 10.0)
private val EAST_BOTTOM_STAGE_2 = Block.box(12.5, 7.0, 6.5, 15.5, 7.5, 9.5)
private val EAST_STAGE_2 = Shapes.box(0.6875, 0.296875, 0.34375, 1.0, 0.609375, 0.65625)
private val EAST_CAP_STAGE_2 = Shapes.box(0.71875, 0.59375, 0.375, 0.96875, 0.65625, 0.625)
// Stage 3
private val EAST_TOP_STAGE_3 = Block.box(11.5, 9.75, 6.0, 15.5, 10.5, 10.0)
private val EAST_BODY_STAGE_3 = Block.box(11.0, 5.75, 5.5, 16.0, 9.75, 10.5)
private val EAST_BOTTOM_STAGE_3 = Block.box(11.5, 5.0, 6.0, 15.5, 5.75, 10.0)
// Full fruit
private val EAST_TOP_FRUIT = Block.box(11.0, 9.0, 6.0, 15.0, 10.0, 10.0)
private val EAST_BODY_FRUIT = Block.box(10.0, 4.0, 5.0, 16.0, 9.0, 11.0)
private val EAST_BOTTOM_FRUIT = Block.box(10.5, 3.0, 5.5, 15.5, 4.0, 10.5)
// Stage 3 - Full Fruit
private val EAST_STAGE_3 = Shapes.box(0.625, 0.1875, 0.3125, 1.0, 0.5625, 0.6875)
private val EAST_CAP_STAGE_3 = Shapes.box(0.6875, 0.5625, 0.375, 0.9375, 0.625, 0.625)
private val EAST_AABB = arrayOf(
Shapes.or(EAST_BODY_STAGE_1, EAST_TOP_STAGE_1, EAST_BOTTOM_STAGE_1),
Shapes.or(EAST_BODY_STAGE_2, EAST_TOP_STAGE_2, EAST_BOTTOM_STAGE_2),
Shapes.or(EAST_BODY_STAGE_3, EAST_TOP_STAGE_3, EAST_BOTTOM_STAGE_3),
Shapes.or(EAST_BODY_FRUIT, EAST_TOP_FRUIT, EAST_BOTTOM_FRUIT)
Shapes.or(EAST_STAGE_0, EAST_CAP_STAGE_0),
Shapes.or(EAST_STAGE_1, EAST_CAP_STAGE_1),
Shapes.or(EAST_STAGE_2, EAST_CAP_STAGE_2),
Shapes.or(EAST_STAGE_3, EAST_CAP_STAGE_3)
)
// West
// Stage 0
private val WEST_STAGE_0 = Shapes.box(0.0, 0.5, 0.40625, 0.1875, 0.6875, 0.59375)
private val WEST_CAP_STAGE_0 = Shapes.box(0.03125, 0.65625, 0.4375, 0.15625, 0.71875, 0.5625)
// Stage 1
private val WEST_TOP_STAGE_1 = Block.box(0.5, 11.0, 7.0, 2.5, 11.5, 9.0)
private val WEST_BODY_STAGE_1 = Block.box(0.0, 9.0, 6.5, 3.0, 11.0, 9.5)
private val WEST_BOTTOM_STAGE_1 = Block.box(0.5, 8.5, 7.0, 2.5, 9.0, 9.0)
private val WEST_STAGE_1 = Shapes.box(0.0, 0.40625, 0.375, 0.25, 0.65625, 0.625)
private val WEST_CAP_STAGE_1 = Shapes.box(0.03125, 0.625, 0.40625, 0.21875, 0.6875, 0.59375)
// Stage 2
private val WEST_TOP_STAGE_2 = Block.box(0.5, 10.5, 6.5, 3.5, 11.0, 9.5)
private val WEST_BODY_STAGE_2 = Block.box(0.0, 7.5, 6.0, 4.0, 10.5, 10.0)
private val WEST_BOTTOM_STAGE_2 = Block.box(0.5, 7.0, 6.5, 3.5, 7.5, 9.5)
private val WEST_STAGE_2 = Shapes.box(0.0, 0.296875, 0.34375, 0.3125, 0.609375, 0.65625)
private val WEST_CAP_STAGE_2 = Shapes.box(0.03125, 0.59375, 0.375, 0.28125, 0.65625, 0.625)
// Stage 3
private val WEST_TOP_STAGE_3 = Block.box(0.5, 9.75, 6.0, 4.5, 10.5, 10.0)
private val WEST_BODY_STAGE_3 = Block.box(0.0, 5.75, 5.5, 5.0, 9.75, 10.5)
private val WEST_BOTTOM_STAGE_3 = Block.box(0.5, 5.0, 6.0, 4.5, 5.75, 10.0)
// Full fruit
private val WEST_TOP_FRUIT = Block.box(1.0, 9.0, 6.0, 5.0, 10.0, 10.0)
private val WEST_BODY_FRUIT = Block.box(0.0, 4.0, 5.0, 6.0, 9.0, 11.0)
private val WEST_BOTTOM_FRUIT = Block.box(0.5, 3.0, 5.5, 5.5, 4.0, 10.5)
// Stage 3 - Full Fruit
private val WEST_STAGE_3 = Shapes.box(0.0, 0.1875, 0.3125, 0.375, 0.5625, 0.6875)
private val WEST_CAP_STAGE_3 = Shapes.box(0.0625, 0.5625, 0.375, 0.3125, 0.625, 0.625)
private val WEST_AABB = arrayOf(
Shapes.or(WEST_BODY_STAGE_1, WEST_TOP_STAGE_1, WEST_BOTTOM_STAGE_1),
Shapes.or(WEST_BODY_STAGE_2, WEST_TOP_STAGE_2, WEST_BOTTOM_STAGE_2),
Shapes.or(WEST_BODY_STAGE_3, WEST_TOP_STAGE_3, WEST_BOTTOM_STAGE_3),
Shapes.or(WEST_BODY_FRUIT, WEST_TOP_FRUIT, WEST_BOTTOM_FRUIT)
Shapes.or(WEST_STAGE_0, WEST_CAP_STAGE_0),
Shapes.or(WEST_STAGE_1, WEST_CAP_STAGE_1),
Shapes.or(WEST_STAGE_2, WEST_CAP_STAGE_2),
Shapes.or(WEST_STAGE_3, WEST_CAP_STAGE_3)
)
}
@@ -57,7 +57,8 @@ class BugwortBlock(settings: Properties) : CropBlock(settings), BonemealableBloc
builder.add(IS_WILD)
}
override fun getBaseSeedId(): ItemLike = CobblemonItems.BUGWORT
// TODO after 1.7
// override fun getBaseSeedId(): ItemLike = CobblemonItems.BUGWORT
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AGE_TO_SHAPE[this.getAge(state)]
@@ -93,26 +94,27 @@ class BugwortBlock(settings: Properties) : CropBlock(settings), BonemealableBloc
return floor.`is`(BlockTags.DIRT) || floor.`is`(Blocks.FARMLAND)
}
override fun useWithoutItem(
state: BlockState,
world: Level,
pos: BlockPos,
player: Player,
hit: BlockHitResult
): InteractionResult {
if (state != null && world != null && pos != null) {
// if bugwort is at max age then revert age to 0 and drop items
if (player.getItemInHand(InteractionHand.MAIN_HAND).`is`(Items.BONE_MEAL) && !this.isMaxAge(state)) {
return InteractionResult.PASS
} else if (this.isMaxAge(state)) {
world.playSound(null, pos, CobblemonSounds.BERRY_HARVEST, SoundSource.BLOCKS, 0.4F, 1F)
world.setBlock(pos, state.setValue(this.ageProperty, (0).coerceAtMost(this.maxAge)), UPDATE_CLIENTS)
Block.popResource(world, pos, ItemStack(CobblemonItems.BUGWORT, Random.nextInt(2, 4)))
return InteractionResult.sidedSuccess(world.isClientSide)
}
}
return super.useWithoutItem(state, world, pos, player, hit)
}
// TODO after 1.7
// override fun useWithoutItem(
// state: BlockState,
// world: Level,
// pos: BlockPos,
// player: Player,
// hit: BlockHitResult
// ): InteractionResult {
// if (state != null && world != null && pos != null) {
// // if bugwort is at max age then revert age to 0 and drop items
// if (player.getItemInHand(InteractionHand.MAIN_HAND).`is`(Items.BONE_MEAL) && !this.isMaxAge(state)) {
// return InteractionResult.PASS
// } else if (this.isMaxAge(state)) {
// world.playSound(null, pos, CobblemonSounds.BERRY_HARVEST, SoundSource.BLOCKS, 0.4F, 1F)
// world.setBlock(pos, state.setValue(this.ageProperty, (0).coerceAtMost(this.maxAge)), UPDATE_CLIENTS)
// Block.popResource(world, pos, ItemStack(CobblemonItems.BUGWORT, Random.nextInt(2, 4)))
// return InteractionResult.sidedSuccess(world.isClientSide)
// }
// }
// return super.useWithoutItem(state, world, pos, player, hit)
// }
override fun getBonemealAgeIncrease(world: Level): Int = 1
@@ -1,168 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.block.entity.CakeBlockEntity
import com.cobblemon.mod.common.item.components.FoodColourComponent
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
abstract class CakeBlock(settings: Properties): BaseEntityBlock(settings) {
companion object {
val FACING: DirectionProperty = DirectionProperty.create("facing", Direction.Plane.HORIZONTAL)
val SHAPE_BY_BITE = arrayOf(
box(1.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(3.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(5.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(7.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(9.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(11.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(13.0, 0.0, 1.0, 15.0, 9.0, 15.0)
)
enum class Rotation {
NONE,
CLOCKWISE_90,
CLOCKWISE_180,
COUNTERCLOCKWISE_90
}
}
init {
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH))
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(FACING)
}
override fun setPlacedBy(level: Level, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
super.setPlacedBy(level, pos, state, placer, stack)
if (placer != null) {
level.setBlock(pos, state.setValue(FACING, placer.direction.opposite), 2)
}
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity
blockEntity?.initializeFromItemStack(stack)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity ?: return ItemStack.EMPTY
return blockEntity.toItemStack()
}
override fun getShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape {
val bites = getBites(level, pos)
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity
val maxBites = blockEntity?.maxBites ?: 6
val visualStage = ((bites * SHAPE_BY_BITE.size) / (maxBites + 1))
.coerceIn(0, SHAPE_BY_BITE.lastIndex)
val baseShape = SHAPE_BY_BITE[visualStage]
return when (state.getValue(FACING)) {
Direction.NORTH -> baseShape
Direction.SOUTH -> rotateShape(baseShape, Rotation.CLOCKWISE_180)
Direction.WEST -> rotateShape(baseShape, Rotation.COUNTERCLOCKWISE_90)
Direction.EAST -> rotateShape(baseShape, Rotation.CLOCKWISE_90)
else -> baseShape
}
}
private fun rotateShape(shape: VoxelShape, rotation: Rotation): VoxelShape {
var newShape = net.minecraft.world.phys.shapes.Shapes.empty()
for (box in shape.toAabbs()) {
val minX = box.minX
val minY = box.minY
val minZ = box.minZ
val maxX = box.maxX
val maxY = box.maxY
val maxZ = box.maxZ
val (rMinX, rMinZ, rMaxX, rMaxZ) = when (rotation) {
Rotation.NONE -> arrayOf(minX, minZ, maxX, maxZ)
Rotation.CLOCKWISE_90 -> arrayOf(minZ, 1.0 - maxX, maxZ, 1.0 - minX)
Rotation.CLOCKWISE_180 -> arrayOf(1.0 - maxX, 1.0 - maxZ, 1.0 - minX, 1.0 - minZ)
Rotation.COUNTERCLOCKWISE_90 -> arrayOf(1.0 - maxZ, minX, 1.0 - minZ, maxX)
}
val rotatedVoxel = net.minecraft.world.phys.shapes.Shapes.box(rMinX, minY, rMinZ, rMaxX, maxY, rMaxZ)
newShape = net.minecraft.world.phys.shapes.Shapes.or(newShape, rotatedVoxel)
}
return newShape
}
override fun spawnDestroyParticles(
level: Level,
player: Player,
pos: BlockPos,
state: BlockState
) {
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity
val color = blockEntity?.foodColourComponent?.colours?.lastOrNull()
val woolBlock = when (color) {
DyeColor.RED -> Blocks.RED_WOOL
DyeColor.ORANGE -> Blocks.ORANGE_WOOL
DyeColor.YELLOW -> Blocks.YELLOW_WOOL
DyeColor.LIME -> Blocks.LIME_WOOL
DyeColor.GREEN -> Blocks.GREEN_WOOL
DyeColor.CYAN -> Blocks.CYAN_WOOL
DyeColor.LIGHT_BLUE -> Blocks.LIGHT_BLUE_WOOL
DyeColor.BLUE -> Blocks.BLUE_WOOL
DyeColor.PURPLE -> Blocks.PURPLE_WOOL
DyeColor.MAGENTA -> Blocks.MAGENTA_WOOL
DyeColor.PINK -> Blocks.PINK_WOOL
else -> Blocks.WHITE_WOOL
}
level.levelEvent(player, 2001, pos, getId(woolBlock.defaultBlockState()))
}
fun getFoodColourComponent(level: BlockGetter, pos: BlockPos): FoodColourComponent? {
return (level.getBlockEntity(pos) as? CakeBlockEntity)?.foodColourComponent
}
fun setFoodColourComponent(level: BlockGetter, pos: BlockPos, foodColourComponent: FoodColourComponent) {
(level.getBlockEntity(pos) as? CakeBlockEntity)?.foodColourComponent = foodColourComponent
}
fun getBites(level: BlockGetter, pos: BlockPos): Int = (level.getBlockEntity(pos) as? CakeBlockEntity)?.bites ?: 0
fun setBites(level: BlockGetter, pos: BlockPos, bites: Int) {
(level.getBlockEntity(pos) as? CakeBlockEntity)?.bites = bites
}
}
@@ -1,178 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.CandlePokeCakeBlockEntity
import com.cobblemon.mod.common.client.render.block.CakeBlockEntityRenderer.Companion.TOP_LAYER_HEIGHT
import com.cobblemon.mod.common.util.toEquipmentSlot
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.AbstractCandleBlock
import net.minecraft.world.level.block.AbstractCandleBlock.extinguish
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks.*
import net.minecraft.world.level.block.CandleBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class CandlePokeCakeBlock(settings: Properties) : CakeBlock(settings) {
companion object {
val LIT = BlockStateProperties.LIT
val CANDLE_COLOR = IntegerProperty.create("candle_color", 0, MapColor.MATERIAL_COLORS.size - 1)
fun getCandleByMapColorId(id: Int): CandleBlock {
return when (id) {
MapColor.SAND.id -> CANDLE
MapColor.WOOL.id -> WHITE_CANDLE
MapColor.COLOR_ORANGE.id -> ORANGE_CANDLE
MapColor.COLOR_MAGENTA.id -> MAGENTA_CANDLE
MapColor.COLOR_LIGHT_BLUE.id -> LIGHT_BLUE_CANDLE
MapColor.COLOR_YELLOW.id -> YELLOW_CANDLE
MapColor.COLOR_LIGHT_GREEN.id -> LIME_CANDLE
MapColor.COLOR_PINK.id -> PINK_CANDLE
MapColor.COLOR_GRAY.id -> GRAY_CANDLE
MapColor.COLOR_LIGHT_GRAY.id -> LIGHT_GRAY_CANDLE
MapColor.COLOR_CYAN.id -> CYAN_CANDLE
MapColor.COLOR_PURPLE.id -> PURPLE_CANDLE
MapColor.COLOR_BLUE.id -> BLUE_CANDLE
MapColor.COLOR_BROWN.id -> BROWN_CANDLE
MapColor.COLOR_GREEN.id -> GREEN_CANDLE
MapColor.COLOR_RED.id -> RED_CANDLE
MapColor.COLOR_BLACK.id -> BLACK_CANDLE
else -> CANDLE
} as CandleBlock
}
val CANDLE_SHAPE: VoxelShape = box(7.0, 8.0, 7.0, 9.0, 15.0, 9.0)
val SHAPE: VoxelShape = Shapes.or(SHAPE_BY_BITE[0], CANDLE_SHAPE)
val PARTICLE_OFFSET = Vec3(0.5, 0.5 + TOP_LAYER_HEIGHT, 0.5)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(LIT, false)
.setValue(CANDLE_COLOR, MapColor.SAND.id)
)
}
override fun codec(): MapCodec<out BaseEntityBlock?> {
return simpleCodec(::CandlePokeCakeBlock)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return CandlePokeCakeBlockEntity(pos, state)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(LIT, CANDLE_COLOR)
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hitResult: BlockHitResult
): ItemInteractionResult {
return if (!stack.`is`(Items.FLINT_AND_STEEL) && !stack.`is`(Items.FIRE_CHARGE)) {
if (candleHit(hitResult) && stack.isEmpty && state.getValue(LIT) == true) {
extinguish(player, state, level, pos)
level.addParticle(ParticleTypes.SMOKE, PARTICLE_OFFSET.x + pos.x, pos.y + PARTICLE_OFFSET.y, pos.z + PARTICLE_OFFSET.z, 0.0, 0.1, 0.0)
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
super.useItemOn(stack, state, level, pos, player, hand, hitResult)
}
} else if (state.getValue(LIT) == false) {
level.playSound(player, pos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0f, level.random.nextFloat() * 0.4f + 0.8f)
level.setBlock(pos, state.setValue(BlockStateProperties.LIT, true), UPDATE_ALL_IMMEDIATE)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
player.getItemInHand(hand).hurtAndBreak(1, player, hand.toEquipmentSlot())
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
}
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
val oldCookingComponent = getFoodColourComponent(level, pos)
val candle = getCandleByMapColorId(state.getValue(CANDLE_COLOR))
val newBlockState = CobblemonBlocks.POKE_CAKE.defaultBlockState()
level.setBlockAndUpdate(pos, newBlockState)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
val pokeCakeBlock = newBlockState.block as PokeCakeBlock
oldCookingComponent?.let {
pokeCakeBlock.setFoodColourComponent(level, pos, it)
}
val interactionResult = pokeCakeBlock.eat(level, pos, player)
if (interactionResult.consumesAction()) {
popResource(level, pos, ItemStack(candle.asItem()))
}
return interactionResult
}
private fun candleHit(hit: BlockHitResult): Boolean {
return hit.location.y - hit.blockPos.y.toDouble() > TOP_LAYER_HEIGHT
}
override fun animateTick(
state: BlockState,
level: Level,
pos: BlockPos,
random: RandomSource
) {
if (state.getValue(LIT)) {
AbstractCandleBlock.addParticlesAndSound(level, Vec3(PARTICLE_OFFSET.x + pos.x, PARTICLE_OFFSET.y + pos.y, PARTICLE_OFFSET.z + pos.z), random)
}
}
override fun getShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape = SHAPE
}
@@ -10,24 +10,23 @@ package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.FluidTags
import net.minecraft.util.Mth
import net.minecraft.util.RandomSource
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.ItemLike
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.*
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.CropBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
@@ -116,7 +115,7 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
}
private fun canGrow(world: LevelReader, pos: BlockPos, state: BlockState): Boolean {
return !this.isMaxAge(state) && canSurvive(state, world, pos)
return !this.isMaxAge(state) && canSurvive(state, world, pos) && !(!canGrowInto(world, pos.above()) && state.getValue(AGE) >= AGE_AFTER_HARVEST)
}
private fun canGrowInto(world: LevelReader, pos: BlockPos): Boolean {
@@ -124,28 +123,6 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
return blockState.isAir || blockState.`is`(CobblemonBlocks.HEARTY_GRAINS)
}
override fun playerWillDestroy(world: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
if (!world.isClientSide) {
if (state.getValue(HALF) == DoubleBlockHalf.UPPER) {
val belowPos = pos.below()
val belowState = world.getBlockState(belowPos)
world.destroyBlock(pos, true) // Break the lower block without drops
world.setBlock(
belowPos,
belowState.setValue(AGE, AGE_AFTER_HARVEST), // Reset age to simulate regrowth preparation
UPDATE_CLIENTS
)
} else if (state.getValue(HALF) == DoubleBlockHalf.LOWER) {
val abovePos = pos.above()
val aboveState = world.getBlockState(abovePos)
if (aboveState.block is HeartyGrainsBlock && aboveState.getValue(HALF) == DoubleBlockHalf.UPPER) {
world.destroyBlock(abovePos, true) // Break the upper block without drops
}
}
}
return super.playerWillDestroy(world, pos, state, player)
}
override fun updateShape(
state: BlockState,
direction: Direction,
@@ -157,15 +134,12 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
if (!state.canSurvive(world, pos)) return Blocks.AIR.defaultBlockState()
val doubleBlockHalf = state.getValue(HALF)
return if (direction.axis !== Direction.Axis.Y || doubleBlockHalf == DoubleBlockHalf.LOWER != (direction == Direction.UP) ||
neighborState.`is`(this) && neighborState.getValue(HALF) != doubleBlockHalf) {
if (doubleBlockHalf == DoubleBlockHalf.LOWER && direction == Direction.DOWN && !state.canSurvive(world, pos))
Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
} else {
Blocks.AIR.defaultBlockState()
if (doubleBlockHalf == DoubleBlockHalf.LOWER && direction == Direction.UP) {
if (state.getValue(AGE) > AGE_AFTER_HARVEST && !neighborState.`is`(CobblemonBlocks.HEARTY_GRAINS)) return state.setValue(AGE, AGE_AFTER_HARVEST)
}
return state
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
@@ -211,7 +185,9 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
}
}
override fun getBonemealAgeIncrease(world: Level): Int = 1
override fun getBonemealAgeIncrease(level: Level): Int {
return Mth.nextInt(level.random, 1, 3)
}
override fun canSurvive(state: BlockState, world: LevelReader, pos: BlockPos): Boolean {
return when (state.getValue(HALF)) {
@@ -222,9 +198,9 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
// Check if bottom part is submerged in water
val fluidState = world.getFluidState(pos)
if (fluidState.`is`(FluidTags.WATER) && fluidState.isSource)
return floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_WATER_PLANTABLE) && canGrowInto(world, pos.above())
return floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_WATER_PLANTABLE)
floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_LAND_PLANTABLE) && canGrowInto(world, pos.above())
floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_LAND_PLANTABLE)
}
DoubleBlockHalf.UPPER -> {
getLowerHalf(world, pos, state) != null
@@ -248,4 +224,8 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
private fun isLower(state: BlockState): Boolean =
state.`is`(CobblemonBlocks.HEARTY_GRAINS) && state.getValue(HALF) == DoubleBlockHalf.LOWER
override fun getSoundType(state: BlockState): SoundType? {
return if (state.getValue(WATERLOGGED)) CobblemonSounds.HEARTY_GRAINS_WATER_SOUNDS else CobblemonSounds.HEARTY_GRAINS_SOUNDS
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.block.entity.LureCakeBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class LureCakeBlock(settings: Properties): CakeBlock(settings) {
override fun codec(): MapCodec<LureCakeBlock> {
return simpleCodec(::LureCakeBlock)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return LureCakeBlockEntity(pos, state)
}
}
@@ -1,134 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.CandlePokeCakeBlock.Companion.CANDLE_COLOR
import com.cobblemon.mod.common.block.entity.PokeCakeBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.stats.Stats
import net.minecraft.tags.ItemTags
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionHand.MAIN_HAND
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.CandleBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.phys.BlockHitResult
class PokeCakeBlock(settings: Properties): CakeBlock(settings) {
override fun codec(): MapCodec<out BaseEntityBlock?> {
return simpleCodec(::PokeCakeBlock)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? {
return PokeCakeBlockEntity(pos, state)
}
override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
stack: ItemStack
) {
super.setPlacedBy(level, pos, state, placer, stack)
val blockEntity = level.getBlockEntity(pos) as? PokeCakeBlockEntity
blockEntity?.initializeFromItemStack(stack)
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if (level.isClientSide) {
if (eat(level, pos, player).consumesAction()) {
return InteractionResult.SUCCESS
}
if (player.getItemInHand(MAIN_HAND).isEmpty) {
return InteractionResult.CONSUME
}
}
return eat(level, pos, player)
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hitResult: BlockHitResult
): ItemInteractionResult {
val item = stack.item
val block = state.block as? PokeCakeBlock
if (stack.`is`(ItemTags.CANDLES) && block?.getBites(level, pos) == 0) {
val candleBlock = byItem(item)
if (candleBlock is CandleBlock) {
stack.consume(1, player)
level.playSound(null, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0f, 1.0f)
val oldCookingComponent = block.getFoodColourComponent(level, pos)
val newBlockState = CobblemonBlocks.CANDLE_POKE_CAKE.defaultBlockState()
.setValue(CANDLE_COLOR, (item as BlockItem).block.defaultMapColor().id)
level.setBlockAndUpdate(pos, newBlockState)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
oldCookingComponent?.let {
(newBlockState.block as CakeBlock).setFoodColourComponent(level, pos, it)
}
player.awardStat(Stats.ITEM_USED[item])
return ItemInteractionResult.SUCCESS
}
}
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
}
fun eat(level: LevelAccessor, pos: BlockPos, player: Player): InteractionResult {
if (!player.canEat(false)) {
return InteractionResult.PASS
}
player.getFoodData().eat(2, 0.1F)
level.gameEvent(player, GameEvent.EAT, pos)
var bites = getBites(level, pos)
if (bites < 6) {
setBites(level, pos, bites + 1)
} else {
level.removeBlock(pos, false)
level.gameEvent(player, GameEvent.BLOCK_DESTROY, pos)
}
return InteractionResult.SUCCESS
}
}
@@ -0,0 +1,327 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.util.toEquipmentSlot
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.stats.Stats
import net.minecraft.tags.ItemTags
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.entity.projectile.Projectile
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class PokeSnackBlock(settings: Properties, val isLure: Boolean): BaseEntityBlock(settings) {
companion object {
val MAX_BITES = 8
val CAKE_HEIGHT = 0.4375
val CANDLE_PARTICLE_POSITION = Vec3(0.5, CAKE_HEIGHT + 0.5, 0.5)
val BITES: IntegerProperty = IntegerProperty.create("bites", 0, MAX_BITES)
val CANDLE: IntegerProperty = IntegerProperty.create("candle", 0, MapColor.MATERIAL_COLORS.size - 1)
val idToCandleBlock: Map<Int, Block> = mapOf(
MapColor.WOOL.id to Blocks.WHITE_CANDLE,
MapColor.COLOR_ORANGE.id to Blocks.ORANGE_CANDLE,
MapColor.COLOR_MAGENTA.id to Blocks.MAGENTA_CANDLE,
MapColor.COLOR_LIGHT_BLUE.id to Blocks.LIGHT_BLUE_CANDLE,
MapColor.COLOR_YELLOW.id to Blocks.YELLOW_CANDLE,
MapColor.COLOR_LIGHT_GREEN.id to Blocks.LIME_CANDLE,
MapColor.COLOR_PINK.id to Blocks.PINK_CANDLE,
MapColor.COLOR_GRAY.id to Blocks.GRAY_CANDLE,
MapColor.COLOR_LIGHT_GRAY.id to Blocks.LIGHT_GRAY_CANDLE,
MapColor.COLOR_CYAN.id to Blocks.CYAN_CANDLE,
MapColor.COLOR_PURPLE.id to Blocks.PURPLE_CANDLE,
MapColor.COLOR_BLUE.id to Blocks.BLUE_CANDLE,
MapColor.COLOR_BROWN.id to Blocks.BROWN_CANDLE,
MapColor.COLOR_GREEN.id to Blocks.GREEN_CANDLE,
MapColor.COLOR_RED.id to Blocks.RED_CANDLE,
MapColor.COLOR_BLACK.id to Blocks.BLACK_CANDLE
)
fun getCandleById(id: Int): Block = idToCandleBlock[id] ?: Blocks.CANDLE
fun getIdByCandle(candle: CandleBlock): Int = (idToCandleBlock.entries.associate { (id, block) -> block to id })[candle] ?: MapColor.SAND.id
private val CANDLE_SHAPE = Shapes.box(0.4375, CAKE_HEIGHT, 0.4375, 0.5625, 0.8125, 0.5625)
private val SHAPES = listOf(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, CAKE_HEIGHT, 0.9375),
Shapes.or(
Shapes.box(0.3125, 0.0, 0.0625, 0.9375, CAKE_HEIGHT, 0.9375),
Shapes.box(0.0625, 0.0, 0.3125, 0.9375, CAKE_HEIGHT, 0.9375)
),
Shapes.or(
Shapes.box(0.625, 0.0, 0.0625, 0.9375, CAKE_HEIGHT, 0.3125),
Shapes.box(0.0625, 0.0, 0.3125, 0.9375, CAKE_HEIGHT, 0.9375)
),
Shapes.box(0.0625, 0.0, 0.3125, 0.9375, CAKE_HEIGHT, 0.9375),
Shapes.or(
Shapes.box(0.6875, 0.0, 0.625, 0.9375, CAKE_HEIGHT, 0.9375),
Shapes.box(0.0625, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.9375)
),
Shapes.box(0.0625, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.9375),
Shapes.or(
Shapes.box(0.0625, 0.0, 0.6875, 0.375, CAKE_HEIGHT, 0.9375),
Shapes.box(0.0625, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.6875)
),
Shapes.box(0.0625, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.6875),
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.6875)
)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(BITES, 0)
.setValue(CANDLE, 0)
.setValue(BlockStateProperties.LIT, false)
)
}
override fun codec(): MapCodec<PokeSnackBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
PrimitiveCodec.BOOL.fieldOf("isLure").forGetter { it.isLure }
).apply(it, ::PokeSnackBlock) }
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(BITES, CANDLE, BlockStateProperties.LIT)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity = PokeSnackBlockEntity(pos, state)
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape? {
if (hasCandle(state)) {
return Shapes.or(SHAPES[state.getValue(BITES)], CANDLE_SHAPE)
}
return SHAPES[state.getValue(BITES)]
}
override fun getRenderShape(blockState: BlockState?) = RenderShape.MODEL
override fun setPlacedBy(level: Level, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
super.setPlacedBy(level, pos, state, placer, stack)
val blockEntity = level.getBlockEntity(pos) as? PokeSnackBlockEntity
blockEntity?.initializeFromItemStack(stack)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
val blockEntity = level.getBlockEntity(pos) as? PokeSnackBlockEntity ?: return ItemStack.EMPTY
return blockEntity.toItemStack()
}
override fun useWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hitResult: BlockHitResult): InteractionResult {
if (!isLure) {
if (!candleHit(hitResult)) {
if (level.isClientSide) {
if (playerEat(level, pos, state, player).consumesAction()) {
return InteractionResult.SUCCESS
}
if (player.getItemInHand(InteractionHand.MAIN_HAND).isEmpty) {
return InteractionResult.CONSUME
}
}
return playerEat(level, pos, state, player)
} else if (canBeLit(state) && player.mainHandItem.isEmpty && player.isCrouching()) {
player.setItemSlot(EquipmentSlot.MAINHAND, ItemStack(getCandleById(state.getValue(CANDLE))))
level.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, 0.5F, level.random.nextFloat() * 0.4F + 0.8F)
level.setBlockAndUpdate(pos, state.setValue(CANDLE, 0))
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
}
}
return super.useWithoutItem(state, level, pos, player, hitResult)
}
override fun useItemOn(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hand: InteractionHand, hitResult: BlockHitResult): ItemInteractionResult {
if (!isLure) {
if (stack.`is`(ItemTags.CANDLES) && !hasCandle(state)) {
val item = stack.getItem()
val candle = byItem(item)
if (candle is CandleBlock) {
stack.consume(1, player)
level.playSound(null as Player?, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0F, 1.0F)
level.setBlockAndUpdate(pos, state.setValue(CANDLE, getIdByCandle(candle)))
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
player.awardStat(Stats.ITEM_USED.get(item))
return ItemInteractionResult.SUCCESS
}
}
if (candleHit(hitResult) && hasCandle(state)) {
val isFlintAndSteel = stack.`is`(Items.FLINT_AND_STEEL)
if (canBeLit(state) && (isFlintAndSteel || stack.`is`(Items.FIRE_CHARGE))) {
setCandleLit(level, state, pos, true)
level.playSound(
player,
pos,
if (isFlintAndSteel) SoundEvents.FLINTANDSTEEL_USE else SoundEvents.FIRECHARGE_USE,
SoundSource.BLOCKS,
1.0F,
level.random.nextFloat() * 0.4F + 0.8F
)
if (isFlintAndSteel) stack.hurtAndBreak(1, player, hand.toEquipmentSlot())
else stack.consume(1, player)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
return ItemInteractionResult.sidedSuccess(level.isClientSide)
} else if (stack.isEmpty() && isCandleLit(state)) {
extinguishCandle(player, state, level, pos)
return ItemInteractionResult.sidedSuccess(level.isClientSide)
}
}
}
return super.useItemOn(stack, state, level, pos, player, hand, hitResult)
}
override fun animateTick(state: BlockState, level: Level, pos: BlockPos, random: RandomSource) {
if (isCandleLit(state)) {
AbstractCandleBlock.addParticlesAndSound(
level,
Vec3(
pos.x + CANDLE_PARTICLE_POSITION.x,
pos.y + CANDLE_PARTICLE_POSITION.y,
pos.z + CANDLE_PARTICLE_POSITION.z
),
random
)
}
}
override fun onProjectileHit(level: Level, state: BlockState, hit: BlockHitResult, projectile: Projectile) {
if (!level.isClientSide && projectile.isOnFire() && canBeLit(state)) {
setCandleLit(level, state, hit.getBlockPos(), true)
}
}
override fun playerWillDestroy(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player): BlockState {
dropCandle(level, blockPos, blockState, player)
return super.playerWillDestroy(level, blockPos, blockState, player)
}
private fun dropCandle(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player?) {
if (hasCandle(blockState) && !level.isClientSide && (!(player?.isCreative ?: false))) {
val itemEntity = ItemEntity(
level,
blockPos.x + 0.5,
blockPos.y + CAKE_HEIGHT,
blockPos.z + 0.5,
ItemStack(getCandleById(blockState.getValue(CANDLE)))
)
itemEntity.setDefaultPickUpDelay()
level.addFreshEntity(itemEntity)
}
}
private fun playerEat(level: Level, pos: BlockPos, state: BlockState, player: Player): InteractionResult {
if (!player.canEat(false)) {
return InteractionResult.PASS
} else {
player.awardStat(Stats.EAT_CAKE_SLICE)
player.getFoodData().eat(2, 0.1F)
level.gameEvent(player, GameEvent.EAT, pos)
eat(level, pos, state, player)
return InteractionResult.SUCCESS
}
}
fun eat(level: Level, pos: BlockPos, state: BlockState, player: Player?) {
val bites = state.getValue(BITES) as Int
if (bites < MAX_BITES) {
level.setBlock(pos, state.setValue(BITES, bites + 1) as BlockState, UPDATE_ALL)
} else {
dropCandle(level, pos, state, player)
level.removeBlock(pos, false)
level.removeBlockEntity(pos)
}
level.playSound(null, player?.blockPosition() ?: pos, SoundEvents.GENERIC_EAT, if (player != null) SoundSource.PLAYERS else SoundSource.NEUTRAL)
spawnEatParticles(level, pos)
player?.let {
level.gameEvent(it, GameEvent.BLOCK_DESTROY, pos)
}
}
private fun hasCandle(state: BlockState) = state.getValue(CANDLE) > 0
private fun isCandleLit(state: BlockState) = state.getValue(BlockStateProperties.LIT)
private fun canBeLit(state: BlockState) = hasCandle(state) && !isCandleLit(state)
private fun setCandleLit(level: LevelAccessor, state: BlockState, pos: BlockPos, lit: Boolean) {
level.setBlock(pos, state.setValue(BlockStateProperties.LIT, lit) as BlockState, UPDATE_ALL_IMMEDIATE)
}
private fun extinguishCandle(player: Player?, state: BlockState, level: LevelAccessor, pos: BlockPos) {
setCandleLit(level, state, pos, false)
level.addParticle(
ParticleTypes.SMOKE,
pos.getX().toDouble() + CANDLE_PARTICLE_POSITION.x(),
pos.getY().toDouble() + CANDLE_PARTICLE_POSITION.y(),
pos.getZ().toDouble() + CANDLE_PARTICLE_POSITION.z(),
0.0,
0.1,
0.0
)
level.playSound(null as Player?, pos, SoundEvents.CANDLE_EXTINGUISH, SoundSource.BLOCKS, 1.0F, 1.0F)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
}
private fun candleHit(hit: BlockHitResult): Boolean {
return hit.getLocation().y - hit.getBlockPos().getY().toDouble() > CAKE_HEIGHT
}
fun spawnEatParticles(level: Level, pos: BlockPos) {
val random = level.random
repeat(5) {
level.addParticle(
BlockParticleOption(ParticleTypes.BLOCK, CobblemonBlocks.POKE_SNACK.defaultBlockState()),
pos.x + random.nextDouble(),
pos.y + random.nextDouble() * CAKE_HEIGHT,
pos.z + random.nextDouble(),
0.0,
0.0,
0.0
)
}
}
}
@@ -11,11 +11,12 @@ package com.cobblemon.mod.common.block.campfirepot
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
import com.cobblemon.mod.common.item.CampfirePotItem
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
@@ -55,12 +56,16 @@ import org.jetbrains.annotations.Nullable
import net.minecraft.world.level.block.CampfireBlock as MCCampfireBlock
@Suppress("OVERRIDE_DEPRECATION")
class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWaterloggedBlock {
class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock(settings), SimpleWaterloggedBlock {
companion object {
val CODEC = simpleCodec(::CampfireBlock)
val CODEC: MapCodec<CampfireBlock> = RecordCodecBuilder.mapCodec {
it.group(
propertiesCodec(),
Codec.BOOL.fieldOf("isSoul").forGetter(CampfireBlock::isSoul)
).apply(it, ::CampfireBlock)
}
val ITEM_DIRECTION = DirectionProperty.create("item_facing")
val LIT = BlockStateProperties.LIT
var SOUL = BooleanProperty.create("soul")
val POWERED = BlockStateProperties.POWERED
private val campfireAABB = Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0)
@@ -78,7 +83,6 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(LIT, true)
.setValue(SOUL, false)
.setValue(ITEM_DIRECTION, Direction.NORTH)
.setValue(POWERED, false))
}
@@ -182,11 +186,9 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
blockEntity.setPotItem(ItemStack.EMPTY)
level.playSoundServer(blockPos.bottomCenter, CobblemonSounds.CAMPFIRE_POT_RETRIEVE)
blockEntity.setItem(PREVIEW_ITEM_SLOT, ItemStack.EMPTY)
Containers.dropContents(level, blockPos, blockEntity)
val facing = blockState.getValue(FACING)
val isSoul = blockState.getValue(SOUL)
blockEntity.setRemoved()
val newBlockState = if (isSoul) Blocks.SOUL_CAMPFIRE.defaultBlockState().setValue(FACING, facing)
@@ -201,7 +203,7 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, ITEM_DIRECTION, LIT, SOUL, POWERED)
builder.add(FACING, ITEM_DIRECTION, LIT, POWERED)
}
override fun updateShape(
@@ -271,7 +273,7 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
return if (state.getValue(SOUL)) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
return if (isSoul) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
}
override fun neighborChanged(
@@ -36,6 +36,7 @@ import net.minecraft.world.item.crafting.RecipeHolder
import net.minecraft.world.item.crafting.RecipeManager
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
import java.util.*
class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, ContainerListener {
private val player: Player
@@ -46,6 +47,8 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
val containerData: ContainerData
private val recipeType: RecipeType<CookingPotRecipe> = CobblemonRecipeTypes.COOKING_POT_COOKING
private val quickCheck = RecipeManager.createCheck(CobblemonRecipeTypes.COOKING_POT_COOKING)
var currentActiveRecipe: RecipeHolder<CookingPotRecipeBase>? = null
var previewItem = ItemStack.EMPTY
constructor(containerId: Int, playerInventory: Inventory) : super(CobblemonMenuType.COOKING_POT, containerId) {
this.playerInventory = playerInventory
@@ -59,6 +62,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
this.player = playerInventory.player
this.level = playerInventory.player.level()
initializeSlots(playerInventory)
this.addSlotListener(this)
}
constructor(containerId: Int, playerInventory: Inventory, container: CraftingContainer, containerData: ContainerData) : super(
@@ -91,9 +95,6 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
addSlot(SeasoningSlot(this.container, slotIndex, 110 + index * 18, 18))
}
addSlot(CookingPotPreviewSlot(this.container,
CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT, resultSlotX, resultSlotY))
for ((index, _) in CampfireBlockEntity.Companion.PLAYER_INVENTORY_SLOTS.withIndex()) {
val i = index / CampfireBlockEntity.Companion.PLAYER_INVENTORY_WIDTH
val j = index % CampfireBlockEntity.Companion.PLAYER_INVENTORY_WIDTH
@@ -148,14 +149,40 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun recipeMatches(recipe: RecipeHolder<CookingPotRecipeBase>): Boolean {
val craftInput = CraftingInput.of(3,3, container.items.subList(1,10))
val recipeValue = recipe.value()
return if (recipeValue is CookingPotRecipeBase) {
recipeValue.matches(container.asCraftInput(), level)
recipeValue.matches(craftInput, level)
} else {
false
}
}
private fun recalculateRecipe() {
val craftInput = CraftingInput.of(3,3, container.items.subList(1,10))
fun <T : CookingPotRecipeBase> fetchRecipe(
recipeType: RecipeType<T>
): Optional<RecipeHolder<CookingPotRecipeBase>> {
val optional = level.recipeManager.getRecipeFor(recipeType, craftInput, level)
@Suppress("UNCHECKED_CAST")
return optional.map { it as RecipeHolder<CookingPotRecipeBase> }
}
val recipe = fetchRecipe(CobblemonRecipeTypes.COOKING_POT_COOKING)
.orElseGet { fetchRecipe(CobblemonRecipeTypes.COOKING_POT_SHAPELESS).orElse(null) }
currentActiveRecipe = recipe
if (recipe != null) {
previewItem = recipe.value.assemble(craftInput, level.registryAccess())
recipe.value.applySeasoning(
previewItem,
container.items.subList(CampfireBlockEntity.SEASONING_SLOTS.first,
CampfireBlockEntity.SEASONING_SLOTS.last + 1)
.filterNotNull()
.filter { !it.isEmpty })
} else previewItem = ItemStack.EMPTY
}
override fun getResultSlotIndex(): Int {
return CampfireBlockEntity.Companion.RESULT_SLOT
}
@@ -187,7 +214,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun shouldMoveToInventory(slotIndex: Int): Boolean {
return slotIndex != CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT && !CampfireBlockEntity.SEASONING_SLOTS.contains(slotIndex)
return !CampfireBlockEntity.SEASONING_SLOTS.contains(slotIndex)
}
override fun quickMoveStack(
@@ -209,8 +236,8 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
slot.onQuickCraft(slotItemStack, itemStack);
} else if (index in CampfireBlockEntity.Companion.PLAYER_INVENTORY_SLOTS || index in CampfireBlockEntity.Companion.PLAYER_HOTBAR_SLOTS) {
if (Seasonings.isSeasoning(slotItemStack)) {
if (!this.moveItemStackTo(slotItemStack, CampfireBlockEntity.Companion.SEASONING_SLOTS.first, CampfireBlockEntity.Companion.SEASONING_SLOTS.last + 1, false) &&
!this.moveItemStackTo(slotItemStack, CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS.first, CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS.last + 1, false)
if (!this.moveItemStackTo(slotItemStack, CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS.first, CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS.last + 1, false) &&
!this.moveItemStackTo(slotItemStack, CampfireBlockEntity.Companion.SEASONING_SLOTS.first, CampfireBlockEntity.Companion.SEASONING_SLOTS.last + 1, false)
) {
return ItemStack.EMPTY
}
@@ -244,6 +271,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun slotChanged(containerToSend: AbstractContainerMenu, dataSlotIndex: Int, stack: ItemStack) {
if (CampfireBlockEntity.CRAFTING_GRID_SLOTS.contains(dataSlotIndex) || CampfireBlockEntity.SEASONING_SLOTS.contains(dataSlotIndex)) recalculateRecipe()
broadcastChanges()
}
@@ -1,31 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.campfirepot
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.RESULT_SLOT
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
class CookingPotPreviewSlot(
container: CraftingContainer,
index: Int,
x: Int,
y: Int
) : Slot(container, index, x, y) {
override fun isActive(): Boolean {
return container.getItem(RESULT_SLOT).isEmpty
}
override fun isFake(): Boolean = true
override fun mayPlace(stack: ItemStack): Boolean = false
override fun mayPickup(player: Player): Boolean = false
override fun isHighlightable() = false
}
@@ -31,12 +31,11 @@ class CookingPotResultSlot(
val menu = player.containerMenu
if (menu is CookingPotMenu) {
menu.broadcastChanges()
player.playNotifySound(CobblemonSounds.CAMPFIRE_POT_USE, SoundSource.MASTER, 1.0f, 1.0f)
player.playNotifySound(CobblemonSounds.CAMPFIRE_POT_TAKE_ITEM, SoundSource.MASTER, 1.0f, 1.0f)
}
super.onTake(player, stack)
}
override fun mayPlace(stack: ItemStack): Boolean = false
override fun isActive(): Boolean = hasItem()
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.cooking.getColourMixFromSeasonings
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.item.components.PotComponent
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
@@ -39,11 +40,12 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
import net.minecraft.world.ContainerHelper
import net.minecraft.world.WorldlyContainer
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.StackedContents
import net.minecraft.world.inventory.*
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeHolder
import net.minecraft.world.item.crafting.RecipeManager
@@ -51,6 +53,7 @@ import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity
import net.minecraft.world.level.block.entity.HopperBlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.phys.Vec3
@@ -67,11 +70,10 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
const val RESULT_SLOT = 0
val CRAFTING_GRID_SLOTS = 1..9
val SEASONING_SLOTS = 10..12
const val PREVIEW_ITEM_SLOT = 13
const val ITEMS_SIZE = 14
const val ITEMS_SIZE = 13
val PLAYER_INVENTORY_SLOTS = 14..40
val PLAYER_HOTBAR_SLOTS = 41..49
val PLAYER_INVENTORY_SLOTS = 13..39
val PLAYER_HOTBAR_SLOTS = 40..48
const val CRAFTING_GRID_WIDTH = 3
const val PLAYER_INVENTORY_WIDTH = 9
@@ -89,15 +91,21 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (!level.isClientSide) return
val isLit = campfireBlockEntity.dataAccess.get(COOKING_PROGRESS_INDEX) > 0
val isSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.runningSound.location)
val isRunningSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.runningSound.location)
val isAmbientSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.ambientSound.location)
val containsItems = campfireBlockEntity.getSeasonings().isNotEmpty() || campfireBlockEntity.getIngredients().isNotEmpty()
if (isLit && !isSoundActive) {
BlockEntitySoundTracker.play(
pos,
CancellableSoundInstance(campfireBlockEntity.runningSound, pos, true, 0.8f, 1.0f)
)
} else if (!isLit && isSoundActive) {
if (containsItems) {
if (isLit) {
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.ambientSound.location)
if (!isRunningSoundActive) BlockEntitySoundTracker.play(pos, CancellableSoundInstance(campfireBlockEntity.runningSound, pos, true, 1.0f, 1.0f))
} else {
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.runningSound.location)
if (!isAmbientSoundActive) BlockEntitySoundTracker.play(pos, CancellableSoundInstance(campfireBlockEntity.ambientSound, pos, true, 1.0f, 1.0f))
}
} else {
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.runningSound.location)
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.ambientSound.location)
}
campfireBlockEntity.brothColor =
@@ -149,7 +157,6 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (optionalRecipe == null) {
campfireBlockEntity.cookingProgress = 0
campfireBlockEntity.setItem(PREVIEW_ITEM_SLOT, ItemStack.EMPTY)
return
}
@@ -160,8 +167,6 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
recipe.applySeasoning(cookedItem, campfireBlockEntity.getSeasonings())
campfireBlockEntity.setItem(PREVIEW_ITEM_SLOT, cookedItem)
if (campfireBlockEntity.isLidOpen) {
campfireBlockEntity.cookingProgress = 0
return
@@ -171,7 +176,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (!ItemStack.isSameItemSameComponents(
resultSlotItem,
cookedItem
) || resultSlotItem.count >= resultSlotItem.maxStackSize
) || resultSlotItem.count + cookedItem.count > resultSlotItem.maxStackSize
) {
campfireBlockEntity.cookingProgress = 0
return
@@ -191,11 +196,11 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
resultSlotItem.grow(cookedItem.count)
}
campfireBlockEntity.consumeCraftingIngredients(recipe)
campfireBlockEntity.consumeCraftingIngredients(recipe, level, pos, state, campfireBlockEntity)
level.playSoundServer(
position = pos.bottomCenter,
sound = CobblemonSounds.CAMPFIRE_POT_CRAFT,
sound = CobblemonSounds.CAMPFIRE_POT_COOK,
)
setChanged(level, pos, state);
@@ -204,7 +209,8 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
}
}
private val runningSound = CobblemonSounds.CAMPFIRE_POT_COOK
private val runningSound = CobblemonSounds.CAMPFIRE_POT_ACTIVE
private val ambientSound = CobblemonSounds.CAMPFIRE_POT_AMBIENT
private var cookingProgress: Int = 0
private var cookingTotalTime: Int = COOKING_TOTAL_TIME
private var isLidOpen: Boolean = true
@@ -270,32 +276,17 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
return particleStorm
}
fun consumeCraftingIngredients(recipe: CookingPotRecipeBase) {
fun consumeCraftingIngredients(recipe: CookingPotRecipeBase, level: Level, pos: BlockPos, state: BlockState, campfireBlockEntity: CampfireBlockEntity) {
val remainderItems = mutableMapOf<Item, Int>() //This is so we don't spawn multiple entities for buckets
fun consumeItem(slot: Int) {
val itemInSlot = getItem(slot)
if (!itemInSlot.isEmpty) {
when (itemInSlot.item) {
Items.LAVA_BUCKET, Items.WATER_BUCKET, Items.MILK_BUCKET -> {
// Replace with empty bucket
setItem(slot, ItemStack(Items.BUCKET))
}
Items.HONEY_BOTTLE -> {
// TODO: Currently eats the empty bottles until the honey bottle stack is empty, replace with better system later.
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) {
setItem(slot, ItemStack.EMPTY)
}
}
else -> {
// Decrease the stack size by 1
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) {
setItem(slot, ItemStack.EMPTY) // Clear the slot if empty
}
}
if (itemInSlot.item.hasCraftingRemainingItem()) {
remainderItems[itemInSlot.item.craftingRemainingItem!!] = (remainderItems[itemInSlot.item.craftingRemainingItem!!] ?: 0) + 1
}
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) setItem(slot, ItemStack.EMPTY)
}
}
@@ -305,6 +296,24 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
for (i in SEASONING_SLOTS.first..SEASONING_SLOTS.last) {
if (recipe.seasoningProcessors.any { it.consumesItem(getItem(i)) }) consumeItem(i)
}
val direction = state.getValue(CampfireBlock.ITEM_DIRECTION)
val container = HopperBlockEntity.getContainerAt(level, pos.relative(direction))
for (remainder in remainderItems) {
var remainderItem = ItemStack(remainder.key, remainder.value)
if (container != null) {
remainderItem = HopperBlockEntity.addItem(campfireBlockEntity, container, remainderItem, direction.opposite)
}
if (!remainderItem.isEmpty) {
val spawnPos = Vec3.atCenterOf(pos).relative(direction, 0.7)
val itemEntity = ItemEntity(level, spawnPos.x, spawnPos.y, spawnPos.z, remainderItem)
itemEntity.setDeltaMovement(direction.stepX * 0.05, 0.0, direction.stepZ * 0.05)
level.addFreshEntity(itemEntity) //Wanted to use default dispenser behavior but its too much speed
}
}
}
fun getSeasonings(): List<ItemStack> =
@@ -429,6 +438,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (level?.isClientSide == true) {
BlockEntitySoundTracker.stop(blockPos, runningSound.location)
BlockEntitySoundTracker.stop(blockPos, ambientSound.location)
}
}
@@ -1,18 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
class CandlePokeCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.CANDLE_POKE_CAKE, pos, state)
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.fishing.SpawnBaitEffects
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
class LureCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.LURE_CAKE, pos, state) {
override val maxBites: Int
get() = 20
/**
* Combine all the [SpawnBait.Effect] values from the [baitEffectsComponent] data.
*/
fun getBaitEffectsFromLureCake(): List<SpawnBait.Effect> {
return baitEffectsComponent?.effects?.mapNotNull(SpawnBaitEffects::getFromIdentifier)?.flatMap { it.effects }.orEmpty()
}
}
@@ -1,18 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
class PokeCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.POKE_CAKE, pos, state)
@@ -8,130 +8,127 @@
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.cooking.getColourMixFromFlavours
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.fishing.SpawnBait.Effect
import com.cobblemon.mod.common.api.fishing.SpawnBaitEffects
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.item.components.BaitEffectsComponent
import com.cobblemon.mod.common.item.components.FlavourComponent
import com.cobblemon.mod.common.item.components.FoodColourComponent
import com.cobblemon.mod.common.item.components.IngredientComponent
import com.cobblemon.mod.common.util.DataKeys
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.nbt.*
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import kotlin.collections.orEmpty
open class CakeBlockEntity(
type: BlockEntityType<*>,
pos: BlockPos,
state: BlockState
) : BlockEntity(type, pos, state) {
open val maxBites: Int
get() = 6
open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) : TintBlockEntity(CobblemonBlockEntities.POKE_SNACK, pos, state) {
var flavourComponent: FlavourComponent? = null
var baitEffectsComponent: BaitEffectsComponent? = null
var foodColourComponent: FoodColourComponent? = null
var bites: Int = 0
var ingredientComponent: IngredientComponent? = null
fun initializeFromItemStack(itemStack: ItemStack) {
flavourComponent = itemStack.get(CobblemonItemComponents.FLAVOUR)
baitEffectsComponent = itemStack.get(CobblemonItemComponents.BAIT_EFFECTS)
foodColourComponent = itemStack.get(CobblemonItemComponents.FOOD_COLOUR)
}
ingredientComponent = itemStack.get(CobblemonItemComponents.INGREDIENT)
if (isLure()) baitEffectsComponent = itemStack.get(CobblemonItemComponents.BAIT_EFFECTS)
override fun saveAdditional(
tag: CompoundTag,
registries: HolderLookup.Provider
) {
super.saveAdditional(tag, registries)
tag.putInt(DataKeys.CAKE_BITES, bites)
flavourComponent?.let { component ->
CobblemonItemComponents.FLAVOUR.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.CAKE_FLAVOUR, encodedTag)
}
}
baitEffectsComponent?.let { component ->
CobblemonItemComponents.BAIT_EFFECTS.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.CAKE_BAIT_EFFECTS, encodedTag)
}
}
foodColourComponent?.let { component ->
CobblemonItemComponents.FOOD_COLOUR.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.CAKE_FOOD_COLOUR, encodedTag)
}
flavourComponent?.let {
getColourMixFromFlavours(it.getDominantFlavours())?.let {
setTint(it)
}
}
}
override fun loadAdditional(
tag: CompoundTag,
registries: HolderLookup.Provider
) {
super.loadAdditional(tag, registries)
bites = tag.getInt(DataKeys.CAKE_BITES)
if (tag.contains(DataKeys.CAKE_FLAVOUR)) {
CobblemonItemComponents.FLAVOUR.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_FLAVOUR))
?.result()
?.ifPresent { component ->
flavourComponent = component
}
fun isLure(): Boolean {
val block = blockState.block
if (block is PokeSnackBlock) {
return block.isLure
}
if (tag.contains(DataKeys.CAKE_BAIT_EFFECTS)) {
CobblemonItemComponents.BAIT_EFFECTS.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_BAIT_EFFECTS))
?.result()
?.ifPresent { component ->
baitEffectsComponent = component
}
}
if (tag.contains(DataKeys.CAKE_FOOD_COLOUR)) {
CobblemonItemComponents.FOOD_COLOUR.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_FOOD_COLOUR))
?.result()
?.ifPresent { component ->
foodColourComponent = component
}
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener?>? {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registries: HolderLookup.Provider): CompoundTag {
val tag = CompoundTag()
saveAdditional(tag, registries)
return tag
return false
}
fun toItemStack(): ItemStack {
val stack = ItemStack(this.blockState.block)
if (stack.item == CobblemonItems.LURE_CAKE && baitEffectsComponent != null) {
if (isLure() && baitEffectsComponent != null) {
stack.set(CobblemonItemComponents.BAIT_EFFECTS, baitEffectsComponent)
}
if (flavourComponent != null) {
stack.set(CobblemonItemComponents.FLAVOUR, flavourComponent)
}
if (foodColourComponent != null) {
stack.set(CobblemonItemComponents.FOOD_COLOUR, foodColourComponent)
}
return stack
}
}
/**
* Combine all the [SpawnBait.Effect] values from the [baitEffectsComponent] data.
*/
fun getBaitEffects(): List<Effect> {
return baitEffectsComponent?.effects?.mapNotNull(SpawnBaitEffects::getFromIdentifier)?.flatMap { it.effects }.orEmpty()
}
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
flavourComponent?.let { component ->
CobblemonItemComponents.FLAVOUR.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.FLAVOUR, encodedTag)
}
}
baitEffectsComponent?.let { component ->
CobblemonItemComponents.BAIT_EFFECTS.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.BAIT_EFFECTS, encodedTag)
}
}
ingredientComponent?.let { component ->
CobblemonItemComponents.INGREDIENT.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.INGREDIENTS, encodedTag)
}
}
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
if (tag.contains(DataKeys.FLAVOUR)) {
CobblemonItemComponents.FLAVOUR.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.FLAVOUR))
?.result()
?.ifPresent { component ->
flavourComponent = component
}
}
if (tag.contains(DataKeys.BAIT_EFFECTS)) {
CobblemonItemComponents.BAIT_EFFECTS.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.BAIT_EFFECTS))
?.result()
?.ifPresent { component ->
baitEffectsComponent = component
}
}
if (tag.contains(DataKeys.INGREDIENTS)) {
CobblemonItemComponents.INGREDIENT.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.INGREDIENTS))
?.result()
?.ifPresent { component ->
ingredientComponent = component
}
}
}
}
@@ -13,4 +13,6 @@ import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class SweetIncenseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.INCENSE_SWEET, pos, state)
class SweetIncenseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(
null // CobblemonBlockEntities.INCENSE_SWEET // TODO after 1.7
, pos, state)
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.IntTag
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
open class TintBlockEntity(type: BlockEntityType<*>?, blockPos: BlockPos, blockState: BlockState) : BlockEntity(type, blockPos, blockState) {
companion object {
const val TINT = "tint"
}
var tint: Int? = null
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
tint?.let { tag.put(TINT, IntTag.valueOf(it)) }
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
if (tag.contains(TINT)) tint = tag.getInt(TINT)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
return this.saveWithoutMetadata(registryLookup)
}
fun getTint() = tint ?: 0xFFFFFF
fun setTint(tintValue: Int) {
level?.let {
tint = tintValue
setChanged()
it.blockEntityChanged(blockPos)
it.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_ALL)
}
}
}
@@ -20,19 +20,20 @@ import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.entity.TintBlockEntity
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.block.CandlePokeCakeBlockEntityRenderer
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
import com.cobblemon.mod.common.client.render.DeferredRenderer
import com.cobblemon.mod.common.client.render.block.*
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.color.AprijuiceItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeBaitItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeSnackItemColorProvider
import com.cobblemon.mod.common.client.render.color.PonigiriItemColorProvider
import com.cobblemon.mod.common.client.render.color.SinisterTeaItemColorProvider
import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
@@ -59,6 +60,8 @@ import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.isLookingAt
import net.minecraft.client.Minecraft
import net.minecraft.client.color.block.BlockColor
import net.minecraft.client.color.item.ItemColor
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.MenuScreens
import net.minecraft.client.gui.screens.Screen
@@ -124,7 +127,7 @@ object CobblemonClient {
this.registerBlockEntityRenderers()
registerBlockRenderTypes()
//registerColors()
registerColors()
registerFlywheelRenderers()
this.registerEntityRenderers()
this.registerItemColors()
@@ -196,16 +199,16 @@ object CobblemonClient {
// .apply()
}
/*
fun registerColors() {
this.implementation.registerBlockColors(BlockColorProvider { _, _, _, _ ->
return@BlockColorProvider 0xE0A33A
}, CobblemonBlocks.APRICORN_LEAVES)
this.implementation.registerItemColors(ItemColorProvider { _, _ ->
return@ItemColorProvider 0xE0A33A
}, CobblemonItems.APRICORN_LEAVES)
this.implementation.registerBlockColors(BlockColor { blockState, view, blockPos, tintIndex ->
blockPos?.let { pos ->
view?.getBlockEntity(pos)?.let { blockEntity ->
if (blockEntity is TintBlockEntity) return@BlockColor blockEntity.getTint()
}
}
return@BlockColor 0xFFFFFF
}, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE)
}
*/
private fun registerBlockRenderTypes() {
@@ -289,10 +292,10 @@ object CobblemonClient {
CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN,
CobblemonBlocks.SACCHARINE_SAPLING,
CobblemonBlocks.POTTED_SACCHARINE_SAPLING,
CobblemonBlocks.LURE_CAKE,
CobblemonBlocks.POKE_CAKE,
CobblemonBlocks.POKE_SNACK,
CobblemonBlocks.LECTERN,
CobblemonBlocks.CAMPFIRE,
CobblemonBlocks.SOUL_CAMPFIRE,
CobblemonBlocks.BLACK_CAMPFIRE_POT,
CobblemonBlocks.BLUE_CAMPFIRE_POT,
CobblemonBlocks.GREEN_CAMPFIRE_POT,
@@ -349,12 +352,7 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LURE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(
CobblemonBlockEntities.CANDLE_POKE_CAKE,
::CandlePokeCakeBlockEntityRenderer
)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
}
private fun registerEntityRenderers() {
@@ -383,9 +381,8 @@ object CobblemonClient {
private fun registerItemColors() {
implementation.registerItemColors(AprijuiceItemColorProvider, *CobblemonItems.aprijuices.toTypedArray())
implementation.registerItemColors(PokeSnackItemColorProvider, CobblemonItems.POKE_SNACK, CobblemonItems.POKE_CAKE)
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_BAIT)
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.LURE_CAKE)
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_CAKE)
implementation.registerItemColors(PonigiriItemColorProvider, CobblemonItems.PONIGIRI)
implementation.registerItemColors(SinisterTeaItemColorProvider, CobblemonItems.SINISTER_TEA)
}
@@ -20,6 +20,8 @@ import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.aspect.aspectParticleMap
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.aspect.ParticleData
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.scheduling.afterOnClient
@@ -100,13 +100,14 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
// Don't render if duration config set to 0 or less
if (maxDurationFrames <= 0F) return
val rideBehaviourSettings = riddenEntity.ridingBehaviourSettings
val rideBehaviourKey = rideBehaviourSettings?.key
val rideBehaviourSettings = riddenEntity.ridingController?.context?.settings ?: return
val ridingBehaviourState = riddenEntity.ridingController?.context?.state ?: return
val rideBehaviourKey = rideBehaviourSettings.key
// Update current ride behaviour if changed
// If composite, get active behaviour instead
if (rideBehaviourKey == CompositeBehaviour.KEY) {
val activeBehaviour = (riddenEntity.ridingState as CompositeState).activeBehaviour.get()
val activeBehaviour = (ridingBehaviourState as CompositeState).activeBehaviour.get()
if (currentBehaviourKey != activeBehaviour) {
currentBehaviourKey = activeBehaviour
resetOverlayState()
@@ -201,10 +201,10 @@ class BattleMoveSelection(
val move = moveTiles.find { it.isHovered(mouseX, mouseY) }
val gimmick = gimmickButtons.find { it.isHovered(mouseX, mouseY) }
if (move != null) {
if(this.request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
move.onClick()
} else {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move))
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move, move.response.gimmickID, move.move.gimmickMove))
playDownSound(Minecraft.getInstance().soundManager)
}
return true
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.InBattleGimmickMove
import com.cobblemon.mod.common.battles.InBattleMove
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
@@ -43,7 +44,9 @@ import kotlin.math.sin
class BattleTargetSelection(
battleGUI: BattleGUI,
request: SingleActionRequest,
val move: InBattleMove
val move: InBattleMove,
val gimmickID: String?,
gimmickMove: InBattleGimmickMove?
) : BattleActionSelection(
battleGUI = battleGUI,
request = request,
@@ -81,9 +84,10 @@ class BattleTargetSelection(
val targets = request.activePokemon.getAllActivePokemon()
val targetType = if (gimmickID != null && gimmickMove != null) gimmickMove.target else move.target
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val selectableTargetList = move.target.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(move.target) else null
val selectableTargetList = targetType.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(targetType) else null
val baseTiles = targets.mapIndexed { index, target ->
val isAlly = target.isAllied(request.activePokemon)
@@ -11,8 +11,8 @@ package com.cobblemon.mod.common.client.gui.cookingpot
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
import com.cobblemon.mod.common.block.campfirepot.SeasoningSlot
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.IS_LID_OPEN_INDEX
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
import com.cobblemon.mod.common.integration.jei.CobblemonJeiPlugin
import com.cobblemon.mod.common.integration.jei.cooking.CampfirePotRecipeCategory
import com.cobblemon.mod.common.item.crafting.CookingPotRecipe
@@ -39,6 +39,7 @@ import net.minecraft.world.inventory.ClickType
import net.minecraft.world.inventory.RecipeBookMenu
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.crafting.CraftingInput
import java.util.*
import kotlin.math.ceil
@Environment(EnvType.CLIENT)
@@ -174,24 +175,36 @@ class CookingPotScreen(
this.recipeBookComponent.renderGhostRecipe(context, this.leftPos, this.topPos, true, partialTicks)
}
val resultSlot = menu.slots[menu.resultSlotIndex]
val optionalRecipe = menu.currentActiveRecipe
if (optionalRecipe != null && !optionalRecipe.value.result.isEmpty && !resultSlot.hasItem()) {
val resultItem = menu.previewItem
RenderSystem.enableBlend()
RenderSystem.setShaderColor(1F, 1F, 1F, 0.5F)
context.renderFakeItem(resultItem, leftPos + resultSlot.x, topPos + resultSlot.y);
context.renderItemDecorations(font, resultItem, leftPos + resultSlot.x, topPos + resultSlot.y)
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F)
RenderSystem.disableBlend()
if (isHovering(resultSlot.x, resultSlot.y, 16, 16, mouseX.toDouble(), mouseY.toDouble()) && menu.carried.isEmpty) {
context.renderTooltip(font, getTooltipFromContainerItem(resultItem), resultItem.tooltipImage, mouseX, mouseY)
}
}
this.renderTooltip(context, mouseX, mouseY)
this.recipeBookComponent.renderTooltip(context, this.leftPos, this.topPos, mouseX, mouseY)
}
override fun renderSlot(guiGraphics: GuiGraphics, slot: Slot) {
if (slot.index != PREVIEW_ITEM_SLOT) {
super.renderSlot(guiGraphics, slot)
return
if (slot is SeasoningSlot && !menu.carried.isEmpty && !slot.mayPlace(menu.carried)) {
val x = slot.x
val y = slot.y
guiGraphics.fill(x,y,x+16,y+16,822018048)
}
RenderSystem.enableBlend()
RenderSystem.setShaderColor(1F, 1F, 1F, 0.5F)
guiGraphics.renderFakeItem(slot.item, slot.x, slot.y);
guiGraphics.renderItemDecorations(minecraft!!.font, slot.item, slot.x, slot.y)
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F)
RenderSystem.disableBlend()
super.renderSlot(guiGraphics, slot)
}
override fun recipesUpdated() {
@@ -146,19 +146,19 @@ class RidingStatsDebugGUI(val vehicle: PokemonEntity) : Screen(lang("ui.debug.ri
val maxStamina = maxStaminaInput.value.toIntOrNull() ?: getRidingStatRange(RidingStat.STAMINA).last
if (minSpeed < maxSpeed) {
vehicle.rideProp.stats[RidingStat.SPEED]?.ranges?.put(ridingStyle, minSpeed..maxSpeed)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.SPEED, minSpeed..maxSpeed)
}
if (minAcceleration < maxAcceleration) {
vehicle.rideProp.stats[RidingStat.ACCELERATION]?.ranges?.put(ridingStyle, minAcceleration..maxAcceleration)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.ACCELERATION, minSpeed..maxSpeed)
}
if (minSkill < maxSkill) {
vehicle.rideProp.stats[RidingStat.SKILL]?.ranges?.put(ridingStyle, minSkill..maxSkill)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.SKILL, minSpeed..maxSpeed)
}
if (minJump < maxJump) {
vehicle.rideProp.stats[RidingStat.JUMP]?.ranges?.put(ridingStyle, minJump..maxJump)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.JUMP, minSpeed..maxSpeed)
}
if (minStamina < maxStamina) {
vehicle.rideProp.stats[RidingStat.STAMINA]?.ranges?.put(ridingStyle, minStamina..maxStamina)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.STAMINA, minSpeed..maxSpeed)
}
CobblemonNetwork.sendToServer(
@@ -198,7 +198,9 @@ class RidingStatsDebugGUI(val vehicle: PokemonEntity) : Screen(lang("ui.debug.ri
}
fun getRidingStatRange(ridingStat: RidingStat): IntRange {
return vehicle.rideProp.stats[ridingStat]?.ranges?.get(ridingStyle) ?: 0..0
// TODO
return 0..0
// return vehicle.rideProp.stats[ridingStat]?.ranges?.get(ridingStyle) ?: 0..0
}
fun getScaledWidth() = Minecraft.getInstance().window.guiScaledWidth
@@ -17,6 +17,7 @@ import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
@@ -25,7 +26,7 @@ class SummaryTab(
val label: MutableComponent? = null,
val icon: ResourceLocation? = null,
onPress: OnPress
): Button(pX, pY, WIDTH, HEIGHT, label, onPress, DEFAULT_NARRATION), CobblemonRenderable {
): Button(pX, pY, WIDTH, HEIGHT, label ?: Component.empty(), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 39
private const val HEIGHT = 13
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.effect
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import net.minecraft.client.Minecraft
import net.minecraft.core.particles.ParticleTypes
object PokeSnackBlockParticlesHandler : ClientNetworkPacketHandler<PokeSnackBlockParticlesPacket> {
override fun handle(packet: PokeSnackBlockParticlesPacket, client: Minecraft) {
val level = Minecraft.getInstance().level ?: return
val blockState = level.getBlockState(packet.blockPos)
val block = blockState.block
if (block is PokeSnackBlock) {
block.spawnEatParticles(level, packet.blockPos)
}
val entityPos = packet.entityPos
if (entityPos != null) {
val random = level.random
repeat(5) {
level.addParticle(
ParticleTypes.POOF,
entityPos.x + random.nextDouble(),
entityPos.y + random.nextDouble(),
entityPos.z + random.nextDouble(),
0.0,
0.0,
0.0
)
}
}
}
}
@@ -32,7 +32,7 @@ object SaccharineLogBlockParticlesHandler : ClientNetworkPacketHandler<Saccharin
val entityPos = packet.entityPos
if (entityPos != null) {
val random = level.random
repeat(10) {
repeat(5) {
level.addParticle(
ParticleTypes.POOF,
entityPos.x + random.nextDouble(),
@@ -21,8 +21,10 @@ object ClientboundUpdateRidingStateHandler : ClientNetworkPacketHandler<Clientbo
if (entity !is PokemonEntity) return@executeIfPossible
if (entity.controllingPassenger == player) return@executeIfPossible
val buffer = packet.data ?: return@executeIfPossible
if (entity.riding?.key != packet.behaviour) return@executeIfPossible
entity.ridingState?.decode(buffer)
if (entity.ridingController?.context?.settings?.key != packet.behaviour) {
entity.ridingController?.changeBehaviour(packet.behaviour)
}
entity.ridingController?.context?.state?.decode(buffer)
}
}
}
@@ -1,295 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.block.CakeBlock
import com.cobblemon.mod.common.block.entity.CakeBlockEntity
import com.cobblemon.mod.common.item.components.FoodColourComponent
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.math.Axis
import kotlin.random.Random
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.inventory.InventoryMenu.BLOCK_ATLAS
import org.joml.Vector3d
open class CakeBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<CakeBlockEntity> {
companion object {
const val PIXEL_SHIFT = 1.0 / 16.0
const val WHITE_OPAQUE_COLOR = 0xFFFFFFFF.toInt()
const val ATLAS_START = 0.0f
const val ATLAS_END = 1.0f
const val HEIGHT = 0.5f
const val TOP_LAYER_HEIGHT = 0.5625f
const val BOTTOM_LAYER_HEIGHT = 0.0f
object DefaultCakeLayout : CakeLayout {
override val topLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_top_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_top_layer3", LayerColor.PRIMARY),
)
override val sideLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_side_layer1", LayerColor.TERTIARY),
CakeTexture("poke_snack_side_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_side_layer3", LayerColor.PRIMARY),
)
override val bottomLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_bottom_layer1", LayerColor.TERTIARY),
)
override val innerLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_inner_layer1", LayerColor.TERTIARY),
CakeTexture("poke_snack_inner_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_inner_layer3", LayerColor.PRIMARY),
)
}
object HeartCakeLayout : CakeLayout {
override val topLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_top_layer4", LayerColor.SECONDARY),
CakeTexture("poke_snack_top_layer5", LayerColor.PRIMARY),
)
override val sideLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_side_layer1", LayerColor.TERTIARY),
CakeTexture("poke_snack_side_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_side_layer3", LayerColor.PRIMARY),
)
override val bottomLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_bottom_layer1", LayerColor.TERTIARY),
)
override val innerLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_inner_layer1", LayerColor.TERTIARY),
CakeTexture("poke_snack_inner_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_inner_layer3", LayerColor.PRIMARY),
)
}
val CAKE_LAYOUTS = listOf(DefaultCakeLayout, HeartCakeLayout)
fun loadTexture(textureName: String): TextureAtlasSprite {
return Minecraft.getInstance().getTextureAtlas(BLOCK_ATLAS).apply(cobblemonResource(textureName))
}
}
interface CakeLayout {
val topLayers: List<CakeTexture>
val sideLayers: List<CakeTexture>
val bottomLayers: List<CakeTexture>
val innerLayers: List<CakeTexture>
}
class CakeTexture(textureName: String, val color: LayerColor) {
val texture = loadTexture("block/food/${textureName}")
fun getColor(primaryColor: Int, secondaryColor: Int, tertiaryColor: Int): Int {
return when (color) {
LayerColor.PRIMARY -> primaryColor
LayerColor.SECONDARY -> secondaryColor
LayerColor.TERTIARY -> tertiaryColor
}
}
}
enum class LayerColor {
PRIMARY, SECONDARY, TERTIARY
}
override fun render(
blockEntity: CakeBlockEntity,
partialTick: Float,
poseStack: PoseStack,
bufferSource: MultiBufferSource,
packedLight: Int,
packedOverlay: Int
) {
val foodColourComponent = blockEntity.foodColourComponent
renderCake(blockEntity, foodColourComponent, poseStack, bufferSource, packedLight, blockEntity.bites)
}
fun renderCake(
blockEntity: CakeBlockEntity,
foodColourComponent: FoodColourComponent?,
poseStack: PoseStack,
multiBufferSource: MultiBufferSource,
light: Int,
bites: Int,
) {
val seedString = foodColourComponent?.colours?.joinToString()
val random = Random(seedString.hashCode())
val cakeLayout: CakeLayout = CAKE_LAYOUTS[random.nextInt(CAKE_LAYOUTS.size)]
val visualStages = 7f
val maxBites = blockEntity.maxBites.toFloat()
val bitesPerStage = ((maxBites + 1) / visualStages).coerceAtLeast(1f)
val visualStage = (bites / bitesPerStage).toInt().coerceIn(0, visualStages.toInt() - 1)
val biteOffset = (15f - 2f * visualStage) / 16f
val clippedWidth = (ATLAS_END - ATLAS_START) * biteOffset
val topLayer = cakeLayout.topLayers.first().texture
val uClipped = topLayer.u0 + (topLayer.u1 - topLayer.u0) * biteOffset
val vertexConsumer = multiBufferSource.getBuffer(RenderType.cutout())
val primaryColor = foodColourComponent?.colours?.getOrNull(0)?.textureDiffuseColor ?: WHITE_OPAQUE_COLOR
val secondaryColor = foodColourComponent?.colours?.getOrNull(1)?.textureDiffuseColor ?: WHITE_OPAQUE_COLOR
val tertiaryColor = foodColourComponent?.colours?.getOrNull(2)?.textureDiffuseColor ?: WHITE_OPAQUE_COLOR
poseStack.pushPose()
poseStack.translate(0.5, 0.5, 0.5)
val facing = blockEntity.blockState.getValue(CakeBlock.FACING)
val angle = when (facing) {
Direction.NORTH -> 180f
Direction.SOUTH -> 0f
Direction.WEST -> 90f
Direction.EAST -> -90f
else -> 0f
}
poseStack.mulPose(Axis.YP.rotationDegrees(angle))
poseStack.translate(-0.5, -0.5, -0.5)
poseStack.pushPose()
for (layer in cakeLayout.topLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, TOP_LAYER_HEIGHT, ATLAS_START, clippedWidth, TOP_LAYER_HEIGHT, ATLAS_END,
texture.u0, texture.v0, uClipped, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
poseStack.popPose()
val translations = arrayOf(
Vector3d(-1.0, 1.0, 0.5 - PIXEL_SHIFT),
Vector3d(0.0, 1.0, 0.5 - PIXEL_SHIFT),
Vector3d(0.0, 1.0, -0.5 - PIXEL_SHIFT),
Vector3d(-1.0, 1.0, -0.5 - PIXEL_SHIFT),
)
val rotationsY = arrayOf(
Axis.YP.rotationDegrees(90f),
Axis.YP.rotationDegrees(0f),
Axis.YP.rotationDegrees(-90f),
Axis.YP.rotationDegrees(-180f)
)
val cakeIsFull = bites == 0
for (i in translations.indices) {
poseStack.pushPose()
poseStack.mulPose(rotationsY[i])
poseStack.translate(translations[i].x, translations[i].y, translations[i].z)
poseStack.mulPose(Axis.XP.rotationDegrees(90f))
if (i == 0 && !cakeIsFull) {
poseStack.translate(0.0, -visualStage * PIXEL_SHIFT * 2, 0.0)
for (layer in cakeLayout.innerLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, HEIGHT, ATLAS_START, ATLAS_END, HEIGHT, ATLAS_END,
texture.u0, texture.v0, texture.u1, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
} else if (i == 0 || i == 1) {
for (layer in cakeLayout.sideLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, HEIGHT, ATLAS_START, clippedWidth, HEIGHT, ATLAS_END,
texture.u0, texture.v0, uClipped, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
} else if (i == 2) {
for (layer in cakeLayout.sideLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, HEIGHT, ATLAS_START, ATLAS_END, HEIGHT, ATLAS_END,
texture.u0, texture.v0, texture.u1, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
} else if (i == 3) {
val clippedStartX = ATLAS_START + (ATLAS_END - ATLAS_START) * (1 - biteOffset)
for (layer in cakeLayout.sideLayers) {
val texture = layer.texture
val clippedUStart = texture.u0 + (texture.u1 - texture.u0) * (1 - biteOffset)
drawQuad(
vertexConsumer, poseStack,
clippedStartX, HEIGHT, ATLAS_START, ATLAS_END, HEIGHT, ATLAS_END,
clippedUStart, texture.v0, texture.u1, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
}
poseStack.popPose()
}
poseStack.pushPose()
poseStack.translate(0.5, 0.0, 0.5)
poseStack.mulPose(Axis.XP.rotationDegrees(180f))
poseStack.translate(-0.5, 0.0, -0.5)
for (layer in cakeLayout.bottomLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, BOTTOM_LAYER_HEIGHT, ATLAS_START, clippedWidth, BOTTOM_LAYER_HEIGHT, ATLAS_END,
texture.u0, texture.v0, uClipped, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
poseStack.popPose()
poseStack.popPose()
}
private fun drawQuad(
builder: VertexConsumer,
poseStack: PoseStack,
x0: Float, y0: Float, z0: Float,
x1: Float, y1: Float, z1: Float,
u0: Float, v0: Float, u1: Float, v1: Float,
packedLight: Int, color: Int,
) {
drawVertex(builder, poseStack, x0, y0, z0, u0, v0, packedLight, color)
drawVertex(builder, poseStack, x0, y1, z1, u0, v1, packedLight, color)
drawVertex(builder, poseStack, x1, y1, z1, u1, v1, packedLight, color)
drawVertex(builder, poseStack, x1, y0, z0, u1, v0, packedLight, color)
}
private fun drawVertex(
builder: VertexConsumer,
poseStack: PoseStack,
x: Float, y: Float, z: Float,
u: Float, v: Float,
packedLight: Int, color: Int
) {
builder.addVertex(poseStack.last().pose(), x, y, z)
.setColor(color)
.setUv(u, v)
.setLight(packedLight)
.setNormal(0f, 1f, 0f)
}
}
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.block.CandlePokeCakeBlock.Companion.CANDLE_COLOR
import com.cobblemon.mod.common.block.CandlePokeCakeBlock.Companion.getCandleByMapColorId
import com.cobblemon.mod.common.block.entity.CakeBlockEntity
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.world.level.block.state.properties.BlockStateProperties.LIT
class CandlePokeCakeBlockEntityRenderer(
ctx: BlockEntityRendererProvider.Context,
) : CakeBlockEntityRenderer(ctx) {
override fun render(
blockEntity: CakeBlockEntity,
partialTick: Float,
poseStack: PoseStack,
bufferSource: MultiBufferSource,
packedLight: Int,
packedOverlay: Int
) {
super.render(blockEntity, partialTick, poseStack, bufferSource, packedLight, packedOverlay)
poseStack.pushPose()
poseStack.translate(0.0, TOP_LAYER_HEIGHT.toDouble(), 0.0)
val candleBlock = getCandleByMapColorId(blockEntity.blockState.getValue(CANDLE_COLOR))
Minecraft.getInstance().blockRenderer.renderSingleBlock(
candleBlock.defaultBlockState().setValue(LIT, blockEntity.blockState.getValue(LIT)),
poseStack,
bufferSource,
packedLight,
packedOverlay
)
poseStack.popPose()
}
}
@@ -0,0 +1,115 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.block.PokeSnackBlock.Companion.CANDLE
import com.cobblemon.mod.common.block.PokeSnackBlock.Companion.getCandleById
import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.client.render.atlas.CobblemonAtlases.BERRY_SPRITE_ATLAS
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.setPosition
import com.cobblemon.mod.common.util.math.geometry.Axis
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.world.level.block.state.properties.BlockStateProperties.LIT
open class PokeSnackBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<PokeSnackBlockEntity> {
override fun render(blockEntity: PokeSnackBlockEntity, partialTick: Float, poseStack: PoseStack, bufferSource: MultiBufferSource, packedLight: Int, packedOverlay: Int) {
renderCandle(blockEntity, poseStack, bufferSource, packedLight, packedOverlay)
renderBerries(blockEntity, poseStack, bufferSource, packedLight, packedOverlay)
}
fun renderCandle(blockEntity: PokeSnackBlockEntity, poseStack: PoseStack, bufferSource: MultiBufferSource, packedLight: Int, packedOverlay: Int) {
val candleId = blockEntity.blockState.getValue(CANDLE)
if (candleId > 0) {
val candleBlock = getCandleById(blockEntity.blockState.getValue(CANDLE))
poseStack.pushPose()
poseStack.translate(0.0, PokeSnackBlock.CAKE_HEIGHT, 0.0)
Minecraft.getInstance().blockRenderer.renderSingleBlock(
candleBlock.defaultBlockState().setValue(LIT, blockEntity.blockState.getValue(LIT)),
poseStack,
bufferSource,
packedLight,
packedOverlay
)
poseStack.popPose()
}
}
fun renderBerries(blockEntity: PokeSnackBlockEntity, poseStack: PoseStack, bufferSource: MultiBufferSource, packedLight: Int, packedOverlay: Int) {
// RenderType.entityCutoutNoCull for shading, as shading is absent in RenderType CobblemonRenderLayers.BERRY_LAYER
val buffer = bufferSource.getBuffer(
RenderType.entityCutoutNoCull(BERRY_SPRITE_ATLAS.textureAtlas.location())
)
val berryIngredients = (blockEntity.ingredientComponent?.ingredientIds?.take(3) ?: listOf())
.mapNotNull { Berries.getByIdentifier(it) }
berryIngredients.forEachIndexed { index, berry ->
val model = BerryModelRepository.modelOf(berry.fruitModelIdentifier) ?: return
when (berryIngredients.size) {
1 -> {
val pos = berry.pokeSnackPositionings[0].position
model.setPosition(Axis.X_AXIS.ordinal, 8F)
model.setPosition(Axis.Y_AXIS.ordinal, pos.y.toFloat())
model.setPosition(Axis.Z_AXIS.ordinal, 8F)
model.setRotation(
Math.toRadians(180.0).toFloat(),
Math.toRadians(0.0).toFloat(),
Math.toRadians(0.0).toFloat()
)
}
2 -> {
val pos = berry.pokeSnackPositionings[0].position
val rot = berry.pokeSnackPositionings[0].rotation
val isFirst = index == 0
model.setPosition(Axis.X_AXIS.ordinal, pos.x.toFloat())
model.setPosition(Axis.Y_AXIS.ordinal, pos.y.toFloat())
model.setPosition(Axis.Z_AXIS.ordinal, if (isFirst) pos.z.toFloat() else (16F - pos.z.toFloat()))
model.setRotation(
Math.toRadians(
if (isFirst) rot.x else (180.0 + (180.0 - rot.x))
).toFloat(),
Math.toRadians(rot.y).toFloat(),
Math.toRadians(rot.z).toFloat()
)
}
else -> {
val pos = berry.pokeSnackPositionings[index].position
val rot = berry.pokeSnackPositionings[index].rotation
model.setPosition(Axis.X_AXIS.ordinal, pos.x.toFloat())
model.setPosition(Axis.Y_AXIS.ordinal, pos.y.toFloat())
model.setPosition(Axis.Z_AXIS.ordinal, pos.z.toFloat())
model.setRotation(
Math.toRadians(rot.x).toFloat(),
Math.toRadians(rot.y).toFloat(),
Math.toRadians(rot.z).toFloat()
)
}
}
model.render(poseStack, buffer, packedLight, packedOverlay)
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.color
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.cooking.getColourMixFromFlavours
import net.minecraft.client.color.item.ItemColor
import net.minecraft.world.item.ItemStack
object PokeSnackItemColorProvider : ItemColor {
private const val BASE_LAYER_INDEX = 0
override fun getColor(stack: ItemStack, layer: Int): Int {
val flavourComponent = stack.get(CobblemonItemComponents.FLAVOUR) ?: return -1
if (layer == BASE_LAYER_INDEX) {
val colorMix = getColourMixFromFlavours(flavourComponent.getDominantFlavours())
if (colorMix != null) return colorMix
}
return -1
}
}
@@ -32,13 +32,10 @@ object MountedLivingEntityRenderer {
) {
if (entity.vehicle !is Rideable) return
val matrix = stack.last().pose()
val seatIndex = pokemon.passengers.indexOf(entity)
val seat = pokemon.seats[seatIndex]
val delegate = pokemon.delegate as PokemonClientDelegate
val locator = delegate.locatorStates[seat.locator]
val locator = delegate.locatorStates[delegate.getSeatLocator(entity)]
//Positions player
//Positions entity
if (locator != null) {
MountedPokemonAnimationRenderController.setup(pokemon, partialTicks)
@@ -113,13 +113,13 @@ object CobblemonModelPredicateRegistry {
"minecraft:brown_mushroom" -> 0.07f
"minecraft:pumpkin" -> 0.08f
"cobblemon:rice" -> 0.09f
"minecraft:raw_chicken" -> 0.10f
"minecraft:raw_cod" -> 0.11f
"minecraft:raw_mutton" -> 0.12f
"minecraft:raw_porkchop" -> 0.13f
"minecraft:raw_rabbit" -> 0.14f
"minecraft:raw_salmon" -> 0.15f
"minecraft:raw_beef" -> 0.16f
"minecraft:chicken" -> 0.10f
"minecraft:cod" -> 0.11f
"minecraft:mutton" -> 0.12f
"minecraft:porkchop" -> 0.13f
"minecraft:rabbit" -> 0.14f
"minecraft:salmon" -> 0.15f
"minecraft:beef" -> 0.16f
"minecraft:egg" -> 0.17f
"minecraft:poisonous_potato" -> 0.18f
"minecraft:rotten_flesh" -> 0.19f
@@ -8,17 +8,26 @@
package com.cobblemon.mod.common.client.render.models.blockbench
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel.Companion.getOrMakeShard
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.shader.CobblemonShaders
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.ImmutableList
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.client.renderer.RenderType
import com.mojang.blaze3d.vertex.VertexConsumer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.model.EntityModel
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.resources.ResourceLocation
import org.apache.commons.lang3.tuple.Triple
/**
* A wrapping around a [PosableModel] that presents as an [EntityModel]. This is used to continue using
@@ -29,13 +38,46 @@ import net.minecraft.resources.ResourceLocation
* @since January 5th, 2024
*/
abstract class PosableEntityModel<T : Entity>(
renderTypeFunc: (ResourceLocation) -> RenderType = RenderType::entityCutout
renderTypeFunc: (ResourceLocation) -> RenderType = {
val textures = ImmutableList.of(
Triple.of(it, false, false),
Triple.of(cobblemonResource("textures/tera_layer.png"), false, false),
Triple.of(cobblemonResource("textures/tera_layer.png"), false, false),
Triple.of(cobblemonResource("textures/tera_layer.png"), false, false),
Triple.of(cobblemonResource("textures/tera_layer.png"), false, false),
)
makeLayer(RenderStateShard.MultiTextureStateShard(textures), false, false, false)
}
) : EntityModel<T>(renderTypeFunc) {
val context: RenderContext = RenderContext().also {
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.WORLD)
}
lateinit var posableModel: PosableModel
companion object {
fun makeLayer(texture: RenderStateShard.EmptyTextureStateShard, emissive: Boolean, translucent: Boolean, translucentCull: Boolean): RenderType {
val multiPhaseParameters: RenderType.CompositeState = RenderType.CompositeState.builder()
.setShaderState(getOrMakeShard() as RenderStateShard.ShaderStateShard)
.setTextureState(texture)
.setTransparencyState(if (translucent) RenderStateShard.TRANSLUCENT_TRANSPARENCY else RenderStateShard.NO_TRANSPARENCY)
.setLightmapState(if (!emissive) RenderStateShard.LIGHTMAP else RenderStateShard.LightmapStateShard(false))
.setCullState(RenderStateShard.CULL)
.setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE)
.setOverlayState(RenderStateShard.OVERLAY)
.createCompositeState(false)
return RenderType.create(
"cobblemon_entity_layer",
DefaultVertexFormat.NEW_ENTITY,
VertexFormat.Mode.QUADS,
256,
true,
translucent,
multiPhaseParameters
)
}
}
override fun renderToBuffer(
stack: PoseStack,
buffer: VertexConsumer,
@@ -49,6 +91,7 @@ abstract class PosableEntityModel<T : Entity>(
// We're using the living entity renderer, ergo we need to apply a Y offset of 24 model units (1.5 blocks) for reasons only God can tell us.
stack.translate(0.0, 1.5, 0.0)
}
// RenderSystem.setShader { CobblemonShaders.OVERLAY }
posableModel.render(context, stack, buffer, packedLight, overlay, color)
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.client.ClientMoLangFunctions.animationFunctions
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
import com.cobblemon.mod.common.client.render.ModelLayer
@@ -33,6 +34,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.SimpleQuirk
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.shader.CobblemonShaders
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
@@ -40,26 +42,33 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.plus
import com.cobblemon.mod.common.util.toRGBA
import com.google.common.collect.ImmutableList
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.ShaderInstance
import net.minecraft.client.renderer.texture.DynamicTexture
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.ItemDisplayContext
import net.minecraft.world.phys.Vec3
import org.apache.commons.lang3.tuple.Triple
import org.joml.Matrix4f
import org.joml.Vector3f
import java.lang.reflect.Constructor
/**
* A model that can be posed and animated using [PoseAnimation]s and [ActiveAnimation]s. This
@@ -414,6 +423,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
/** Needed a custom one, so I can make it take in a dynamic texture instead of resource location */
class DynamicStateShard(texture: DynamicTexture) : RenderStateShard.EmptyTextureStateShard(Runnable {
RenderSystem.setShaderTexture(0, texture.id)
RenderSystem.setShaderTexture(1, cobblemonResource("textures/tera_layer.png"))
}, Runnable {
texture.close() // Cleanup
})
@@ -453,13 +463,14 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
if (provider != null) {
for (layer in currentLayers) {
var renderLayer : RenderType
var texture: ResourceLocation? = null
if (layer.texture is AnimatedModelTextureSupplier && layer.texture.interpolation) {
//Handle Interpolation
val texture = layer.texture.interpolatedTexture(currentState ?: FloatingState()) ?: continue
renderLayer = makeLayer(DynamicStateShard(texture), layer.emissive, layer.translucent, layer.translucent_cull)
}
else {
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
renderLayer = getLayer(texture, layer.emissive, layer.translucent, layer.translucent_cull)
}
val consumer = provider.getBuffer(renderLayer)
@@ -490,18 +501,21 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
return ItemDisplayContext.valueOf(displayContextString)
}
companion object {
var test: Any? = null
fun getOrMakeShard(): Any {
if (test == null) {
test = RenderStateShard.ShaderStateShard({ CobblemonShaders.OVERLAY })
}
return test!!
}
}
/** Generates a [RenderType] by the power of god and anime. Only possible thanks to 100 access wideners. */
fun makeLayer(texture: RenderStateShard.EmptyTextureStateShard, emissive: Boolean, translucent: Boolean, translucentCull: Boolean): RenderType {
val multiPhaseParameters: RenderType.CompositeState = RenderType.CompositeState.builder()
.setShaderState(
when {
emissive && translucent -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER
!emissive && translucent && translucentCull -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER
!emissive && translucent -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_SHADER
!emissive && !translucent -> RenderStateShard.RENDERTYPE_ENTITY_CUTOUT_SHADER
else -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER // This one should be changed to maybe a custom shader? Translucent stuffs with things
}
)
.setShaderState(getOrMakeShard() as RenderStateShard.ShaderStateShard)
.setTextureState(texture)
.setTransparencyState(if (translucent) RenderStateShard.TRANSLUCENT_TRANSPARENCY else RenderStateShard.NO_TRANSPARENCY)
.setLightmapState(if (!emissive) RenderStateShard.LIGHTMAP else RenderStateShard.LightmapStateShard(false))
@@ -528,7 +542,14 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
} else if (!emissive && !translucentCull) {
RenderType.entityTranslucent(texture)
} else {
makeLayer(RenderStateShard.TextureStateShard(texture, false, false), emissive = emissive, translucent = translucent, translucentCull = translucentCull)
val texture = RenderStateShard.MultiTextureStateShard(ImmutableList.of(
Triple.of(texture, false, false),
Triple.of(cobblemonResource("textures/tera_layer.png"), false, false),
Triple.of(cobblemonResource("textures/tera_layer.png"), false, false),
Triple.of(cobblemonResource("textures/tera_layer.png"), false, false),
Triple.of(cobblemonResource("textures/tera_layer.png"), false, false),
))
makeLayer(texture, emissive = emissive, translucent = translucent, translucentCull = translucentCull)
}
}
@@ -271,7 +271,7 @@ abstract class PosableState : Schedulable {
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0) * -1.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("input_up") { params ->
val lookBackTick = params.getInt(0)
@@ -33,6 +33,7 @@ class PitchTiltAnimation(
) : PoseAnimation() {
companion object {
const val PREVIOUS_ANGLE = "previous_pitch_tilt_angle"
const val CORRECTED_ANGLE = "corrected_pitch_tilt_angle"
const val PITCHED_TILT = "has_pitched_tilt"
}
@@ -52,10 +53,9 @@ class PitchTiltAnimation(
return
}
val delta = entity.deltaMovement
// The angleOfMotion should be 0 in the case that horizontal movement is 1 and vertical movement is 0.
// It should be the angle in degrees that becomes higher as the entity's motion becomes more upwards, and negative as the entity's motion becomes more downwards
val vertical = delta.y
val horizontal = delta.horizontalDistance()
// It should be the angle in degrees that becomes higher as the entity's motion becomes more upwards, and negative as the entity's motion becomes more downwards
val angleOfMotion = if (horizontal == 0.0) {
when {
vertical > 0 -> maxPitch
@@ -64,20 +64,20 @@ class PitchTiltAnimation(
}
} else {
Mth.atan2(vertical, horizontal).toDegrees()
}.coerceIn(minPitch, maxPitch)
val maxChangePerTick = this.maxChangePerTick * intensity
}.coerceIn(minPitch, maxPitch) * -1
val lastAngle = state.numbers[PREVIOUS_ANGLE] ?: 0F
val interpolatedAngle = if (angleOfMotion - lastAngle > maxChangePerTick) {
lastAngle + maxChangePerTick
} else if (angleOfMotion - lastAngle < -maxChangePerTick) {
lastAngle - maxChangePerTick
val change = (angleOfMotion - lastAngle).coerceIn(-maxChangePerTick, maxChangePerTick)
if (PITCHED_TILT !in state.renderMarkers) {
state.numbers.remove(CORRECTED_ANGLE)
val correctedAngle = (lastAngle + change) * intensity
bone.addRotation(0, correctedAngle.toRadians())
state.numbers[PREVIOUS_ANGLE] = correctedAngle
} else {
angleOfMotion
// It was already pitched, which means the body bone already has a rotation, so only rotate it a pinch more.
bone.addRotation(0, change.toRadians() * intensity)
state.numbers[PREVIOUS_ANGLE] = lastAngle + change * intensity
}
bone.addRotation(0, -interpolatedAngle.toRadians())
state.numbers[PREVIOUS_ANGLE] = interpolatedAngle
state.renderMarkers += PITCHED_TILT
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation.Companion.CORRECTED_ANGLE
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation.Companion.PITCHED_TILT
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation.Companion.PREVIOUS_ANGLE
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
@@ -59,7 +60,6 @@ class SingleBoneLookAnimation(
minYaw = minYaw ?: -45F,
)
override var labels = setOf("look")
override fun setupAnim(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
val head = bone ?: return
@@ -67,8 +67,14 @@ class SingleBoneLookAnimation(
val yaw = yawMultiplier * headYaw.coerceIn(minYaw, maxYaw)
// If PitchTiltAnimation was applied then we should counteract that tilt. It does kinda
// assume that the tilt was on the body bone, though.
val counterTiltDegrees = if (PITCHED_TILT in state.renderMarkers) { state.numbers[PREVIOUS_ANGLE] ?: 0 } else 0F
head.addRotation(X_AXIS, (pitch.toRadians() * intensity) + counterTiltDegrees.toRadians())
var counterTiltDegrees = if (PITCHED_TILT in state.renderMarkers) { state.numbers[PREVIOUS_ANGLE] ?: 0F } else 0F
// If the corrected angle is present then we're probably in a pose transition so some amount of the tilt
// has already been corrected for, so we counter a bit less.
if (CORRECTED_ANGLE in state.numbers && PITCHED_TILT in state.renderMarkers) {
counterTiltDegrees -= state.numbers[CORRECTED_ANGLE] ?: 0F
}
state.numbers[CORRECTED_ANGLE] = counterTiltDegrees
head.addRotation(X_AXIS, pitch.toRadians() * intensity - counterTiltDegrees.toRadians())
head.addRotation(Y_AXIS, yaw.toRadians() * intensity)
}
}
@@ -1,88 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class DrowzeeModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("drowzee")
override val head = getPart("head")
override val leftLeg = getPart("left_leg")
override val rightLeg = getPart("right_leg")
override var portraitScale = 2.4F
override var portraitTranslation = Vec3(0.0, -0.1, 0.0)
override var profileScale = 0.9F
override var profileTranslation = Vec3(0.0, 0.4, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
lateinit var battleidle: Pose
override fun registerPoses() {
val blink = quirk { bedrockStateful("drowzee", "blink")}
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("drowzee", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
condition = { !it.isBattling },
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("drowzee", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("drowzee", "ground_walk")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("drowzee", "battle_idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("drowzee", "faint") else null
}
@@ -1,86 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class HypnoModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("hypno")
override val head = getPart("head")
override val leftLeg = getPart("left_upper_leg")
override val rightLeg = getPart("right_upper_leg")
override var portraitScale = 2.1F
override var portraitTranslation = Vec3(-0.3, 0.6, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(-0.05, 0.5, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
lateinit var battleidle: Pose
override fun registerPoses() {
val blink = quirk { bedrockStateful("hypno", "blink")}
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("hypno", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("hypno", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("hypno", "ground_walk")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("hypno", "battle_idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("hypno", "faint") else null
}
@@ -262,7 +262,6 @@ object VaryingModelRepository {
inbuilt("ditto", ::DittoModel)
inbuilt("dodrio", ::DodrioModel)
inbuilt("doduo", ::DoduoModel)
inbuilt("drowzee", ::DrowzeeModel)
inbuilt("electabuzz", ::ElectabuzzModel)
inbuilt("exeggcute", ::ExeggcuteModel)
inbuilt("exeggutor", ::ExeggutorModel)
@@ -279,7 +278,6 @@ object VaryingModelRepository {
inbuilt("growlithe", ::GrowlitheModel)
inbuilt("hitmonchan", ::HitmonchanModel)
inbuilt("hitmonlee", ::HitmonleeModel)
inbuilt("hypno", ::HypnoModel)
inbuilt("jynx", ::JynxModel)
inbuilt("kabuto", ::KabutoModel)
inbuilt("kabutops", ::KabutopsModel)

Some files were not shown because too many files have changed in this diff Show More