Compare commits

...
Author SHA1 Message Date
Aram 891f984d08 Custom Rendering for Overlay, Am not satisfied yet 2025-04-19 13:15:50 +02:00
plastered_crab bb379764f7 Time for pokemon to breed with their brains! Merging in brain branch finally!!! 2025-04-18 09:50:53 -07:00
plastered_crab 3ec78d9d7e Merge branch 'refs/heads/1.21.1-breeding-pokemingle.com' into 1.21.1-breeding 2025-04-18 09:36:56 -07:00
plastered_crab 12973dd66a Grotto Block finally mimics blocks perfectly (with AO) 2025-04-18 09:26:46 -07:00
Aram ed30e4cd6f Almost working Ambient Occlusion, it doesnt check for all surroundings yet 2025-04-18 17:39:35 +02:00
Aram 32c260446e Grotto isnt black anymore!, Occlusion still broken 2025-04-18 17:30:42 +02:00
plastered_crab 41f1eb699f Marging with smarterer and fixing reference issues 2025-04-18 07:56:23 -07:00
plastered_crab e68a593e9e Merge branch 'refs/heads/smarterer-pokemons' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/RidingManager.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/PosableModel.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonServerDelegate.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Species.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/ai/PokemonBrain.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/EntityExtensions.kt
#	common/src/main/resources/data/cobblemon/behaviours/pokemon/pokemon_breeding.json
#	common/src/main/resources/data/cobblemon/behaviours/pokemon/pokemon_core.json
2025-04-18 06:51:29 -07:00
plastered_crab 903ffe2143 Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-04-18 06:44:22 -07:00
Aram 1d6845d595 When in Creative, the Incubator also doesn't remove the egg from your inventory 2025-04-18 12:41:15 +02:00
Aram f8eb72e437 Finished Incubator Hatching Animations 2025-04-18 11:39:18 +02:00
Plastered_Crab 28a3b8edfe Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2025-04-17 15:51:39 -07:00
Plastered_Crab 96ee23b7d1 🥚2️⃣ Add ALL Finalized Gen 2 Egg Sprites and Egg Textures 2025-04-17 15:51:23 -07:00
Aram d3ce829e5b Incubator Hatching Animation WIP
Currently Missing the BlockPos so the Pokemon wont spawn

check: EggRenderHelper
2025-04-17 16:35:46 +02:00
Plastered_Crab 4010f138ae Remove some old code and comment out debug lines 2025-04-16 11:32:24 -07:00
Plastered_Crab 5100d6c028 Habitat spawning system now references player party levels to determine the level of things that spawn 2025-04-16 11:25:20 -07:00
plastered_crab 154ff75a9d Comment out debugging lines to check other things 2025-04-16 07:38:07 -07:00
plastered_crab 678c98a5b6 GrottoBlockEntities now grab a set of habitat spawns during each Full Moon or if it is empty 2025-04-16 07:37:26 -07:00
plastered_crab d9143788bf Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-04-16 06:14:17 -07:00
plastered_crab 7e1956f165 Use POI system to detect Grotto Blocks and use them to spawn Pokemon around habitats 2025-04-16 00:08:28 -07:00
plastered_crab 994a41af78 Add structureCenter variable to GrottoBlockEntity to try to use later to center habitat spawns 2025-04-15 21:32:37 -07:00
Plastered_Crab 2d5f15a4db Fix another oopsie 2025-04-15 15:37:05 -07:00
Plastered_Crab 8442000708 Habitats files now work with weighted spawnpools and per-entry weights too 🌳 2025-04-15 15:28:06 -07:00
Plastered_Crab 695ac93f99 Habitats are starting to work now :D 2025-04-15 12:46:58 -07:00
plastered_crab e5e5c29c12 Edits to Dead Wood habitat 2025-04-15 07:45:20 -07:00
plastered_crab 893277fdec Habitats support list of spawn choices per moon cycle instead of only individual spawns 2025-04-15 00:43:38 -07:00
plastered_crab 60dc881e3d Basic system for Grotto Blocks to cycle spawn pool choices each Full Moon cycle 2025-04-14 23:50:15 -07:00
plastered_crab f98acf0bc4 Fix oopsie 2025-04-14 22:04:38 -07:00
plastered_crab 6e104fdb01 Spawn system detects Grotto blocks as POIs and detects the habitat they belong to along with reads the spawn pool of the habitat 2025-04-14 22:00:48 -07:00
plastered_crab 05c559e203 Grotto block now detects and saves the structure they spawn with onto them 2025-04-14 18:49:54 -07:00
plastered_crab 1323caeeec Add test dead tree habitat for testing 2025-04-14 18:15:44 -07:00
Plastered_Crab 6dc5809622 Add test Grotto Block for habitat testing 2025-04-11 15:24:27 -07:00
Plastered_Crab d4416e50b2 Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2025-04-10 16:13:22 -07:00
Plastered_Crab 520bcda956 Adding test files for experimenting with habitats 2025-04-10 16:12:56 -07:00
Sterrezo d10c082e67 Subtitled the egg sounds 2025-04-10 22:33:23 +02:00
Sterrezo 72d608b300 Volume corrections and fixed a missing sound 2025-04-10 22:13:14 +02:00
Sterrezo 2b4dc467ff Egg sounds! 2025-04-10 21:36:14 +02:00
Plastered_Crab b9390838d0 Merge branch '1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-04-10 09:59:44 -07:00
plastered_crab e36bbd98f2 No more stuttering hatch animation (Thank you Landon <3) 2025-04-09 22:37:56 -07:00
plastered_crab c4aa925f39 Put hatch animation onto PokemonEntity 2025-04-09 20:57:26 -07:00
plastered_crab ad5e85ad97 Final Hatching animation + particles working more now! 2025-04-09 14:50:09 -07:00
plastered_crab 5279603432 Particle keyframes starting to work on Hatching Animations now :D 2025-04-09 13:02:15 -07:00
plastered_crab 583fba4e77 Sound keyframes starting to work 2025-04-09 08:56:20 -07:00
plastered_crab 9ee983b2a2 Good checkpoint on trying to get the hatching sound to play via keyframe 2025-04-08 21:31:30 -07:00
Plastered_Crab 16ffbbbd9e Some code to try to get Hatching sounds working :D 2025-04-08 16:33:13 -07:00
Plastered_Crab f23495fe99 Fix for hatching multiple eggs on the same nest due to a missing sync 2025-04-08 14:53:35 -07:00
Plastered_Crab f61448c333 Hatching animations work now! REJOICE! 2025-04-08 14:44:58 -07:00
Plastered_Crab 406770eaf1 More work towards getting the hatching animations working 2025-04-08 13:34:16 -07:00
Plastered_Crab 2513fab317 More work towards getting the hatching animations working 2025-04-08 12:11:47 -07:00
Plastered_Crab 3a79e49edb More work towards getting the hatching animations working 2025-04-08 10:44:16 -07:00
plastered_crab 87dad0143b Hatching animation FINALLY starting to work 2025-04-08 00:48:39 -07:00
plastered_crab bdc91fea71 Add in new files to get ready for Hatching animation implementation 2025-04-07 19:04:41 -07:00
Plastered_Crab eb64666229 Merge with main to keep things up to date for less pain later 2025-04-07 13:43:07 -07:00
Plastered_Crab f7ed0c8799 Merge branch '1.21.1-breeding' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/NestBlockEntity.kt
2025-04-07 13:25:27 -07:00
Plastered_Crab 9fde7adb7b Merge with main to keep things up to date for less pain later 2025-04-07 13:14:56 -07:00
Plastered_Crab 8b8c07065b Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/RidingManager.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/PosableModel.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonServerDelegate.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/EntityExtensions.kt
2025-04-07 12:48:42 -07:00
Plastered_Crab 6d761fef4f Merge in new Brains code and remove Nest Spawner 2025-03-27 16:59:38 -07:00
Plastered_Crab 737ce1314c Remove old unused "Nest" spawner and merge in new code 2025-03-27 16:31:46 -07:00
Plastered_Crab 7b89f82d45 Merge branch '1.21.1-breeding' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonActivities.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonMemories.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonSpecies.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/repository/VaryingModelRepository.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/FormData.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/ai/PokemonBrain.kt
#	common/src/main/resources/cobblemon-common.accesswidener
2025-03-27 15:43:17 -07:00
Plastered_Crab 2203d543a1 Merge branch 'smarterer-pokemons' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/permission/CobblemonPermissions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonSpecies.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/BestSpawner.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/Category.kt
2025-03-27 15:32:11 -07:00
Guilherme Pereira c129157687 add foliage color to leaf nests 2025-03-27 03:52:41 -03:00
Guilherme Pereira 276c49ac5c fix lush nest caves configured feature 2025-03-27 03:52:26 -03:00
Plastered_Crab f517a63cc9 Make it so nests naturally generate in the world in their proper biomes 2025-03-18 15:52:31 -07:00
Plastered_Crab 1e2f685ee5 fix for filename for Gastly's egg 2025-03-17 16:33:13 -07:00
Plastered_Crab 5245614804 Add light level to Warm Ocean nest and adjust collision boxes of new nests 2025-03-17 16:29:12 -07:00
Plastered_Crab 8b97c3cb85 Whoopsie these files were missed 2025-03-17 15:51:07 -07:00
Plastered_Crab d607463116 Added the 8 new nest types! 2025-03-17 15:50:42 -07:00
Plastered_Crab 07cc92eb18 Added new underwater Seagrass nest for cold oceans/rivers :D 2025-03-04 16:21:04 -08:00
Plastered_Crab 9665eba7b0 Wild parents will bring their lost eggs back to their nest more consistently now 2025-02-28 12:25:05 -08:00
Plastered_Crab 9f83c7c72c Put that thing back where it came from or so help me 2025-02-26 15:52:10 -08:00
Plastered_Crab 9bb74358f7 Get this thing on track with Main again too 2025-02-26 15:48:06 -08:00
Plastered_Crab e2219730e6 Merge branch '1.21.1-breeding' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/NestBlockEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
2025-02-26 15:40:47 -08:00
Plastered_Crab 4daaf50b1a Get this thing on track with Main again 2025-02-26 15:37:01 -08:00
Plastered_Crab b79792ebbf Merge branch 'main' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonSpecies.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/repository/VaryingModelRepository.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/Category.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/FormData.kt
#	common/src/main/resources/cobblemon-common.accesswidener
2025-02-21 15:14:44 -08:00
Plastered_Crab 7a0b294d3d Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2025-02-21 15:04:14 -08:00
Plastered_Crab ad1312d077 Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/Category.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
2025-02-21 14:36:04 -08:00
Plastered_Crab d65d7b779f Add delay to NestSensor 2025-02-12 15:49:45 -08:00
Plastered_Crab 7ce25d8158 Wild pokemon will claim the nests they build for themselves 2025-02-12 15:20:55 -08:00
Plastered_Crab 178d7293fb Okay they go to nests in single pairs for real this time 2025-02-12 14:29:04 -08:00
Plastered_Crab ba0bba2202 Code change for parity with the pokemingle branch 2025-02-12 13:32:16 -08:00
Plastered_Crab 29d5ce5f0e Pairs of wild pokemon will "claim" a nest before they get there 2025-02-12 12:57:25 -08:00
Plastered_Crab b9ebfc17ed Merge branch '1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-02-12 09:36:10 -08:00
Plastered_Crab 2e28792c92 Add variable to NestBlockEntity to allow wild mons to "claim" them before breeding so hoards don't run to a single nest 2025-02-12 09:02:21 -08:00
plastered_crab 648d15403f Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-02-11 12:09:17 -08:00
plastered_crab 3fe3f521ee Fix regional form predicate registration 2025-02-11 12:08:54 -08:00
plastered_crab 0ff638a0ca Merge remote-tracking branch 'origin/1.21.1-breeding-pokemingle.com' into 1.21.1-breeding-pokemingle.com 2025-02-11 11:25:02 -08:00
plastered_crab 485b5f46f6 Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-02-11 11:24:42 -08:00
plastered_crab b7c2063510 Add ALL Gen 1 - Gen 9 Egg predicate files updated YIPPEE 2025-02-11 11:01:46 -08:00
plastered_crab 94d7ae5290 Add ALL Gen 1 - Gen 9 Egg item model predicate files 2025-02-11 08:37:28 -08:00
plastered_crab d75d5da250 Add ALL gen 9 Egg sprites! That is ALL NOW (THANK YOU CUMULO) 2025-02-11 08:27:43 -08:00
plastered_crab d3cd1be24e Add ALL gen 8 Egg sprites! (THANK YOU CUMULO) 2025-02-11 07:44:07 -08:00
Plastered_Crab d1f11316ec Wild pokemon are more mindful of biomes/materials when making their own nests 2025-02-10 16:47:30 -08:00
plastered_crab 1e50bd999b Wild pokemon now gather nesting material dropped on the ground to make tree, water, snow, etc nests! 2025-02-09 22:34:45 -08:00
plastered_crab 0257f6c6a0 Wild pokemon now have a chance to gather materials around them to make nests for themselves if none are around 2025-02-07 22:15:58 -08:00
Plastered_Crab a3bc1cf72b Wild pokemon will try to detect nest materials to build their own nests if there is not one nearby 2025-02-07 17:12:59 -08:00
Plastered_Crab e236632c67 Wild Pokemon Parents will attack the player until they give the egg back >:( 2025-02-06 17:32:25 -08:00
Plastered_Crab 4730d97898 Wild Pokemon Parents will now give up searching for a lost egg after some time 2025-02-06 15:20:30 -08:00
Plastered_Crab 36994a134d Wild Pokemon Parents will now attack players who take their eggs and bring eggs back 2025-02-06 13:09:36 -08:00
Plastered_Crab 97899b3fa9 Wild pokemon parents will look for their lost egg and bring it back to the nest 2025-02-06 11:56:25 -08:00
plastered_crab 713bd6d6ef Tons of code for wild parent pokemon to know when their egg is missing and to go looking for it (not working fully yet) 2025-02-06 08:01:47 -08:00
plastered_crab 48736fad1f Fix aggression once more since refactor 2025-02-03 20:00:02 -08:00
plastered_crab 5a47deb9a7 Ground pokemon will ignore nests in trees and other areas they cannot pathfind to! 2025-02-01 22:59:33 -08:00
plastered_crab 4b01facb4f Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-02-01 21:08:48 -08:00
plastered_crab 158d4d6aed Tweaking generation of wild nests 2025-02-01 21:03:58 -08:00
plastered_crab ab91776d97 Natural generation of all 15 wild nests almost working properly now 2025-02-01 18:08:35 -08:00
plastered_crab 286ffe7219 More work to add new world gen wild nests 2025-02-01 01:39:21 -08:00
plastered_crab 79da4cb8a8 Add new world gen files for nests (thank you Slippy!) 2025-02-01 01:35:02 -08:00
Plastered_Crab 32f3c75d45 Fix to make Snow Nests only spawn where it is cold 2025-01-31 15:46:46 -08:00
Plastered_Crab acc81d60ad Remove test code for spawning pokemon 2025-01-31 15:31:53 -08:00
Plastered_Crab 4b093a0072 Merge branch '1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-01-31 14:39:41 -08:00
Plastered_Crab e0c57007db Wild Nests test generation on ground, in trees, and in snow 2025-01-31 14:38:54 -08:00
Plastered_Crab 66b3579a9b Wild Nests test generation on ground, in trees, and in snow 2025-01-31 14:34:41 -08:00
Plastered_Crab 0981bb71ce Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2025-01-31 09:47:19 -08:00
Plastered_Crab 6b762cd6d3 Add all Gen 6 and Gen 7 Egg Sprites 2025-01-31 09:47:03 -08:00
Plastered_Crab 581c466c0e Add all Gen 6 and Gen 7 Egg Sprites 2025-01-31 09:46:05 -08:00
Plastered_Crab 5622c65fca Add all Gen 6 and Gen 7 Egg Sprites 2025-01-31 09:45:55 -08:00
Plastered_Crab 51c6ea576b Add all Gen 6 and Gen 7 Egg Sprites 2025-01-31 09:42:24 -08:00
plastered_crab b7b8fe1fb9 fix some sillies 2025-01-31 06:29:51 -08:00
plastered_crab 1634a20464 Config for increased hatching rates in the nether for nests and incubators 2025-01-30 23:57:24 -08:00
plastered_crab 7190c33dab Test bounding box data for 9 new nests coming later 2025-01-30 20:38:26 -08:00
Plastered_Crab 8ea20925d3 Mons with only Water 1/2/3 egg groups can breed at underwater nests. Mons with water + other egg groups can breed at both 2025-01-30 16:52:22 -08:00
Plastered_Crab d4c7ce4eb9 Merge branch 'smarterer-pokemons' into 1.21.1-breeding-pokemingle.com 2025-01-30 15:05:15 -08:00
Plastered_Crab 625237457a move initialization of config to be before CobblemonItems 2025-01-30 12:50:23 -08:00
Plastered_Crab 0f21173f5f move initialization of config to be before CobblemonItems 2025-01-30 12:48:18 -08:00
Plastered_Crab 7fda4570c6 More preparations for the other 9 nests 2025-01-30 10:46:30 -08:00
Plastered_Crab e0aae17f37 Prepare for the other 9 types of nests 2025-01-30 10:14:01 -08:00
plastered_crab 32b4459e19 Adjust BreedAtNest brain task to account for Destiny Knot durability via config 2025-01-30 07:38:06 -08:00
plastered_crab 9072a7ac3c Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/breeding/BreedingLogic.kt
2025-01-30 07:31:50 -08:00
plastered_crab 0e39874f9a Added logic for Destiny Knot durability IF the configs are set to true 2025-01-30 06:31:38 -08:00
plastered_crab aa5d116910 Added config for Destiny Knot Durability and if it breaks or not during breeding 2025-01-30 06:22:21 -08:00
plastered_crab 1f49d0d21b Added config Masuda method & config Legend, Genderless, and Ditto breeding 2025-01-29 23:40:43 -08:00
Plastered_Crab 05a612bf79 Merge branch '1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-01-29 13:24:06 -08:00
Plastered_Crab ab0d839006 Comment out possibly unused access wideners 2025-01-29 13:22:46 -08:00
Plastered_Crab 56b061c93d Optimize bounding box for Incubators 2025-01-29 11:39:03 -08:00
Plastered_Crab ec97e16609 Fix faulty bounding boxes on Eggs, Nests, and Incubators! 2025-01-29 11:32:55 -08:00
plastered_crab e4ce58414c Changes to the 7 new nest types 🪺🥺 2025-01-29 07:41:45 -08:00
plastered_crab b07856ed58 Add 7 new nest types 🪺🥺 2025-01-28 21:51:17 -08:00
plastered_crab b7d2f38490 More changes to the MateSensor to try to "pair" mates together 2025-01-28 17:56:26 -08:00
plastered_crab 295185c1cf Change to the MateSensor to try to "pair" mates together 2025-01-28 10:15:22 -08:00
plastered_crab f0d8ee21d9 No more pokemon swingers partys (hopefully) 2025-01-28 09:20:43 -08:00
plastered_crab b203020b4d Pokemon now have a cooldown memory for checking for near mates + compat checks 2025-01-28 08:06:52 -08:00
plastered_crab 5962149da8 Cancel path to nest task if an egg is in it 2025-01-28 06:51:25 -08:00
plastered_crab 24101bed08 Fixes to AI pathing to nests 2025-01-27 08:10:38 -08:00
plastered_crab 321736657e AI breeding is broken once more, but some progress trying to fix 2025-01-25 20:04:21 -08:00
plastered_crab e50309871a Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/breeding/BreedingLogic.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/NestBlock.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/NestBlockEntity.kt
2025-01-24 22:51:58 -08:00
plastered_crab 36cd7e426a Fix give pokemon egg command (it is slow for some reason) 2025-01-24 16:47:37 -08:00
plastered_crab 6660747b31 Fix give pokemon egg command (it is slow for some reason) 2025-01-24 14:55:57 -08:00
plastered_crab c021f28dc7 Eggs now animate based on how ready they are to hatch! :D 2025-01-24 14:42:22 -08:00
plastered_crab a7a2eceda6 More work to refactor the pokemon ai breeding tasks 2025-01-24 06:15:28 -08:00
plastered_crab a4cac592e6 Merge branch 'refs/heads/smarterer-pokemons' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/resources/data/cobblemon/brain_presets/pokemon/pokemon_core.json
2025-01-23 18:13:16 -08:00
plastered_crab b29c6e0d9c Merge branch 'refs/heads/1.21.1-breeding-egganims' into 1.21.1-breeding 2025-01-23 18:06:09 -08:00
amo ba9ea5b793 Add eggmodelrepo to posableModelRepositories so anims and groups are registered 2025-01-23 23:30:36 +00:00
Plastered_Crab 47d5ac4108 trying to get these eggs to wiggle 2025-01-23 09:56:02 -08:00
plastered_crab c6df8bbb61 Why won't these damn egg WIGGLE 2025-01-23 07:12:41 -08:00
plastered_crab 97055b7ede so close to getting egg animations working (no more crashing) 2025-01-22 21:45:34 -08:00
plastered_crab df64ac268e change to Bedrock egg structure 2025-01-22 21:12:28 -08:00
plastered_crab 541a3e257b change to Bedrock egg structure 2025-01-22 19:53:22 -08:00
plastered_crab 1a995dfca8 change to Bedrock egg structure 2025-01-22 19:23:43 -08:00
plastered_crab 9aef6b02ad change to Bedrock egg structure 2025-01-22 19:22:00 -08:00
Plastered_Crab f914f20283 Incubator Recipe 2025-01-22 16:28:10 -08:00
Plastered_Crab 03e558efbb Incubator Recipe 2025-01-22 16:27:54 -08:00
Plastered_Crab 925a0551b2 Nests can only be mined with silk touch + break when block underneath is broken 2025-01-22 16:04:32 -08:00
Plastered_Crab 1677f9fe89 Some preparations for Egg animations based on Ready to Hatch State 2025-01-22 15:30:10 -08:00
Plastered_Crab 1db4c90479 Egg incubators now have a little light in them that lights up when it has an egg 💡🥚 2025-01-22 14:22:52 -08:00
Plastered_Crab e3beeff5a8 Egg Incubators send out Comparator values if they are ready to hatch 🧪👨‍🏫 2025-01-22 12:18:03 -08:00
Plastered_Crab 52a6d84041 Protections and Comparator code for Incubators 🧪👨‍🏫 2025-01-22 11:14:27 -08:00
plastered_crab 84f2183f36 Eggs can be hoppered into Incubators :D 2025-01-22 07:57:56 -08:00
plastered_crab 492cd7bc9e Work towards hoppers working on nests :D 2025-01-21 22:09:04 -08:00
Plastered_Crab c21a25610c Add all Gen 5 Egg Sprites 2025-01-21 11:40:47 -08:00
plastered_crab 4eed3c06a7 Work towards Eggs showing patterns for forms 2025-01-21 09:44:05 -08:00
plastered_crab 7f804445c4 You can make a Charizard breed with Blastoise to make Bulbasaur now (forms don't work...) 2025-01-20 22:55:38 -08:00
plastered_crab 89eee30488 Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/breeding/BreedingLogic.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/breeding/BreedingLogicManager.kt
2025-01-20 22:36:30 -08:00
plastered_crab b386a20351 You can make a Charizard breed with Blastoise to make Bulbasaur now (forms don't work...) 2025-01-20 22:33:23 -08:00
Plastered_Crab ac06bc6103 Custom Breeding rules are almost fully functional 2025-01-20 16:15:09 -08:00
plastered_crab cda8697dd9 You can now catch a pokemon in wild Doubles battles when there is only 1 mon left 2025-01-19 22:33:11 -08:00
plastered_crab f7f7400163 Parents can no longer move to kill you during a wild Double Battle!✋🛑🥚😡 2025-01-19 21:05:32 -08:00
plastered_crab ea0c4731d8 Merge remote-tracking branch 'origin/1.21.1-breeding-pokemingle.com' into 1.21.1-breeding-pokemingle.com 2025-01-19 19:34:02 -08:00
plastered_crab 204b923289 Merge branch 'refs/heads/smarterer-pokemons' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonSpecies.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/FormData.kt
#	common/src/main/resources/data/cobblemon/brain_presets/pokemon/pokemon_core.json
2025-01-19 18:54:12 -08:00
Plastered_Crab 6f34b6347d Pokemon no longer want to mate during battle 😳😳 2025-01-18 20:46:31 -08:00
Plastered_Crab a935edbb55 You can now Double Battle with the parents of a nest 2025-01-18 20:22:02 -08:00
Plastered_Crab e58c3b9422 Pokemon will now warn players when they start to get too close to the nest, then will attack if they get closer 2025-01-18 17:20:56 -08:00
Plastered_Crab a5ddc556f3 Merge remote-tracking branch 'origin/1.21.1-breeding-pokemingle.com' into 1.21.1-breeding-pokemingle.com 2025-01-18 15:27:37 -08:00
Plastered_Crab a541cc3e55 Merge branch '1.21.1-breeding' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/resources/data/cobblemon/species/generation1/growlithe.json
#	common/src/main/resources/data/cobblemon/species/generation3/zigzagoon.json
2025-01-18 15:27:10 -08:00
Plastered_Crab e355bdd68a Add ALL gen 4 egg sprites 2025-01-18 15:24:00 -08:00
Plastered_Crab d687d91090 Add ALL gen 4 egg sprites 2025-01-18 15:23:46 -08:00
Plastered_Crab 6b38147c23 Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2025-01-18 15:09:49 -08:00
Plastered_Crab 0241dbcfa1 Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/tags/CobblemonItemTags.kt
2025-01-18 15:09:35 -08:00
plastered_crab b2e641e269 Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-01-17 22:48:38 -08:00
plastered_crab 63f32cb79c Add all gen 3 Egg block textures🥚 2025-01-17 22:48:04 -08:00
plastered_crab 7b05868c5a Add all gen 3 Egg block textures🥚 2025-01-17 22:46:51 -08:00
plastered_crab c4fd0d6fd7 Wild Pokemon attack players close to their nests 🧍🥚😤👊 2025-01-17 21:57:28 -08:00
plastered_crab a4f0eb999c dumb commit to make you not read my horrible sentence in the last commit 2025-01-17 20:22:17 -08:00
plastered_crab 85b7231ff6 Wild pokemon will patrolling the nest will now phyiscally beat up the player that gets too close to the nest 🧍🥚😤👊 2025-01-17 20:20:52 -08:00
plastered_crab 0f3d0008e9 Wild pokemon will now patrol a radius around their nest AND get angry at visible players that get near the nest 2025-01-17 18:40:37 -08:00
plastered_crab 74addf45c3 Pastured Pokemon will only breed in Player-placed nests, wild pokemon only breed in wild nests 2025-01-17 14:43:01 -08:00
Plastered_Crab f97a8a8475 prep work for wild pokemon ignoring player nests and owned pokemon ignoring wild nests 🥚🪺 2025-01-16 16:20:59 -08:00
Plastered_Crab e8cc212968 Pokemon now get tired from mating 😴🫃🏻🥚 2025-01-16 16:05:30 -08:00
plastered_crab 7918924c1c Add test egg cooldown system 2025-01-16 10:59:16 -08:00
plastered_crab 28a41860b9 Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com 2025-01-16 09:52:17 -08:00
plastered_crab 20ef700422 Add all gen 3 Egg Sprites 2025-01-16 09:24:20 -08:00
plastered_crab c017863ef8 Add Unown Egg sprite 2025-01-16 08:48:59 -08:00
Plastered_Crab d28ac2facf Pokemon now listen to their brains that it is time to MATE 🫃🏻🥚 2025-01-15 13:49:20 -08:00
Plastered_Crab b391f13b11 Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2025-01-15 09:28:57 -08:00
plastered_crab c2f29a0dc3 Alter Breeding Logic to prepare for more Brain stuff 2025-01-15 07:19:45 -08:00
plastered_crab 0c4a148c52 Merge branch 'refs/heads/1.21.1-breeding' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
2025-01-15 07:18:49 -08:00
plastered_crab 5c4aa543fa Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2025-01-15 06:08:18 -08:00
plastered_crab d3a9b50c03 Add gen 2 egg sprites (minus unown) 2025-01-15 06:07:13 -08:00
Plastered_Crab 46962d95fc Pokemon AI is starting to learn how to find a mate and go to a nest to breed 2025-01-13 15:13:23 -08:00
plastered_crab ea50837750 Changes to AI driven Mating tasks 2025-01-13 07:42:13 -08:00
plastered_crab b03fe17e8c Start of AI driven Mating tasks 2025-01-12 20:27:07 -08:00
Guilherme Pereira 16cf5ecbb7 add '/givepokemonegg' and '/giveallpokemonegg' commands 2025-01-12 18:33:09 -03:00
plastered_crab 1b34278ec4 Merge branch 'refs/heads/smarterer-pokemons' into 1.21.1-breeding-pokemingle.com
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonMemories.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonSensors.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/FormData.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Species.kt
#	common/src/main/resources/data/cobblemon/flows/battle_victory/npc_battle_end_scripts.molang
2025-01-12 13:32:38 -08:00
plastered_crab 045d5a8a5e Start to the new Pokemon dating site 2025-01-12 13:26:38 -08:00
plastered_crab 5729c9d5be Egg tweaks 2025-01-12 09:55:32 -08:00
plastered_crab a286584c32 Egg sprites are working now! YIPPEEE 2025-01-12 09:37:09 -08:00
plastered_crab a5895aaa7c Move sprites and egg item model file 2025-01-12 00:46:45 -08:00
plastered_crab 0ab6fef566 Added more predicate files for sprites 2025-01-11 22:51:48 -08:00
plastered_crab a973d21bc0 Added TONS of Egg sprites :D 2025-01-11 22:48:29 -08:00
plastered_crab e149d3f4c8 Add more egg sprite prep 2025-01-11 14:41:44 -08:00
plastered_crab adfde3e236 Add more egg sprite prep 2025-01-11 14:41:23 -08:00
plastered_crab 18cdf705ed Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/item/EggItemRenderer.kt
#	common/src/main/resources/assets/cobblemon/models/item/egg.json
#	common/src/main/resources/assets/cobblemon/textures/egg_patterns/inventory/charmander_egg.png
2025-01-11 00:20:37 -08:00
plastered_crab e5730a5119 Add egg test sprites and get model predicates for sprites working 2025-01-11 00:17:06 -08:00
plastered_crab b295cd7bef Merge branch 'refs/heads/1.21.1-breeding-sprite-test' into 1.21.1-breeding 2025-01-10 23:26:15 -08:00
plastered_crab a168715b3c Merge branch 'refs/heads/main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
2025-01-10 23:21:42 -08:00
plastered_crab 55f8212451 Holy Schmoly! So many egg files! 2025-01-10 23:09:42 -08:00
plastered_crab c5e7c79c2a Changes to egg sprite renderer 2025-01-10 17:21:04 -08:00
Plastered_Crab d4a44b4977 Merge branch 'main' into 1.21.1-breeding 2025-01-10 14:20:55 -08:00
Plastered_Crab f9f0668f6b Back to basics to start fresh later 2025-01-10 13:59:56 -08:00
Plastered_Crab d01beb3ba8 plz sprites be nice 2025-01-10 13:36:41 -08:00
Plastered_Crab 3cac052772 Bro why won't these sprites render 2025-01-10 13:29:51 -08:00
Plastered_Crab 84bc8bc426 Added New Egg sprites to test with 2025-01-10 10:02:50 -08:00
Plastered_Crab 98a25908c2 More stuff to try to get egg sprites working 2025-01-10 09:02:25 -08:00
plastered_crab 4ed3d41eb1 Progress towards trying to get Egg Items to render separately from the Egg Blocks 2025-01-09 22:04:02 -08:00
Plastered_Crab 672fd7365f OOPS I should have deleted this test Egg Move rule lmao 2025-01-09 15:42:25 -08:00
Plastered_Crab 39a16beb14 Breeding Logic reworked + Moves passed down work now according to games (Egg moves, Tm/Hm, learned moves, etc) + Added new datapackable Egg Move system for custom egg move rules 2025-01-09 15:38:58 -08:00
plastered_crab 32dbd1f8c9 Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2025-01-07 01:30:25 -08:00
plastered_crab bcc23ec4fc Merge branch 'refs/heads/main' into 1.21.1-breeding 2025-01-07 01:30:08 -08:00
luke 143fc8256b Added gen 2 non-regional egg textures 2025-01-02 20:23:34 -07:00
luke 70d753d1d3 Incubator hatch handling 2024-12-29 00:33:13 -07:00
luke 00a67b22df Fixed incubator interaction 2024-12-28 11:11:24 -07:00
luke 12811fe504 Implemented most incubator block logic 2024-12-27 18:42:40 -07:00
Plastered_Crab 40ffb99928 Merge branch 'main' into 1.21.1-breeding 2024-12-26 13:49:35 -08:00
Plastered_Crab 78fb8fb834 Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2024-12-26 13:40:45 -08:00
Plastered_Crab 614663a49c Merge branch 'main' into 1.21.1-breeding 2024-12-26 13:27:46 -08:00
plastered_crab ca9955f4cd Some more changes to try to prepare for eggs to render as items properly 2024-12-24 02:14:19 -08:00
Plastered_Crab 2f40c5d71b Pokemon cry when they hatch from eggs now! CUTE >:D 2024-12-20 15:22:01 -08:00
Plastered_Crab 1a84ca4ba0 Eggs in nests can now hatch!! (in a basic sense) they just don't cry when hatching yet though :( 2024-12-20 15:04:24 -08:00
Plastered_Crab 5f29986d41 Fix some persistent renderings of eggs in the incubator and ground and fix some item models 2024-12-20 12:49:02 -08:00
Plastered_Crab bfb63088c1 Increase time for wild nests to have a family 2024-12-20 10:12:33 -08:00
Plastered_Crab 626bd7f4f4 Now created eggs are always the base pre-evolution (yippeee) 2024-12-20 10:10:27 -08:00
Guilherme Pereira bff941264c temporary workaround for nest spawner not working 2024-12-20 12:30:10 -03:00
plastered_crab 10b472e980 More work on trying to get parent Pokemon to spawn at wild nests 2024-12-20 00:05:22 -08:00
Plastered_Crab 7888a8ac31 Added NestSpawner (and friends). Test to have Charizard guarding nest 2024-12-19 21:25:57 -08:00
plastered_crab d79adfc0be Very early prototype system for "Wild Pokemon Nests" 2024-12-19 16:31:38 -08:00
plastered_crab e4baf5fe3e Pastures recalculate breeding pairs after recalling pokemon 2024-12-19 13:38:07 -08:00
plastered_crab 3013c30084 Eggs will have two parents always now (don't ask about before...) 2024-12-19 13:28:29 -08:00
plastered_crab fc10b550cd Fix Egg position in DisplayCaseRenderer 2024-12-19 13:14:43 -08:00
plastered_crab 514787a1c4 Eggs can now be placed in Nests, Incubators, Display Cases, and on the ground! 2024-12-19 12:32:46 -08:00
plastered_crab c32f3c5581 Fix some issues with the Egg Incubator block and renderer 2024-12-19 01:27:39 -08:00
plastered_crab 94b9b7ade3 Add Egg Incubator block and entity/renderer classes 2024-12-19 00:59:54 -08:00
Plastered_Crab ee33b4292c Added more textures for some species eggs (thank you Cumulo) 2024-12-18 15:51:43 -08:00
Plastered_Crab 271e74d6f2 Added more textures for some species eggs (thank you Cumulo) 2024-12-18 15:50:53 -08:00
Plastered_Crab 2ea41c3dae Adding some placeholder starter/legendary egg textures for testing 2024-12-18 14:47:49 -08:00
Plastered_Crab a6d93aba17 EGGS RENDER IN NESTS NOW!!! WOOOO 2024-12-18 13:55:44 -08:00
Plastered_Crab db1711b76f More attempts to render the eggs (I hate these things) 2024-12-18 10:23:34 -08:00
Plastered_Crab ab5b2e8fdf Add EggComponent and try to get Server to sync to Client (I AM SO CLOSE) 2024-12-17 17:02:16 -08:00
Plastered_Crab efa4028948 Change Egg data to prevent cheesing, and try to make them render via texture instead of overlay 2024-12-17 14:13:29 -08:00
Plastered_Crab 65529474cb Merge main into Breeding branch to keep it up to date 2024-12-17 10:19:04 -08:00
Plastered_Crab 7d756249ae Merge branch 'main' into breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/fishing/FishingBait.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/moves/MoveSet.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/PokemonPastureBlockEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/command/TestCommand.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/fishing/PokeRodFishingBobberEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/item/group/CobblemonItemGroups.kt
#	common/src/main/resources/cobblemon-common.accesswidener
2024-12-17 10:14:54 -08:00
apion 3fcfd2ff6d We can go into worlds pog 2024-08-02 18:06:02 -04:00
apion 98545541e0 Port to 1.21 (it compiles at least) 2024-08-02 05:44:33 -04:00
apion 56820ad626 Merge branch 'refs/heads/sega_bass_pro_fishing_2' into breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlockEntities.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/tags/CobblemonItemTags.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/BerryBlockEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/PokemonPastureBlockEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/atlas/CobblemonAtlases.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/BasicBlockEntityRenderState.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/BerryBlockRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/FossilAnalyzerRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/RestorationTankRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/layer/CobblemonRenderLayers.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/command/TestCommand.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/item/group/CobblemonItemGroups.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt
#	common/src/main/resources/cobblemon-common.accesswidener
#	common/src/main/resources/data/cobblemon/loot_table/blocks/egg.json
#	common/src/main/resources/data/cobblemon/tags/item/defaulting_poke_balls.json
2024-08-02 03:03:41 -04:00
Plastered_Crab 64546b25f2 - Tweaked Breeding Logic for passing IVs 2024-02-28 12:35:22 -08:00
Plastered_Crab d5496fb465 - Adjusted the Heart Breeding particles to look more vanilla 2024-02-28 12:31:57 -08:00
jeffw773 bcd2474d5f - Pokemon emit Heart particles when bred + logic for nests with eggs is tweaked 2024-02-27 22:13:16 -08:00
apion e331b959c8 Fixed IV logic 2024-02-25 07:49:20 -05:00
apion ce159ac742 Allow insertion of eggs to nests 2024-02-14 17:20:09 -05:00
apion 3e4eaf0397 Fixed another persistence issue 2024-02-14 13:12:30 -05:00
apion 2a2308b4a4 Fixed some desync/persistence issues 2024-02-14 12:54:19 -05:00
apion 47511bac89 Made eggs hatchable 2024-02-13 21:54:47 -05:00
apion c6a5e69f91 Merge remote-tracking branch 'origin/main' into breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/FossilAnalyzerRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/FossilTubeRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/layer/CobblemonRenderLayers.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt
2024-02-13 20:58:48 -05:00
apion 32a9185cc1 Made egg blocks drop their egg 2024-02-13 20:09:25 -05:00
apion ec54aeb1fd Fixed crash if parent pokemon had no hidden ability options, sped up egg sprite rendering a bit 2024-02-13 17:55:27 -05:00
apion 9aab1113fd Fixed inventory lighting 2024-02-13 16:21:51 -05:00
apion 86578cd266 Got 3D sprite model generation working properly, except for lighting in inventory. Also, licenses 2024-02-13 09:43:03 -05:00
apion 566d74a638 Move egg inventory rendering to generate and use BakedModels 2024-02-12 14:21:42 -05:00
apion 23988b55a1 Egg inventory rendering fully working 2024-02-10 01:06:57 -05:00
apion d5418ed5c7 Got egg rendering working in a less sus way 2024-02-09 22:54:06 -05:00
apion 5318b1470d Eggs now kinda work in inventory 2024-01-30 23:29:57 -05:00
apion 0ca91eb071 Merge branch 'main' into breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/BerryBlockEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/BerryBlockRenderer.kt
#	common/src/main/resources/cobblemon-common.accesswidener
2024-01-30 22:58:08 -05:00
apion 7d31719cd1 Update patterns to include inv sprites 2024-01-30 22:51:45 -05:00
apion 25ed3d28f4 Added placeable eggs 2024-01-30 20:22:21 -05:00
apion 5df64d038b Start working on adding egg item, and egg item rendering 2024-01-30 16:40:56 -05:00
apion ec6d838ea6 Fixed render distances for all BERs 2024-01-08 13:27:34 -05:00
apion 6bea8aafe6 Added a bunch of egg patterns 2024-01-08 13:12:13 -05:00
Lily 5bbf5d2d85 Added documentation to BreedingLogic, added Gender and PokeBall functionality 2024-01-02 21:59:59 -05:00
apion 6d09590374 Fixed lighting and tinting issues on eggs 2024-01-02 14:18:18 -05:00
apion 0e0ebc0b48 Pasture blocks now properly update nest entities, fix some rendering bugs, fix block entity sync issues 2024-01-02 12:40:27 -05:00
apion 0c4a4ddda1 Generified berry rendering optimizations and apply them to eggs, added new data class for egg pokemon, added a bunch of nbt serializer stuff 2024-01-02 10:01:11 -05:00
apion 1efeddc875 Merge branch 'berry-rendering' into breeding 2024-01-02 07:56:31 -05:00
apion 82c6e8f42d Merge branch 'main' into breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt
2024-01-02 07:56:19 -05:00
apion 5ff522bb85 Added egg rendering, and nest block entity 2024-01-02 06:59:52 -05:00
apion b2b73575d4 Add some API stuff 2024-01-02 04:41:41 -05:00
apion d7bbccab08 Move BerryBlockRenderState to be client specific 2024-01-02 04:26:52 -05:00
apion 21b8fe8474 Fix flower texture 2024-01-02 04:15:55 -05:00
apion 94c210efd4 Big cool rendering stuff 2024-01-02 04:06:46 -05:00
apion d3100be95a Refined breeding logic 2023-12-29 23:17:51 -05:00
apion 3353c15af6 Add nest detection in pasture block 2023-12-20 23:26:35 -05:00
apion 1cced11431 Merge conflicts lul 2023-12-20 22:54:06 -05:00
apion 646ad5606a Merge branch 'archaeology' into breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
2023-12-20 22:50:17 -05:00
apion 8214e848f9 Added nest blocks and oak twig texture + licenses :D 2023-12-20 22:18:26 -05:00
apion ece9c63121 Added breeding events, refactored logic a bit 2023-12-20 20:16:08 -05:00
apion ce81f07aa2 Started work on pasture behavior for breeding 2023-12-19 19:48:47 -05:00
apion 9bcaa6dfad Merge branch 'main' into breeding 2023-12-16 21:19:45 -05:00
apion 295efe8c11 Merge branch 'breeding' of https://gitlab.com/cable-mc/cobblemon into breeding 2023-12-16 21:17:43 -05:00
apion 5cff811aab Merge branch 'main' into breeding 2023-12-16 21:14:57 -05:00
apion 00e721d714 Add nest assets 2023-11-20 07:01:10 -05:00
Nick 0c602027cb Begin work on an API for breeding logic 2023-10-26 22:17:55 -07:00
Lily 7070369cea breeding logic 2023-10-23 22:10:36 -04:00
1838 changed files with 29541 additions and 139 deletions
@@ -194,6 +194,8 @@ object Cobblemon {
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
}
this.loadConfig()
implementation.registerPermissionValidator()
implementation.registerSoundEvents()
implementation.registerDataComponents()
@@ -216,7 +218,6 @@ object Cobblemon {
ExperienceGroups.registerDefaults()
CaptureCalculators.registerDefaults()
this.loadConfig()
// CobblemonBlockPredicates.touch()
CobblemonOrePlacedFeatures.register()
CobblemonPlacedFeatures.register()
@@ -18,6 +18,7 @@ object CobblemonActivities {
val POKEMON_SLEEPING_ACTIVITY = register(Activity("pokemon_sleeping"))
val POKEMON_GROW_CROP = register(Activity("pokemon_grow_crop"))
val POKEMON_SLEEP_ON_TRAINER_BED = register(Activity("pokemon_sleep_on_trainer_bed"))
val GUARD_NEST = register(Activity("guard_nest"))
fun register(activity: Activity): Activity {
activities.add(activity)
@@ -49,6 +49,11 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
@JvmField
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN).build(null))
@JvmField
val GROTTO_BLOCK: BlockEntityType<GrottoBlockEntity> = this.create("grotto_block",
BlockEntityType.Builder.of(::GrottoBlockEntity, CobblemonBlocks.GROTTO_BLOCK).build(null)
)
@JvmField
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
CobblemonBlocks.GILDED_CHEST,
@@ -82,6 +87,46 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
).build(null)
)
val NEST: BlockEntityType<NestBlockEntity> = this.create("nest",
BlockEntityType.Builder.of(
::NestBlockEntity,
CobblemonBlocks.NEST_STICK,
CobblemonBlocks.NEST_LEAF,
CobblemonBlocks.NEST_STONE,
CobblemonBlocks.NEST_DEEPSLATE,
CobblemonBlocks.NEST_BASALT,
CobblemonBlocks.NEST_SNOW,
CobblemonBlocks.NEST_NETHER,
CobblemonBlocks.NEST_SOUL_SAND,
CobblemonBlocks.NEST_WARM_OCEAN,
CobblemonBlocks.NEST_SEAGRASS,
CobblemonBlocks.NEST_LUSH,
CobblemonBlocks.NEST_HAY,
CobblemonBlocks.NEST_SAND,
CobblemonBlocks.NEST_RED_SAND,
CobblemonBlocks.NEST_CHORUS,
CobblemonBlocks.WATER_NEST,
CobblemonBlocks.CAVE_NEST,
CobblemonBlocks.BASIC_NEST
).build(null))
val EGG: BlockEntityType<EggBlockEntity> = this.create("egg",
BlockEntityType.Builder.of(
::EggBlockEntity,
CobblemonBlocks.EGG
).build(null)
)
@JvmField
val INCUBATOR: BlockEntityType<IncubatorBlockEntity> = this.create("incubator",
BlockEntityType.Builder.of(
::IncubatorBlockEntity,
CobblemonBlocks.INCUBATOR
).build(null)
)
@JvmField
val DISPLAY_CASE: BlockEntityType<DisplayCaseBlockEntity> = this.create("display_case",
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
@@ -478,6 +478,61 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val YACHE_BERRY = this.berryBlock("yache")
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
val NEST_STICK = nestBlock(NestBlock.NestVariant.STICK)
val NEST_LEAF = nestBlock(NestBlock.NestVariant.LEAF)
val NEST_STONE = nestBlock(NestBlock.NestVariant.STONE)
val NEST_DEEPSLATE = nestBlock(NestBlock.NestVariant.DEEPSLATE)
val NEST_BASALT = nestBlock(NestBlock.NestVariant.BASALT)
val NEST_SNOW = nestBlock(NestBlock.NestVariant.SNOW)
val NEST_NETHER = this.create(
"nest_nether",
NestBlock(NestBlock.NestVariant.NETHER, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY).lightLevel { 2 })
)
val NEST_SOUL_SAND = nestBlock(NestBlock.NestVariant.SOUL_SAND)
val NEST_WARM_OCEAN = this.create(
"nest_warm_ocean",
NestBlock(NestBlock.NestVariant.WARM_OCEAN, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY).lightLevel { 6 })
)
val NEST_SEAGRASS = nestBlock(NestBlock.NestVariant.SEAGRASS)
val NEST_LUSH = nestBlock(NestBlock.NestVariant.LUSH)
val NEST_HAY = nestBlock(NestBlock.NestVariant.HAY)
val NEST_SAND = nestBlock(NestBlock.NestVariant.SAND)
val NEST_RED_SAND = nestBlock(NestBlock.NestVariant.RED_SAND)
val NEST_CHORUS = nestBlock(NestBlock.NestVariant.CHORUS)
val BASIC_NEST = nestBlock(NestBlock.NestVariant.BASIC)
val CAVE_NEST = nestBlock(NestBlock.NestVariant.CAVE)
//val NETHER_NEST = nestBlock(NestBlock.NestVariant.NETHER)
val WATER_NEST = nestBlock(NestBlock.NestVariant.WATER)
val EGG = this.create("egg", EggBlock(BlockBehaviour.Properties
.ofFullCopy(Blocks.DRAGON_EGG)
.pushReaction(PushReaction.BLOCK)
.sound(SoundType.BONE_BLOCK)
))
@JvmField
val GROTTO_BLOCK = this.create("grotto_block", GrottoBlock(BlockBehaviour.Properties.of()
.mapColor(MapColor.STONE)
.strength(1.5F)
.pushReaction(PushReaction.BLOCK)
))
@JvmField
val INCUBATOR = this.create("incubator", IncubatorBlock(BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_LIGHT_GRAY)
.sound(SoundType.METAL)
.strength(Blocks.COAL_BLOCK.defaultDestroyTime(), Blocks.DIAMOND_ORE.explosionResistance)
.noOcclusion()
.pushReaction(PushReaction.BLOCK)
.lightLevel { state ->
if (state.getValue(IncubatorBlock.HAS_EGG)) 5 else 0 // Emit 5 light when HAS_EGG is true
}
))
init {
/**
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
@@ -506,6 +561,12 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
return block
}
private fun nestBlock(variant: NestBlock.NestVariant): NestBlock {
val identifier = cobblemonResource(variant.serializedName)
val block = NestBlock(variant, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
return this.create(identifier.path, block)
}
/**
* Calls helper method from Vanilla
*/
@@ -60,6 +60,8 @@ object CobblemonCommands {
RenameBoxCommand.register(dispatcher)
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
GivePokemonEggCommand.register(dispatcher)
GiveAllPokemonEggCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
UnlockPCBoxWallpaperCommand.register(dispatcher)
@@ -8,9 +8,12 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.block.entity.EggBlockEntity
import com.cobblemon.mod.common.item.components.HeldItemCapableComponent
import com.cobblemon.mod.common.item.components.PokemonItemComponent
import com.cobblemon.mod.common.item.RodBaitComponent
import com.cobblemon.mod.common.item.components.EggComponent
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.component.DataComponentType
@@ -36,11 +39,18 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(RodBaitComponent.PACKET_CODEC)
.build())
val EGG_DATA: DataComponentType<EggComponent> = create("egg", DataComponentType.builder<EggComponent>()
.persistent(EggComponent.CODEC) // For saving/loading
.networkSynchronized(EggComponent.PACKET_CODEC) // For network synchronization
.build())
fun register() {
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:pokemon_item"), POKEMON_ITEM)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:bait"), BAIT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:egg_data"), EGG_DATA)
}
override val registry = BuiltInRegistries.DATA_COMPONENT_TYPE
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.MintBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.NestBlock
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.item.*
@@ -41,6 +42,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.item.Item.Properties
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.food.FoodProperties
@@ -392,6 +394,33 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val BERRY_JUICE = this.create("berry_juice", BerryJuiceItem())
val NEST_STICK = blockItem(NestBlock.NestVariant.STICK.serializedName, CobblemonBlocks.NEST_STICK)
val NEST_LEAF = blockItem(NestBlock.NestVariant.LEAF.serializedName, CobblemonBlocks.NEST_LEAF)
val NEST_STONE = blockItem(NestBlock.NestVariant.STONE.serializedName, CobblemonBlocks.NEST_STONE)
val NEST_DEEPSLATE = blockItem(NestBlock.NestVariant.DEEPSLATE.serializedName, CobblemonBlocks.NEST_DEEPSLATE)
val NEST_BASALT = blockItem(NestBlock.NestVariant.BASALT.serializedName, CobblemonBlocks.NEST_BASALT)
val NEST_SNOW = blockItem(NestBlock.NestVariant.SNOW.serializedName, CobblemonBlocks.NEST_SNOW)
val NEST_NETHER = blockItem(NestBlock.NestVariant.NETHER.serializedName, CobblemonBlocks.NEST_NETHER)
val NEST_SOUL_SAND = blockItem(NestBlock.NestVariant.SOUL_SAND.serializedName, CobblemonBlocks.NEST_SOUL_SAND)
val NEST_WARM_OCEAN = blockItem(NestBlock.NestVariant.WARM_OCEAN.serializedName, CobblemonBlocks.NEST_WARM_OCEAN)
val NEST_SEAGRASS = blockItem(NestBlock.NestVariant.SEAGRASS.serializedName, CobblemonBlocks.NEST_SEAGRASS)
val NEST_LUSH = blockItem(NestBlock.NestVariant.LUSH.serializedName, CobblemonBlocks.NEST_LUSH)
val NEST_HAY = blockItem(NestBlock.NestVariant.HAY.serializedName, CobblemonBlocks.NEST_HAY)
val NEST_SAND = blockItem(NestBlock.NestVariant.SAND.serializedName, CobblemonBlocks.NEST_SAND)
val NEST_RED_SAND = blockItem(NestBlock.NestVariant.RED_SAND.serializedName, CobblemonBlocks.NEST_RED_SAND)
val NEST_CHORUS = blockItem(NestBlock.NestVariant.CHORUS.serializedName, CobblemonBlocks.NEST_CHORUS)
val BASIC_NEST = blockItem(NestBlock.NestVariant.BASIC.serializedName, CobblemonBlocks.BASIC_NEST)
val CAVE_NEST = blockItem(NestBlock.NestVariant.CAVE.serializedName, CobblemonBlocks.CAVE_NEST)
val WATER_NEST = blockItem(NestBlock.NestVariant.WATER.serializedName, CobblemonBlocks.WATER_NEST)
@JvmField
val INCUBATOR = blockItem("incubator", CobblemonBlocks.INCUBATOR)
@JvmField
val GROTTO_BLOCK = blockItem("grotto_block", CobblemonBlocks.GROTTO_BLOCK)
@JvmField
val GALARICA_NUTS = create("galarica_nuts", GalaricaNutItem())
@@ -705,7 +734,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val COVERT_CLOAK = heldItem("covert_cloak")
@JvmField
val DESTINY_KNOT = heldItem("destiny_knot")
val DESTINY_KNOT = heldItem("destiny_knot", null, if (Cobblemon.config.canDestinyKnotBreak) Properties().durability(Cobblemon.config.destinyKnotDurability) else Properties())
@JvmField
val DRAGON_FANG = heldItem("dragon_fang")
@JvmField
@@ -1225,6 +1254,10 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val FAIRY_GEM = noSettingsItem("fairy_gem")
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
@JvmField
val POKEMON_EGG = eggItem("egg", CobblemonBlocks.EGG, Item.Properties().stacksTo(1))
private fun eggItem(name: String, block: Block, settings: Item.Properties = Item.Properties()): BlockItem = this.create(name, BlockItem(block, settings))
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
@@ -1268,9 +1301,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
}
)
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
private fun heldItem(name: String, remappedName: String? = null, settings:Item.Properties = Item.Properties()): CobblemonItem = create(
name,
CobblemonItem(Item.Properties()).also {
CobblemonItem(settings).also {
if (remappedName != null) {
CobblemonHeldItemManager.registerRemap(it, remappedName)
}
@@ -10,15 +10,18 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.UUIDUtil
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import java.util.Optional
import java.util.UUID
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
object CobblemonMemories {
val memories = mutableMapOf<String, MemoryModuleType<*>>()
@@ -33,6 +36,11 @@ object CobblemonMemories {
val NPC_BATTLING = register("npc_battling", PrimitiveCodec.BOOL)
val DIALOGUES = register<List<ActiveDialogue>>("npc_dialogues")
val ACTIVE_ACTION_EFFECT = register<ActionEffectContext>("active_action_effect")
val POTENTIAL_MATE = register<PokemonEntity>("potential_mate")
val MATE_SCAN_COOLDOWN = register("mate_scan_cooldown", PrimitiveCodec.BOOL)
val NEST_LOCATION = register<BlockPos>("nest_location")
val PATERNAL_NEST_LOCATION = register<BlockPos>("paternal_nest")
val IRRITATED_AT = register<UUID>("irritated_at")
val POKEMON_FLYING = register("pokemon_flying", PrimitiveCodec.BOOL)
val POKEMON_DROWSY = register("pokemon_drowsy", PrimitiveCodec.BOOL)
val POKEMON_SLEEPING = register("pokemon_sleeping", PrimitiveCodec.BOOL)
@@ -41,6 +49,16 @@ object CobblemonMemories {
val TARGETED_BATTLE_POKEMON = register<UUID>("targeted_battle_pokemon")
val NEAREST_VISIBLE_ATTACKER = register<LivingEntity>("nearest_visible_attacker")
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
val EGG_THIEF = register<Player>("egg_thief") // Stores the player stealing the egg
val EGG_LOCATION = register<BlockPos>("egg_location") // Stores the last known location of the egg
val EGG_IN_NEST = register("egg_in_nest", PrimitiveCodec.BOOL)
val READY_TO_GRAB_EGG = register("ready_to_grab_egg", PrimitiveCodec.BOOL)
val HOLDING_EGG = register("holding_egg", PrimitiveCodec.BOOL)
val EGG_SEARCH_COUNTDOWN = register ("egg_search_countdown", PrimitiveCodec.BOOL)
val NESTING_MATERIAL_LOCATION = register<BlockPos>("nesting_material_location")
val SENSED_NEST_MATERIAL = register<ItemEntity>("sensed_nest_material")
val IDEAL_NEST_LOCATION = register<BlockPos>("ideal_nest_location")
val READY_TO_BUILD_NEST = register("ready_to_build_nest", PrimitiveCodec.BOOL)
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
@@ -66,6 +66,7 @@ import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.*
import com.cobblemon.mod.common.net.messages.client.breeding.BreedingRuleRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
@@ -105,9 +106,9 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.OpenPCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.RenamePCBoxPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCBoxPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangePCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
@@ -139,6 +140,7 @@ import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.breeding.TriggerEggHatchPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
@@ -166,6 +168,12 @@ import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPa
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonToPartyPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePartyPokemonToPCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.ReleasePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.SwapPCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.UnlinkPlayerFromPCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.*
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
import com.cobblemon.mod.common.net.messages.server.trade.CancelTradePacket
@@ -183,7 +191,9 @@ import com.cobblemon.mod.common.net.serverhandling.battle.TeamLeaveHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHandler
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
import com.cobblemon.mod.common.net.messages.server.storage.pc.*
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.breeding.TriggerEggHatchHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
@@ -215,6 +225,12 @@ import com.cobblemon.mod.common.net.serverhandling.storage.SwapPCPartyPokemonHan
import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.MovePCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.MovePCPokemonToPartyHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.MovePartyPokemonToPCHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.ReleasePartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.SwapPCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.UnlinkPlayerFromPCHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
@@ -222,6 +238,7 @@ import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHa
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
import com.cobblemon.mod.common.util.server
import net.minecraft.server.level.ServerPlayer
@@ -375,6 +392,8 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PokedexDexSyncPacket.ID, PokedexDexSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FishingBaitRegistrySyncPacket.ID, FishingBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BreedingRuleRegistrySyncPacket.ID, BreedingRuleRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(HabitatSyncPacket.ID, HabitatSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -545,6 +564,9 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, OrientationPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
// Breeding Packets
list.add(PacketRegisterInfo(TriggerEggHatchPacket.ID, TriggerEggHatchPacket::decode, TriggerEggHatchHandler))
// Behaviour Packets
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
@@ -19,7 +19,7 @@ import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
object CobblemonVillagerPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Registry<PoiType>>, PoiType>() {
object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Registry<PoiType>>, PoiType>() {
override val registry: Registry<PoiType> = BuiltInRegistries.POINT_OF_INTEREST_TYPE
override val resourceKey: ResourceKey<Registry<PoiType>> = Registries.POINT_OF_INTEREST_TYPE
@@ -27,6 +27,11 @@ object CobblemonVillagerPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKe
val NURSE_KEY: ResourceKey<PoiType> = createKey("nurse")
@JvmField
val NURSE = create(NURSE_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.HEALING_MACHINE), 1, 1))
@JvmField
val GROTTO_BLOCK_KEY: ResourceKey<PoiType> = createKey("grotto_block")
@JvmField
val GROTTO_BLOCK = create(GROTTO_BLOCK_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.GROTTO_BLOCK), 0, 1))
private fun createKey(string: String): ResourceKey<PoiType> = ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string))
@@ -8,10 +8,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DefendOwnerSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DrowsySensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonAdultSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonGrowableCropSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.*
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
import com.cobblemon.mod.common.entity.sensor.BattlingPokemonSensor
import com.cobblemon.mod.common.entity.sensor.NPCBattlingSensor
@@ -25,6 +22,11 @@ object CobblemonSensors {
val NPC_BATTLING = register("npc_battling", ::NPCBattlingSensor)
val BATTLING_POKEMON = register("battling_pokemon", ::BattlingPokemonSensor)
val MATE_SENSOR = register("mate_sensor", ::MateSensor)
val NEST_SENSOR = register("nest_sensor", ::NestSensor)
val EGG_SENSOR = register("egg_sensor", :: EggSensor)
val NEST_MATERIAL_SENSOR = register("nest_material_sensor", :: NestMaterialSensor)
val IDEAL_NEST_LOCATION_SENSOR = register("ideal_nest_location_sensor", ::IdealNestPlacementSensor)
// val NPC_BATTLING = register("npc_battling", ::NPCBattlingSensor)
val POKEMON_DROWSY = register("pokemon_drowsy", ::DrowsySensor)
@@ -84,6 +84,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val POKEDEX_SCAN_ZOOM_INCREMENT = this.create("item.pokedex.scan_zoom_increment")
@JvmField
val BREEDING_EGG_HATCH = this.create("breeding.egg_hatch")
@JvmField
val ITEM_USE = this.create("item.use")
@JvmField
@@ -24,7 +24,7 @@ object CobblemonVillagerProfessions: PlatformRegistry<Registry<VillagerProfessio
override val resourceKey: ResourceKey<Registry<VillagerProfession>> = Registries.VILLAGER_PROFESSION
@JvmField
val NURSE = profession(CobblemonVillagerPoiTypes.NURSE_KEY, CobblemonSounds.VILLAGER_WORK_NURSE)
val NURSE = profession(CobblemonPoiTypes.NURSE_KEY, CobblemonSounds.VILLAGER_WORK_NURSE)
/**
* Register a villager profession texture override for Cobblemon villagers of the given [VillagerProfession] for specified names.
@@ -0,0 +1,148 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.AttackEggThiefTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.BreedAtNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.SearchForEggTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class AttackEggThiefTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed_at_nest" // Category
}
val condition = booleanVariable(BREED, "can_breed", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(AttackEggThiefTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE),
it.absent(MemoryModuleType.WALK_TARGET)
).apply(it) { nestMemory, mateMemory, walkTarget ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// Convert nest location to Vec3
val targetVec = Vec3.atCenterOf(nestLocation)
// Check proximity to the nest
if (entity.distanceToSqr(targetVec) > 2.0 && mate.distanceToSqr(targetVec) > 2.0) {
return@Trigger false
}
// Access the nest block entity
val nestBlockEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity
?: return@Trigger false
// double check to make sure the nest is not taken yet and cancel if it is
if (nestBlockEntity.getItem(0) != ItemStack.EMPTY) return@Trigger false
// Generate the egg using BreedingLogicManager
val parentOne = (entity as PokemonEntity).pokemon
val parentTwo = mate.pokemon
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val form = BreedingLogicManager.calculateForm(parentOne, parentTwo)
// Ensure the breeding result is valid
val egg = breedingResult.pokemon?.let { result ->
Egg(
species = result.species.resourceIdentifier,
parentOne = parentOne,
parentTwo = parentTwo,
form = form.name.lowercase(),
timeToHatch = 6000F
)
} ?: return@Trigger false
// Set the egg in the nest
nestBlockEntity.setItem(egg.createEggItemStack())
// set the parents of the nest if it/they are wild so they guard it
if (nestBlockEntity.ownerUUID == null)
nestBlockEntity.parents = Pair((entity as PokemonEntity).uuid, mate.uuid)
// set breeding cooldown for both
parentOne.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentOne)
parentTwo.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentTwo)
// Display heart particles above parents
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
entity.getRandomX(1.0), // Particle X position with slight randomness
entity.getRandomY() + 0.5, // Slightly raised Y position
entity.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * entity.random.nextGaussian(), // X velocity with slight variance
0.1 * entity.random.nextGaussian(), // Y velocity with upwards variance
0.2 * entity.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
mate.getRandomX(1.0), // Particle X position with slight randomness
mate.getRandomY() + 0.5, // Slightly raised Y position
mate.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * mate.random.nextGaussian(), // X velocity with slight variance
0.1 * mate.random.nextGaussian(), // Y velocity with upwards variance
0.2 * mate.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
// Notify nearby players
world.players().forEach { player ->
player.displayClientMessage("An egg has been laid in the nest!".green(), true)
}
// todo maybe have them fall asleep after?
return@Trigger true
}
}
}*/
@@ -0,0 +1,146 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.BreedAtNestTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class BreedAtNestTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed_at_nest" // Category
}
val condition = booleanVariable(BREED, "can_breed", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(BreedAtNestTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE),
it.absent(MemoryModuleType.WALK_TARGET)
).apply(it) { nestMemory, mateMemory, walkTarget ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// Convert nest location to Vec3
val targetVec = Vec3.atCenterOf(nestLocation)
// Check proximity to the nest
if (entity.distanceToSqr(targetVec) > 2.0 && mate.distanceToSqr(targetVec) > 2.0) {
return@Trigger false
}
// Access the nest block entity
val nestBlockEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity
?: return@Trigger false
// double check to make sure the nest is not taken yet and cancel if it is
if (nestBlockEntity.getItem(0) != ItemStack.EMPTY) return@Trigger false
// Generate the egg using BreedingLogicManager
val parentOne = (entity as PokemonEntity).pokemon
val parentTwo = mate.pokemon
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val form = BreedingLogicManager.calculateForm(parentOne, parentTwo)
// Ensure the breeding result is valid
val egg = breedingResult.pokemon?.let { result ->
Egg(
species = result.species.resourceIdentifier,
parentOne = parentOne,
parentTwo = parentTwo,
form = form.name.lowercase(),
timeToHatch = 6000F
)
} ?: return@Trigger false
// Set the egg in the nest
nestBlockEntity.setItem(egg.createEggItemStack())
// set the parents of the nest if it/they are wild so they guard it
if (nestBlockEntity.ownerUUID == null)
nestBlockEntity.parents = Pair((entity as PokemonEntity).uuid, mate.uuid)
// set breeding cooldown for both
parentOne.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentOne)
parentTwo.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentTwo)
// Display heart particles above parents
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
entity.getRandomX(1.0), // Particle X position with slight randomness
entity.getRandomY() + 0.5, // Slightly raised Y position
entity.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * entity.random.nextGaussian(), // X velocity with slight variance
0.1 * entity.random.nextGaussian(), // Y velocity with upwards variance
0.2 * entity.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
mate.getRandomX(1.0), // Particle X position with slight randomness
mate.getRandomY() + 0.5, // Slightly raised Y position
mate.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * mate.random.nextGaussian(), // X velocity with slight variance
0.1 * mate.random.nextGaussian(), // Y velocity with upwards variance
0.2 * mate.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
// Notify nearby players
world.players().forEach { player ->
player.displayClientMessage("An egg has been laid in the nest!".green(), true)
}
// todo maybe have them fall asleep after?
return@Trigger true
}
}
}*/
@@ -0,0 +1,106 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.BuildNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToNestTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class BuildNestTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed" // Category
}
val condition = booleanVariable(BREED, "breeds", true).asExpressible()
val speedMultiplier = numberVariable(BREED, "speed_multiplier", 0.6).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
speedMultiplier
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(BuildNestTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(MemoryModuleType.LOOK_TARGET), // todo This might need to be it.absent but only it.present works atm
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE)
).apply(it) { lookTarget, walkTarget, nestMemory, mateMemory ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
// Don't do anything if the pokemon has a breeding cooldown
if ((entity as PokemonEntity).pokemon.breedingCooldown != 0) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
//val nestLocation = it.get(nestMemory)
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
//val mate = it.get(mateMemory) as? PokemonEntity ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// todo try to find a way to only have pairs of mates instead of an orgy
// Create a target vector at the nest location
val targetVec = Vec3.atCenterOf(nestLocation)
// Check if Pokémon are close enough to the nest
if (entity.distanceToSqr(targetVec) <= 2.0 && mate.distanceToSqr(targetVec) <= 2.0) {
return@Trigger false // Task ends when both are close enough
}
*//*if ((entity as PokemonEntity).tethering.tetheringId != null) {
val test1 = 1 // for breakpointing here to test my pastured mons
}*//*
// try to get the main pokemon to path and look at the nest
walkTarget.set(WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
// try to find a way to get the mate to pathfind and look at the nest too...
mate.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1)
)
mate.brain.setMemory(MemoryModuleType.LOOK_TARGET, BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
}*/
@@ -0,0 +1,173 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.GuardNestTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
class GuardNestTaskConfig : SingleTaskConfig {
// todo make new variables
companion object {
const val BREED = "breed"
}
val condition = booleanVariable(BREED, "breeds", true).asExpressible()
val speedMultiplier = numberVariable(BREED, "speed_multiplier", 0.6).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
speedMultiplier
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(GuardNestTask.create(), PokemonEntity::class.java)
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.PATERNAL_NEST_LOCATION),
it.registered(MemoryModuleType.NEAREST_VISIBLE_PLAYER), // if player is invisible they do not patrol
it.registered(MemoryModuleType.ANGRY_AT),
it.absent(MemoryModuleType.ATTACK_TARGET),
it.registered(CobblemonMemories.IRRITATED_AT)
).apply(it) { lookTarget, walkTarget, paternalNest, nearestVisiblePlayer, angryAt, attackTarget, irritatedAt ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive || entity !is PokemonEntity) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
//val nestLocation = it.get(nestMemory)
val nestLocation: BlockPos = (paternalNest.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
val nestCenter = Vec3.atCenterOf(nestLocation)
// todo maybe cancel task if player is close to nest and set angry at task to that player
val nearestPlayerOptional = entity.brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER)
val player = nearestPlayerOptional.orElse(null) as? ServerPlayer
val irritatedAtTarget = entity.brain.getMemory(CobblemonMemories.IRRITATED_AT)
if (player != null) {
val playerDistanceSquared = player.position().distanceToSqr(nestCenter)
if (!irritatedAtTarget.isPresent && playerDistanceSquared <= 15 * 15) {
// Player is within 15 blocks, set angry and cry
entity.cry()
// set irritated at target to player near egg
irritatedAt.set(player.uuid)
// Display angry particles above parent
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.ANGRY_VILLAGER,
entity.getRandomX(1.0), // Particle X position with slight randomness
entity.getRandomY() + 0.5, // Slightly raised Y position
entity.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * entity.random.nextGaussian(), // X velocity with slight variance
0.1 * entity.random.nextGaussian(), // Y velocity with upwards variance
0.2 * entity.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
}
if (playerDistanceSquared <= 8 * 8) {
// Player is within 8 blocks, attack
entity.cry()
// Display angry particles above parent
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.ANGRY_VILLAGER,
entity.getRandomX(1.0), // Particle X position with slight randomness
entity.getRandomY() + 0.5, // Slightly raised Y position
entity.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * entity.random.nextGaussian(), // X velocity with slight variance
0.1 * entity.random.nextGaussian(), // Y velocity with upwards variance
0.2 * entity.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
angryAt.set(player.uuid)
attackTarget.set(player)
return@Trigger false // Cancel the task
}
}
// Scan for a valid walkable position within an 8-block sphere
val validPosition = findValidWalkablePosition(world, nestCenter, entity, 8)
if (validPosition == null) {
return@Trigger false // No valid position found
}
// Set the entity's walk and look targets
walkTarget.set(WalkTarget(validPosition, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(validPosition.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
private fun findValidWalkablePosition(
world: ServerLevel,
center: Vec3,
entity: PathfinderMob,
radius: Int
): Vec3? {
val random = world.random
// Try a number of random positions within the radius
repeat(20) { // Attempt up to 20 random positions
val offsetX = random.nextDouble() * 2 * radius - radius
val offsetY = random.nextDouble() * 2 * radius - radius
val offsetZ = random.nextDouble() * 2 * radius - radius
val candidate = center.add(offsetX, offsetY, offsetZ)
// Convert to block position
val blockPos = BlockPos(candidate.x.toInt(), candidate.y.toInt(), candidate.z.toInt())
// Check if the position is walkable and not inside a solid block
if (world.getBlockState(blockPos).isSolidRender(world, blockPos).not() &&
entity.navigation.isStableDestination(blockPos)
) {
return Vec3.atCenterOf(blockPos) // Return the valid position
}
}
return null // No valid position found
}*/
}
@@ -0,0 +1,139 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
import java.util.UUID
class HuntPlayerTaskConfig : SingleTaskConfig {
// todo make new variables
companion object {
const val BREED = "breed"
}
val condition = booleanVariable(BREED, "breeds", true).asExpressible()
val speedMultiplier = numberVariable(BREED, "speed_multiplier", 0.6).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
speedMultiplier
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.present(MemoryModuleType.NEAREST_PLAYERS), // this will be null most of the time
it.present(MemoryModuleType.ANGRY_AT),
it.registered(MemoryModuleType.ATTACK_TARGET),
it.registered(MemoryModuleType.DISTURBANCE_LOCATION),
).apply(it) { lookTarget, walkTarget, nearestPlayers, angryAt, attackTarget, disturbanceLocation ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive || entity !is PokemonEntity) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val angryAtUuid = (angryAt.value() as? IdF<UUID?>)?.value() ?: return@Trigger false
val disturbancePos = (disturbanceLocation.value() as? IdF<BlockPos>)?.value()
val players = (nearestPlayers.value() as? IdF<List<Player?>?>)?.value()
val targetPlayer = players?.find { it?.uuid == angryAtUuid }
if (targetPlayer != null) {
// If the angry player is nearby, set them as the attack target
entity.cry()
attackTarget.set(targetPlayer)
return@Trigger true
}
// If the angry player is not nearby, search around the Pokémon
val maxTicksToSearch = 200L
if (time > maxTicksToSearch) {
// Timeout after 200 ticks
return@Trigger false
}
// If no disturbance location or it is caused by a nearby player, skip
if (disturbancePos != null) {
val disturberCausingDisturbance = players?.any {
it?.position()?.distanceToSqr(Vec3.atCenterOf(disturbancePos))!! <= 4.0 * 4.0
}
disturberCausingDisturbance?.let { it1 ->
if (!it1) {
// Pathfind toward disturbance location
walkTarget.set(WalkTarget(Vec3.atCenterOf(disturbancePos), speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(Vec3.atCenterOf(disturbancePos)))
return@Trigger true
}
}
}
// If no valid disturbance location, pathfind randomly within proximity
val validPosition = findValidWalkablePosition(world, entity.position(), entity, 8)
if (validPosition == null) {
return@Trigger false // No valid position found
}
walkTarget.set(WalkTarget(validPosition, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(validPosition.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
private fun findValidWalkablePosition(
world: ServerLevel,
center: Vec3,
entity: PathfinderMob,
radius: Int
): Vec3? {
val random = world.random
// Try a number of random positions within the radius
repeat(20) { // Attempt up to 20 random positions
val offsetX = random.nextDouble() * 2 * radius - radius
val offsetY = random.nextDouble() * 2 * radius - radius
val offsetZ = random.nextDouble() * 2 * radius - radius
val candidate = center.add(offsetX, offsetY, offsetZ)
// Convert to block position
val blockPos = BlockPos(candidate.x.toInt(), candidate.y.toInt(), candidate.z.toInt())
// Check if the position is walkable and not inside a solid block
if (world.getBlockState(blockPos).isSolidRender(world, blockPos).not() &&
entity.navigation.isStableDestination(blockPos)
) {
return Vec3.atCenterOf(blockPos) // Return the valid position
}
}
return null // No valid position found
}
}
@@ -0,0 +1,45 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToIdealNestLocationTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToNestMaterialTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToNestTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class PathToIdealNestLocationTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed" // Category
}
val condition = booleanVariable(BREED, "breeds", true).asExpressible()
val speedMultiplier = numberVariable(BREED, "speed_multiplier", 0.6).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
speedMultiplier
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(PathToIdealNestLocationTask.create(), PokemonEntity::class.java)
}
}
@@ -0,0 +1,106 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToNestMaterialTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToNestTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class PathToNestMaterialTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed" // Category
}
val condition = booleanVariable(BREED, "breeds", true).asExpressible()
val speedMultiplier = numberVariable(BREED, "speed_multiplier", 0.6).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
speedMultiplier
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(PathToNestMaterialTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(MemoryModuleType.LOOK_TARGET), // todo This might need to be it.absent but only it.present works atm
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE)
).apply(it) { lookTarget, walkTarget, nestMemory, mateMemory ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
// Don't do anything if the pokemon has a breeding cooldown
if ((entity as PokemonEntity).pokemon.breedingCooldown != 0) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
//val nestLocation = it.get(nestMemory)
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
//val mate = it.get(mateMemory) as? PokemonEntity ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// todo try to find a way to only have pairs of mates instead of an orgy
// Create a target vector at the nest location
val targetVec = Vec3.atCenterOf(nestLocation)
// Check if Pokémon are close enough to the nest
if (entity.distanceToSqr(targetVec) <= 2.0 && mate.distanceToSqr(targetVec) <= 2.0) {
return@Trigger false // Task ends when both are close enough
}
*//*if ((entity as PokemonEntity).tethering.tetheringId != null) {
val test1 = 1 // for breakpointing here to test my pastured mons
}*//*
// try to get the main pokemon to path and look at the nest
walkTarget.set(WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
// try to find a way to get the mate to pathfind and look at the nest too...
mate.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1)
)
mate.brain.setMemory(MemoryModuleType.LOOK_TARGET, BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
}*/
@@ -0,0 +1,105 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToNestTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class PathToNestTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed" // Category
}
val condition = booleanVariable(BREED, "breeds", true).asExpressible()
val speedMultiplier = numberVariable(BREED, "speed_multiplier", 0.6).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
speedMultiplier
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(PathToNestTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(MemoryModuleType.LOOK_TARGET), // todo This might need to be it.absent but only it.present works atm
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE)
).apply(it) { lookTarget, walkTarget, nestMemory, mateMemory ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
// Don't do anything if the pokemon has a breeding cooldown
if ((entity as PokemonEntity).pokemon.breedingCooldown != 0) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
//val nestLocation = it.get(nestMemory)
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
//val mate = it.get(mateMemory) as? PokemonEntity ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// todo try to find a way to only have pairs of mates instead of an orgy
// Create a target vector at the nest location
val targetVec = Vec3.atCenterOf(nestLocation)
// Check if Pokémon are close enough to the nest
if (entity.distanceToSqr(targetVec) <= 2.0 && mate.distanceToSqr(targetVec) <= 2.0) {
return@Trigger false // Task ends when both are close enough
}
*//*if ((entity as PokemonEntity).tethering.tetheringId != null) {
val test1 = 1 // for breakpointing here to test my pastured mons
}*//*
// try to get the main pokemon to path and look at the nest
walkTarget.set(WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
// try to find a way to get the mate to pathfind and look at the nest too...
mate.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1)
)
mate.brain.setMemory(MemoryModuleType.LOOK_TARGET, BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
}*/
@@ -0,0 +1,149 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.BreedAtNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PickUpEggTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PlaceEggInNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.SearchForEggTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PickUpEggTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed_at_nest" // Category
}
val condition = booleanVariable(BREED, "can_breed", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(PickUpEggTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE),
it.absent(MemoryModuleType.WALK_TARGET)
).apply(it) { nestMemory, mateMemory, walkTarget ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// Convert nest location to Vec3
val targetVec = Vec3.atCenterOf(nestLocation)
// Check proximity to the nest
if (entity.distanceToSqr(targetVec) > 2.0 && mate.distanceToSqr(targetVec) > 2.0) {
return@Trigger false
}
// Access the nest block entity
val nestBlockEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity
?: return@Trigger false
// double check to make sure the nest is not taken yet and cancel if it is
if (nestBlockEntity.getItem(0) != ItemStack.EMPTY) return@Trigger false
// Generate the egg using BreedingLogicManager
val parentOne = (entity as PokemonEntity).pokemon
val parentTwo = mate.pokemon
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val form = BreedingLogicManager.calculateForm(parentOne, parentTwo)
// Ensure the breeding result is valid
val egg = breedingResult.pokemon?.let { result ->
Egg(
species = result.species.resourceIdentifier,
parentOne = parentOne,
parentTwo = parentTwo,
form = form.name.lowercase(),
timeToHatch = 6000F
)
} ?: return@Trigger false
// Set the egg in the nest
nestBlockEntity.setItem(egg.createEggItemStack())
// set the parents of the nest if it/they are wild so they guard it
if (nestBlockEntity.ownerUUID == null)
nestBlockEntity.parents = Pair((entity as PokemonEntity).uuid, mate.uuid)
// set breeding cooldown for both
parentOne.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentOne)
parentTwo.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentTwo)
// Display heart particles above parents
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
entity.getRandomX(1.0), // Particle X position with slight randomness
entity.getRandomY() + 0.5, // Slightly raised Y position
entity.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * entity.random.nextGaussian(), // X velocity with slight variance
0.1 * entity.random.nextGaussian(), // Y velocity with upwards variance
0.2 * entity.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
mate.getRandomX(1.0), // Particle X position with slight randomness
mate.getRandomY() + 0.5, // Slightly raised Y position
mate.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * mate.random.nextGaussian(), // X velocity with slight variance
0.1 * mate.random.nextGaussian(), // Y velocity with upwards variance
0.2 * mate.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
// Notify nearby players
world.players().forEach { player ->
player.displayClientMessage("An egg has been laid in the nest!".green(), true)
}
// todo maybe have them fall asleep after?
return@Trigger true
}
}
}*/
@@ -0,0 +1,106 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PickUpNestMaterialTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class PickUpNestMaterialTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed" // Category
}
val condition = booleanVariable(BREED, "breeds", true).asExpressible()
val speedMultiplier = numberVariable(BREED, "speed_multiplier", 0.6).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
speedMultiplier
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(PickUpNestMaterialTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(MemoryModuleType.LOOK_TARGET), // todo This might need to be it.absent but only it.present works atm
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE)
).apply(it) { lookTarget, walkTarget, nestMemory, mateMemory ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
// Don't do anything if the pokemon has a breeding cooldown
if ((entity as PokemonEntity).pokemon.breedingCooldown != 0) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
//val nestLocation = it.get(nestMemory)
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
//val mate = it.get(mateMemory) as? PokemonEntity ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// todo try to find a way to only have pairs of mates instead of an orgy
// Create a target vector at the nest location
val targetVec = Vec3.atCenterOf(nestLocation)
// Check if Pokémon are close enough to the nest
if (entity.distanceToSqr(targetVec) <= 2.0 && mate.distanceToSqr(targetVec) <= 2.0) {
return@Trigger false // Task ends when both are close enough
}
*//*if ((entity as PokemonEntity).tethering.tetheringId != null) {
val test1 = 1 // for breakpointing here to test my pastured mons
}*//*
// try to get the main pokemon to path and look at the nest
walkTarget.set(WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
// try to find a way to get the mate to pathfind and look at the nest too...
mate.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1)
)
mate.brain.setMemory(MemoryModuleType.LOOK_TARGET, BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
}*/
@@ -0,0 +1,148 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.BreedAtNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PlaceEggInNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.SearchForEggTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PlaceEggInNestTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed_at_nest" // Category
}
val condition = booleanVariable(BREED, "can_breed", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(PlaceEggInNestTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE),
it.absent(MemoryModuleType.WALK_TARGET)
).apply(it) { nestMemory, mateMemory, walkTarget ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// Convert nest location to Vec3
val targetVec = Vec3.atCenterOf(nestLocation)
// Check proximity to the nest
if (entity.distanceToSqr(targetVec) > 2.0 && mate.distanceToSqr(targetVec) > 2.0) {
return@Trigger false
}
// Access the nest block entity
val nestBlockEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity
?: return@Trigger false
// double check to make sure the nest is not taken yet and cancel if it is
if (nestBlockEntity.getItem(0) != ItemStack.EMPTY) return@Trigger false
// Generate the egg using BreedingLogicManager
val parentOne = (entity as PokemonEntity).pokemon
val parentTwo = mate.pokemon
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val form = BreedingLogicManager.calculateForm(parentOne, parentTwo)
// Ensure the breeding result is valid
val egg = breedingResult.pokemon?.let { result ->
Egg(
species = result.species.resourceIdentifier,
parentOne = parentOne,
parentTwo = parentTwo,
form = form.name.lowercase(),
timeToHatch = 6000F
)
} ?: return@Trigger false
// Set the egg in the nest
nestBlockEntity.setItem(egg.createEggItemStack())
// set the parents of the nest if it/they are wild so they guard it
if (nestBlockEntity.ownerUUID == null)
nestBlockEntity.parents = Pair((entity as PokemonEntity).uuid, mate.uuid)
// set breeding cooldown for both
parentOne.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentOne)
parentTwo.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentTwo)
// Display heart particles above parents
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
entity.getRandomX(1.0), // Particle X position with slight randomness
entity.getRandomY() + 0.5, // Slightly raised Y position
entity.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * entity.random.nextGaussian(), // X velocity with slight variance
0.1 * entity.random.nextGaussian(), // Y velocity with upwards variance
0.2 * entity.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
mate.getRandomX(1.0), // Particle X position with slight randomness
mate.getRandomY() + 0.5, // Slightly raised Y position
mate.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * mate.random.nextGaussian(), // X velocity with slight variance
0.1 * mate.random.nextGaussian(), // Y velocity with upwards variance
0.2 * mate.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
// Notify nearby players
world.players().forEach { player ->
player.displayClientMessage("An egg has been laid in the nest!".green(), true)
}
// todo maybe have them fall asleep after?
return@Trigger true
}
}
}*/
@@ -0,0 +1,148 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.BreedAtNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.RetrieveEggTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.SearchForEggTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class RetrieveEggTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed_at_nest" // Category
}
val condition = booleanVariable(BREED, "can_breed", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(RetrieveEggTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE),
it.absent(MemoryModuleType.WALK_TARGET)
).apply(it) { nestMemory, mateMemory, walkTarget ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// Convert nest location to Vec3
val targetVec = Vec3.atCenterOf(nestLocation)
// Check proximity to the nest
if (entity.distanceToSqr(targetVec) > 2.0 && mate.distanceToSqr(targetVec) > 2.0) {
return@Trigger false
}
// Access the nest block entity
val nestBlockEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity
?: return@Trigger false
// double check to make sure the nest is not taken yet and cancel if it is
if (nestBlockEntity.getItem(0) != ItemStack.EMPTY) return@Trigger false
// Generate the egg using BreedingLogicManager
val parentOne = (entity as PokemonEntity).pokemon
val parentTwo = mate.pokemon
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val form = BreedingLogicManager.calculateForm(parentOne, parentTwo)
// Ensure the breeding result is valid
val egg = breedingResult.pokemon?.let { result ->
Egg(
species = result.species.resourceIdentifier,
parentOne = parentOne,
parentTwo = parentTwo,
form = form.name.lowercase(),
timeToHatch = 6000F
)
} ?: return@Trigger false
// Set the egg in the nest
nestBlockEntity.setItem(egg.createEggItemStack())
// set the parents of the nest if it/they are wild so they guard it
if (nestBlockEntity.ownerUUID == null)
nestBlockEntity.parents = Pair((entity as PokemonEntity).uuid, mate.uuid)
// set breeding cooldown for both
parentOne.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentOne)
parentTwo.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentTwo)
// Display heart particles above parents
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
entity.getRandomX(1.0), // Particle X position with slight randomness
entity.getRandomY() + 0.5, // Slightly raised Y position
entity.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * entity.random.nextGaussian(), // X velocity with slight variance
0.1 * entity.random.nextGaussian(), // Y velocity with upwards variance
0.2 * entity.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
mate.getRandomX(1.0), // Particle X position with slight randomness
mate.getRandomY() + 0.5, // Slightly raised Y position
mate.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * mate.random.nextGaussian(), // X velocity with slight variance
0.1 * mate.random.nextGaussian(), // Y velocity with upwards variance
0.2 * mate.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
// Notify nearby players
world.players().forEach { player ->
player.displayClientMessage("An egg has been laid in the nest!".green(), true)
}
// todo maybe have them fall asleep after?
return@Trigger true
}
}
}*/
@@ -0,0 +1,147 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.BreedAtNestTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.SearchForEggTask
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.kinds.IdF
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class SearchForEggTaskConfig : SingleTaskConfig {
companion object {
const val BREED = "breed_at_nest" // Category
}
val condition = booleanVariable(BREED, "can_breed", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return WrapperLivingEntityTask(SearchForEggTask.create(), PokemonEntity::class.java)
}
}
/*override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
return BehaviorBuilder.create {
it.group(
it.present(CobblemonMemories.NEST_LOCATION),
it.present(CobblemonMemories.POTENTIAL_MATE),
it.absent(MemoryModuleType.WALK_TARGET)
).apply(it) { nestMemory, mateMemory, walkTarget ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
// Retrieve nest location and mate entity from memory
val nestLocation: BlockPos = (nestMemory.value() as? IdF<BlockPos>)?.value() ?: return@Trigger false
val mate = (mateMemory.value() as? IdF<PokemonEntity>)?.value() ?: return@Trigger false
// Convert nest location to Vec3
val targetVec = Vec3.atCenterOf(nestLocation)
// Check proximity to the nest
if (entity.distanceToSqr(targetVec) > 2.0 && mate.distanceToSqr(targetVec) > 2.0) {
return@Trigger false
}
// Access the nest block entity
val nestBlockEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity
?: return@Trigger false
// double check to make sure the nest is not taken yet and cancel if it is
if (nestBlockEntity.getItem(0) != ItemStack.EMPTY) return@Trigger false
// Generate the egg using BreedingLogicManager
val parentOne = (entity as PokemonEntity).pokemon
val parentTwo = mate.pokemon
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val form = BreedingLogicManager.calculateForm(parentOne, parentTwo)
// Ensure the breeding result is valid
val egg = breedingResult.pokemon?.let { result ->
Egg(
species = result.species.resourceIdentifier,
parentOne = parentOne,
parentTwo = parentTwo,
form = form.name.lowercase(),
timeToHatch = 6000F
)
} ?: return@Trigger false
// Set the egg in the nest
nestBlockEntity.setItem(egg.createEggItemStack())
// set the parents of the nest if it/they are wild so they guard it
if (nestBlockEntity.ownerUUID == null)
nestBlockEntity.parents = Pair((entity as PokemonEntity).uuid, mate.uuid)
// set breeding cooldown for both
parentOne.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentOne)
parentTwo.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentTwo)
// Display heart particles above parents
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
entity.getRandomX(1.0), // Particle X position with slight randomness
entity.getRandomY() + 0.5, // Slightly raised Y position
entity.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * entity.random.nextGaussian(), // X velocity with slight variance
0.1 * entity.random.nextGaussian(), // Y velocity with upwards variance
0.2 * entity.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
(world as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
mate.getRandomX(1.0), // Particle X position with slight randomness
mate.getRandomY() + 0.5, // Slightly raised Y position
mate.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * mate.random.nextGaussian(), // X velocity with slight variance
0.1 * mate.random.nextGaussian(), // Y velocity with upwards variance
0.2 * mate.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
// Notify nearby players
world.players().forEach { player ->
player.displayClientMessage("An egg has been laid in the nest!".green(), true)
}
// todo maybe have them fall asleep after?
return@Trigger true
}
}
}*/
@@ -76,6 +76,19 @@ interface TaskConfig {
"flee_nearest_hostile" to FleeNearestHostileTaskConfig::class.java,
"run_script" to RunScript::class.java,
"look_in_direction" to LookInDirectionTaskConfig::class.java,
"path_to_nest" to PathToNestTaskConfig::class.java,
"path_to_nest_material" to PathToNestMaterialTaskConfig::class.java,
"breed_at_nest" to BreedAtNestTaskConfig::class.java,
"guard_nest" to GuardNestTaskConfig::class.java,
"search_for_egg" to SearchForEggTaskConfig::class.java,
"attack_egg_thief" to AttackEggThiefTaskConfig::class.java,
"retrieve_egg" to RetrieveEggTaskConfig::class.java,
"pick_up_egg" to PickUpEggTaskConfig::class.java,
"pick_up_nest_material" to PickUpNestMaterialTaskConfig::class.java,
"path_to_ideal_nest_location" to PathToIdealNestLocationTaskConfig::class.java,
"place_egg_in_nest" to PlaceEggInNestTaskConfig::class.java,
"build_nest" to BuildNestTaskConfig::class.java,
"hunt_player" to HuntPlayerTaskConfig::class.java,
"wake_up" to WakeUpTaskConfig::class.java,
"go_to_sleep" to GoToSleepTaskConfig::class.java,
"find_resting_place" to FindRestingPlaceTaskConfig::class.java,
@@ -0,0 +1,185 @@
package com.cobblemon.mod.common.api.breeding
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.resources.ResourceLocation
import java.util.Optional
data class BreedingRule(
val parent1rules: ParentRules,
val parent2rules: ParentRules,
// The resulting baby data (all strings except hasItem will appear in ParentRules)
val species: String?,
val form: String?,
val nature: String?,
val ability: String?,
val moves: Moves,
val ivs: Ivs,
val gender: String?,
// If you want a separate pokeball string field:
val pokeball: String?
) {
companion object {
val CODEC: Codec<BreedingRule> = RecordCodecBuilder.create { builder ->
builder.group(
// Parent rules
ParentRules.CODEC.fieldOf("parent1rules")
.forGetter<BreedingRule> { it.parent1rules },
ParentRules.CODEC.fieldOf("parent2rules")
.forGetter<BreedingRule> { it.parent2rules },
// ---- All optional string fields for the baby data ----
Codec.STRING.optionalFieldOf("species")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<BreedingRule> { it.species },
Codec.STRING.optionalFieldOf("form")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<BreedingRule> { it.form },
Codec.STRING.optionalFieldOf("nature")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<BreedingRule> { it.nature },
Codec.STRING.optionalFieldOf("ability")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<BreedingRule> { it.ability },
// Moves & IVs can be empty objects but must exist in the JSON
Moves.CODEC.fieldOf("moves")
.forGetter<BreedingRule> { it.moves },
Ivs.CODEC.fieldOf("ivs")
.forGetter<BreedingRule> { it.ivs },
Codec.STRING.optionalFieldOf("gender")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<BreedingRule> { it.gender },
// If you want pokeball to be a string instead of ResourceLocation
Codec.STRING.optionalFieldOf("pokeball")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<BreedingRule> { it.pokeball }
).apply(builder, ::BreedingRule)
}
}
data class ParentRules(
val species: String?,
val form: String?,
val ability: String?,
val gender: String?,
// hasMove is also just a string now
val hasMove: String?,
// hasItem stays as a ResourceLocation?
val hasItem: ResourceLocation?
) {
companion object {
val CODEC: Codec<ParentRules> = RecordCodecBuilder.create { builder ->
builder.group(
Codec.STRING.optionalFieldOf("species")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<ParentRules> { it.species },
Codec.STRING.optionalFieldOf("form")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<ParentRules> { it.form },
Codec.STRING.optionalFieldOf("ability")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<ParentRules> { it.ability },
Codec.STRING.optionalFieldOf("gender")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<ParentRules> { it.gender },
Codec.STRING.optionalFieldOf("hasMove")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<ParentRules> { it.hasMove },
// Keep hasItem as a ResourceLocation
ResourceLocation.CODEC.optionalFieldOf("hasItem")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<ParentRules> { it.hasItem }
).apply(builder, ::ParentRules)
}
}
}
data class Moves(
val move1: String?,
val move2: String?,
val move3: String?,
val move4: String?
) {
companion object {
val CODEC: Codec<Moves> = RecordCodecBuilder.create { builder ->
builder.group(
Codec.STRING.optionalFieldOf("move1")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Moves> { it.move1 },
Codec.STRING.optionalFieldOf("move2")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Moves> { it.move2 },
Codec.STRING.optionalFieldOf("move3")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Moves> { it.move3 },
Codec.STRING.optionalFieldOf("move4")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Moves> { it.move4 }
).apply(builder, ::Moves)
}
}
}
data class Ivs(
val hp: Int?,
val attack: Int?,
val defence: Int?,
val special_attack: Int?,
val special_defence: Int?,
val speed: Int?
) {
companion object {
val CODEC: Codec<Ivs> = RecordCodecBuilder.create { builder ->
builder.group(
Codec.INT.optionalFieldOf("hp")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Ivs> { it.hp },
Codec.INT.optionalFieldOf("attack")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Ivs> { it.attack },
Codec.INT.optionalFieldOf("defence")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Ivs> { it.defence },
Codec.INT.optionalFieldOf("special_attack")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Ivs> { it.special_attack },
Codec.INT.optionalFieldOf("special_defence")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Ivs> { it.special_defence },
Codec.INT.optionalFieldOf("speed")
.xmap({ it.orElse(null) }, { Optional.ofNullable(it) })
.forGetter<Ivs> { it.speed }
).apply(builder, ::Ivs)
}
}
}
}
@@ -0,0 +1,108 @@
package com.cobblemon.mod.common.api.breeding
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.breeding.BreedingRuleRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object BreedingRules : JsonDataRegistry<BreedingRule> {
override val id = cobblemonResource("breeding_rules")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<BreedingRules>()
override val typeToken: TypeToken<BreedingRule> = TypeToken.get(BreedingRule::class.java)
override val resourcePath = "breeding_rules"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.setPrettyPrinting()
.create()
private val registry = mutableMapOf<ResourceLocation, BreedingRule>()
override fun sync(player: ServerPlayer) {
// Send the entire list of BreedingRule entries to the player
BreedingRuleRegistrySyncPacket(this.registry.values.toList()).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, BreedingRule>) {
// Clear existing data and load new data from JSON
registry.clear()
data.forEach { (id, breedingRule) ->
registry[id] = breedingRule
}
println("Loaded Breeding Rules: ${registry.size}")
}
/**
* Attempts to find a single BreedingRule that matches the given mother/father conditions.
* Returns the first matching BreedingRule, or null if no match is found.
*/
fun getBreedingRule(mother: Pokemon, father: Pokemon): BreedingRule? {
// 1. Prepare the actual ParentRules objects for mother/father.
val motherRules = createParentRules(mother)
val fatherRules = createParentRules(father)
// 2. Find all matching breeding rules in the registry.
// A rule matches if parent1rules & parent2rules match the mother/father conditions.
// If multiple rules match, we just return the first (or you could randomize).
val matchingRules = registry.values.firstOrNull { rule ->
rule.species != null && (
(checkParentRules(rule.parent1rules, motherRules) && checkParentRules(rule.parent2rules, fatherRules)) ||
(checkParentRules(rule.parent1rules, fatherRules) && checkParentRules(rule.parent2rules, motherRules))
)
}
// 3. If none match, return null. Otherwise return the first match.
return matchingRules
}
/**
* Helper function to create a BreedingRule.ParentRules object from an actual Pokémon instance.
* Adjust as needed if you'd like more robust checking (e.g. multiple moves, alt forms, etc.).
*/
private fun createParentRules(parent: Pokemon): BreedingRule.ParentRules {
// NOTE: Adjust these calls if your Pokemon class has different ways to get form/ability
val speciesId = parent.species.name.lowercase()
val formId = parent.form.name.lowercase() // If you store form as a ResourceLocation
val abilityId = parent.ability.name.lowercase() // If you store ability as a ResourceLocation
return BreedingRule.ParentRules(
species = speciesId,
form = formId,
ability = abilityId,
gender = parent.gender.name.lowercase(),
hasMove = parent.moveSet.getMoves().firstOrNull()?.template?.name,
hasItem = parent.heldItemNoCopy().item.let {
BuiltInRegistries.ITEM.getKey(it) // If empty, might be minecraft:air
}
)
}
/**
* Check if a parent's actual rules match the expected parent rules from the data.
*/
private fun checkParentRules(
expected: BreedingRule.ParentRules,
actual: BreedingRule.ParentRules
): Boolean {
return (expected.species == null || expected.species == actual.species) &&
(expected.form == null || expected.form == actual.form) &&
(expected.ability == null || expected.ability == actual.ability) &&
(expected.gender == null || expected.gender == actual.gender) &&
(expected.hasMove == null || expected.hasMove == actual.hasMove) &&
(expected.hasItem == null || expected.hasItem == actual.hasItem)
}
}
@@ -21,6 +21,8 @@ import com.cobblemon.mod.common.api.events.berry.BerryHarvestEvent
import com.cobblemon.mod.common.api.events.berry.BerryMutationOfferEvent
import com.cobblemon.mod.common.api.events.berry.BerryMutationResultEvent
import com.cobblemon.mod.common.api.events.berry.BerryYieldCalculationEvent
import com.cobblemon.mod.common.api.events.breeding.BreedingEligibilityEvent
import com.cobblemon.mod.common.api.events.breeding.BreedingResultEvent
import com.cobblemon.mod.common.api.events.drops.LootDroppedEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntityLoadEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveEvent
@@ -262,6 +264,12 @@ object CobblemonEvents {
@JvmField
val HATCH_EGG_POST = EventObservable<HatchEggEvent.Post>()
//Breeding
@JvmField
val BREEDING_RESULT = EventObservable<BreedingResultEvent>()
@JvmField
val BREEDING_ELIGIBLE = EventObservable<BreedingEligibilityEvent>()
// -------------------------------------------------------------------------------------
//
// Riding
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.breeding
import com.cobblemon.mod.common.pokemon.Pokemon
data class BreedingEligibilityEvent(
val canBreed: Boolean,
val mother: Pokemon,
val father: Pokemon
)
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.breeding
import com.cobblemon.mod.common.api.pokemon.breeding.BreedingResult
import com.cobblemon.mod.common.pokemon.Pokemon
data class BreedingResultEvent(
val breedingResult: BreedingResult,
val mother: Pokemon,
val father: Pokemon
)
@@ -23,7 +23,7 @@ import net.minecraft.nbt.CompoundTag
* reload.
*
* @property pokemonEntity The Pokémon entity being saved.
* @property nbt The [NbtCompound] that the Pokémon is being saved to.
* @property nbt The [CompoundTag] that the Pokémon is being saved to.
* You should add or remove from this for whatever you
* want to accomplish. The existing contents will be all
* of what Cobblemon saves natively.
@@ -0,0 +1,61 @@
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import net.minecraft.resources.ResourceLocation
data class Habitat(
val structure: ResourceLocation,
val spawnPool: List<WeightedGroup>
) {
companion object {
val CODEC: Codec<Habitat> = RecordCodecBuilder.create { instance ->
instance.group(
ResourceLocation.CODEC.fieldOf("structure").forGetter { it.structure },
WeightedGroup.CODEC.listOf().fieldOf("spawnPool").forGetter { it.spawnPool }
).apply(instance, ::Habitat)
}
val STREAM_CODEC = ByteBufCodecs.fromCodec(CODEC)
val BLANK_HABITAT = Habitat(
structure = cobblemonResource("blank"),
spawnPool = emptyList()
)
}
fun flatten(): List<ResourceLocation> {
return spawnPool.flatMap { it.spawns.map { s -> s.species } }
}
}
data class WeightedGroup(
val weight: Int = 1,
val spawns: List<WeightedSpecies>
) {
companion object {
val CODEC: Codec<WeightedGroup> = RecordCodecBuilder.create { instance ->
instance.group(
Codec.INT.optionalFieldOf("weight", 1).forGetter { it.weight },
WeightedSpecies.CODEC.listOf().fieldOf("spawns").forGetter { it.spawns }
).apply(instance, ::WeightedGroup)
}
}
}
data class WeightedSpecies(
val species: ResourceLocation,
val weight: Int = 1
) {
companion object {
val CODEC: Codec<WeightedSpecies> = RecordCodecBuilder.create { instance ->
instance.group(
ResourceLocation.CODEC.fieldOf("species").forGetter { it.species },
Codec.INT.optionalFieldOf("weight", 1).forGetter { it.weight }
).apply(instance, ::WeightedSpecies)
}
}
}
@@ -0,0 +1,59 @@
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.HabitatSyncPacket
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object Habitats : JsonDataRegistry<Habitat> {
override val id = cobblemonResource("habitats")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<Habitats>()
override val typeToken: TypeToken<Habitat> = TypeToken.get(Habitat::class.java)
override val resourcePath = "habitats"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.setPrettyPrinting()
.create()
private val table = mutableMapOf<ResourceLocation, Habitat>()
override fun sync(player: ServerPlayer) {
HabitatSyncPacket(table.values.toList()).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, Habitat>) {
table.clear()
table.putAll(data)
println("[Habitats] Reloaded ${table.size} habitat(s):")
for ((structureId, habitat) in table) {
println(" - ${structureId}:")
if (habitat.spawnPool.isEmpty()) {
println(" (No spawn entries)")
} else {
habitat.spawnPool.forEachIndexed { i, group ->
println(" • Group $i (weight: ${group.weight}):")
group.spawns.forEach { entry ->
println(" - ${entry.species} (weight: ${entry.weight})")
}
}
}
}
}
fun getHabitat(structureId: ResourceLocation): Habitat? {
return table[structureId]
}
fun getSpawnOptions(structureId: ResourceLocation): List<ResourceLocation> {
return table[structureId]?.flatten() ?: emptyList()
}
}
@@ -0,0 +1,208 @@
package com.cobblemon.mod.common.api.nest
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.apricorn.Apricorn
import com.cobblemon.mod.common.api.tags.CobblemonBiomeTags
import com.cobblemon.mod.common.block.NestBlock
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BiomeTags
import net.minecraft.tags.TagKey
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import kotlin.jvm.optionals.getOrNull
/**
* Contains the different Nest variants and util methods to get their item, block, biome, and materials from.
*
* @author Plastered_Crab
* @since Feb 7th, 2025
*/
enum class Nest : StringRepresentable {
STICK,
LEAF,
STONE,
DEEPSLATE,
SNOW,
LUSH,
SEAGRASS,
WARM_OCEAN,
SAND,
RED_SAND,
NETHER,
BASALT,
SOUL_SAND,
CHORUS;
//HAY;
// Crab Note: todo maybe we want these to be a List of BlockItems?
fun item(): BlockItem = when(this) {
STICK -> CobblemonItems.NEST_STICK
LEAF -> CobblemonItems.NEST_LEAF
STONE -> CobblemonItems.NEST_STONE
DEEPSLATE -> CobblemonItems.NEST_DEEPSLATE
SNOW -> CobblemonItems.NEST_SNOW
LUSH -> CobblemonItems.NEST_LUSH
SEAGRASS -> CobblemonItems.NEST_SEAGRASS
WARM_OCEAN -> CobblemonItems.NEST_WARM_OCEAN
SAND -> CobblemonItems.NEST_SAND
RED_SAND -> CobblemonItems.NEST_RED_SAND
NETHER -> CobblemonItems.NEST_NETHER
BASALT -> CobblemonItems.NEST_BASALT
SOUL_SAND -> CobblemonItems.NEST_SOUL_SAND
CHORUS -> CobblemonItems.NEST_CHORUS
//HAY -> CobblemonItems.NEST_HAY
}
// todo make a map of ideal placement blocks for each nest
fun placementBlocks(): List<Block> = when (this) {
STICK -> listOf(Blocks.GRASS_BLOCK, Blocks.DIRT, Blocks.PODZOL, Blocks.COARSE_DIRT)
LEAF -> listOf(Blocks.OAK_LEAVES, Blocks.JUNGLE_LEAVES, Blocks.DARK_OAK_LEAVES, Blocks.BIRCH_LEAVES, Blocks.ACACIA_LEAVES, Blocks.AZALEA_LEAVES, Blocks.MANGROVE_LEAVES, Blocks.CHERRY_LEAVES)
STONE -> listOf(Blocks.STONE, Blocks.ANDESITE, Blocks.DIORITE, Blocks.GRANITE, Blocks.DRIPSTONE_BLOCK, Blocks.COBBLESTONE, Blocks.ICE, Blocks.PACKED_ICE, Blocks.BLUE_ICE)
DEEPSLATE -> listOf(Blocks.DEEPSLATE, Blocks.COBBLED_DEEPSLATE)
SNOW -> listOf(Blocks.SNOW_BLOCK, Blocks.GRASS_BLOCK, Blocks.DIRT, Blocks.COARSE_DIRT) // todo dirt, grass, and coarse dirt ONLY if replacing the location of snowlayer
LUSH -> listOf(Blocks.MOSS_BLOCK, Blocks.CLAY)
SEAGRASS -> listOf(Blocks.DIRT, Blocks.STONE, Blocks.ANDESITE, Blocks.DIORITE, Blocks.MUD, Blocks.CLAY, Blocks.ANDESITE, Blocks.SAND, Blocks.GRAVEL) // todo (all only if surrounded by water AND not in any of: Warm Ocean, Lukewarm Ocean, or Deep Lukewarm Ocean biomes)
WARM_OCEAN -> listOf(Blocks.SAND, Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.FIRE_CORAL_BLOCK, Blocks.HORN_CORAL_BLOCK, Blocks.TUBE_CORAL_BLOCK, Blocks.BRAIN_CORAL_BLOCK, Blocks.BRAIN_CORAL_BLOCK, Blocks.BUBBLE_CORAL_BLOCK) // todo (all only if surrounded by water AND only when in any of: Warm Ocean, Lukewarm Ocean, or Deep Lukewarm Ocean biomes)
SAND -> listOf(Blocks.SAND, Blocks.SANDSTONE, Blocks.SMOOTH_SANDSTONE) // todo (only if NOT surrounded by water)
RED_SAND -> listOf(Blocks.RED_SAND, Blocks.RED_SANDSTONE, Blocks.SMOOTH_RED_SANDSTONE)
NETHER -> listOf(Blocks.NETHERRACK, Blocks.WARPED_NYLIUM, Blocks.CRIMSON_NYLIUM, Blocks.NETHER_WART_BLOCK, Blocks.WARPED_WART_BLOCK, Blocks.MAGMA_BLOCK)
BASALT -> listOf(Blocks.BASALT, Blocks.BLACKSTONE, Blocks.SMOOTH_BASALT)
SOUL_SAND -> listOf(Blocks.SOUL_SAND, Blocks.SOUL_SOIL)
CHORUS -> listOf(Blocks.END_STONE, Blocks.PURPUR_BLOCK, Blocks.END_STONE_BRICKS)
//HAY -> listOf(Blocks.HAY_BLOCK, Blocks.GRASS_BLOCK)
}
// todo make a map or something for each nest that can be waterlogged and not
/**
* Determines if a nest can be waterlogged.
* Some nests, like those made of water-related materials, can be placed in water.
*/
fun isWaterloggable(): Boolean = when (this) {
SEAGRASS, WARM_OCEAN -> true
else -> false
}
/**
* Determines if this nest type can be placed based on **biome tags**.
*/
fun isValidBiome(world: ServerLevel, pos: BlockPos): Boolean {
val biomeHolder: Holder<Biome> = world.getBiome(pos)
return when (this) {
STICK -> biomeHolder.`is`(CobblemonBiomeTags.HAS_STICK_NEST)
LEAF -> biomeHolder.`is`(CobblemonBiomeTags.HAS_LEAF_NEST_TREES)
STONE -> biomeHolder.`is`(CobblemonBiomeTags.HAS_STONE_NEST)
DEEPSLATE -> biomeHolder.`is`(CobblemonBiomeTags.HAS_DEEPSLATE_NEST_CAVES)
SNOW -> biomeHolder.`is`(CobblemonBiomeTags.HAS_SNOW_NEST)
LUSH -> biomeHolder.`is`(CobblemonBiomeTags.HAS_LUSH_NEST_CAVES)
SEAGRASS -> biomeHolder.`is`(CobblemonBiomeTags.HAS_SEAGRASS_NEST)
WARM_OCEAN -> biomeHolder.`is`(CobblemonBiomeTags.HAS_WARM_OCEAN_NEST)
SAND -> biomeHolder.`is`(CobblemonBiomeTags.HAS_SAND_NEST)
RED_SAND -> biomeHolder.`is`(CobblemonBiomeTags.HAS_RED_SAND_NEST)
NETHER -> biomeHolder.`is`(CobblemonBiomeTags.HAS_NETHER_NEST)
BASALT -> biomeHolder.`is`(CobblemonBiomeTags.HAS_BASALT_NEST)
SOUL_SAND -> biomeHolder.`is`(CobblemonBiomeTags.HAS_SOUL_SAND_NEST)
CHORUS -> biomeHolder.`is`(CobblemonBiomeTags.HAS_CHORUS_NEST)
}
}
// Crab Note: todo maybe we want these to be a List of Items?
fun materials(): Item = when(this) {
STICK -> Items.STICK
LEAF -> Items.OAK_SAPLING
STONE -> Items.COBBLESTONE
DEEPSLATE -> Items.COBBLED_DEEPSLATE
SNOW -> Items.SNOWBALL
LUSH -> Items.MOSS_BLOCK
SEAGRASS -> Items.KELP
WARM_OCEAN -> Items.SEA_PICKLE
SAND -> Items.SAND
RED_SAND -> Items.RED_SAND
NETHER -> Items.WARPED_ROOTS
BASALT -> Items.BASALT
SOUL_SAND -> Items.SOUL_SAND
CHORUS -> Items.CHORUS_FRUIT
//HAY -> Items.WHEAT
}
// Crab Note: todo maybe we want to redo this if we have lists of items needed for each?
/*fun craftedNestResult(itemStack: ItemStack): BlockItem? = when(itemStack) {
ItemStack(Items.STICK, 4) -> CobblemonItems.NEST_STICK
ItemStack(Items.OAK_SAPLING, 4) -> CobblemonItems.NEST_LEAF
ItemStack(Items.COBBLESTONE, 4) -> CobblemonItems.NEST_STONE
ItemStack(Items.COBBLED_DEEPSLATE, 4) -> CobblemonItems.NEST_DEEPSLATE
ItemStack(Items.SNOWBALL, 4) -> CobblemonItems.NEST_SNOW
ItemStack(Items.MOSS_BLOCK, 4) -> CobblemonItems.NEST_LUSH
ItemStack(Items.KELP, 4) -> CobblemonItems.NEST_SEAGRASS
ItemStack(Items.SEA_PICKLE, 4) -> CobblemonItems.NEST_WARM_OCEAN
ItemStack(Items.SAND, 4) -> CobblemonItems.NEST_SAND
ItemStack(Items.RED_SAND, 4) -> CobblemonItems.NEST_RED_SAND
ItemStack(Items.WARPED_ROOTS, 4) -> CobblemonItems.NEST_NETHER
ItemStack(Items.BASALT, 4) -> CobblemonItems.NEST_BASALT
ItemStack(Items.SOUL_SAND, 4) -> CobblemonItems.NEST_SOUL_SAND
ItemStack(Items.CHORUS_FRUIT, 4) -> CobblemonItems.NEST_CHORUS
ItemStack(Items.WHEAT, 4) -> CobblemonItems.NEST_HAY
else -> null
}*/
override fun getSerializedName() = this.name.lowercase()
companion object {
val CODEC: Codec<Nest> = StringRepresentable.fromValues(::values)
private val materialRequirements: Map<Item, Int> = mapOf(
Items.STICK to 2,
Items.OAK_SAPLING to 2,
Items.COBBLESTONE to 2,
Items.COBBLED_DEEPSLATE to 2,
Items.SNOWBALL to 2,
Items.MOSS_BLOCK to 2,
Items.KELP to 2,
Items.SEA_PICKLE to 2,
Items.SAND to 2,
Items.RED_SAND to 2,
Items.WARPED_ROOTS to 2,
Items.BASALT to 2,
Items.SOUL_SAND to 2,
Items.CHORUS_FRUIT to 2,
//Items.WHEAT to 2
)
/**
* Determines if a held stack is sufficient to craft a nest and returns the crafted item and leftover count.
* @param heldStack The item stack in the Pokémon's hand.
* @return A Pair of (crafted nest block item, remaining materials in hand) or null if crafting is not possible.
*/
fun craftedNestResult(heldStack: ItemStack): Pair<BlockItem, Int>? {
val requiredAmount = materialRequirements[heldStack.item] ?: return null // Check if item is a valid material
// Ensure Pokémon has enough materials
if (heldStack.count >= requiredAmount) {
val remaining = heldStack.count - requiredAmount
// Find the nest type that matches the material
val matchingNest = entries.firstOrNull { it.materials() == heldStack.item } ?: return null
return Pair(matchingNest.item(), remaining)
}
return null // Not enough materials
}
}
}
@@ -63,7 +63,7 @@ object NPCClasses : JsonDataRegistry<NPCClass> {
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(SleepDepth::class.java, SleepDepth.adapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(CompoundTag::class.java, CompoundTagAdapter)
.registerTypeAdapter(NPCPartyProvider::class.java, NPCPartyProviderAdapter)
.registerTypeAdapter(NPCInteractConfiguration::class.java, NPCInteractConfigurationAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
@@ -59,7 +59,7 @@ object NPCPresets : JsonDataRegistry<NPCPreset> {
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(SleepDepth::class.java, SleepDepth.adapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
//.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(NPCPartyProvider::class.java, NPCPartyProviderAdapter)
.registerTypeAdapter(NPCInteractConfiguration::class.java, NPCInteractConfigurationAdapter)
.registerTypeAdapter(WeightedAspect::class.java, WeightedAspectAdapter)
@@ -31,6 +31,9 @@ object CobblemonPermissions {
val GIVE_POKEMON_SELF = this.create("${GIVE_POKEMON_BASE}.self", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val GIVE_POKEMON_OTHER = this.create("${GIVE_POKEMON_BASE}.other", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val GIVE_POKEMON_EGG = this.create("${COMMAND_PREFIX}givepokemonegg", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
private const val HEAL_POKEMON_BASE = "${COMMAND_PREFIX}healpokemon"
@JvmStatic
val HEAL_POKEMON_SELF = this.create("$HEAL_POKEMON_BASE.self", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -110,7 +110,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(SleepDepth::class.java, SleepDepth.adapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(CompoundTag::class.java, CompoundTagAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
@@ -186,6 +186,22 @@ object PokemonSpecies : JsonDataRegistry<Species> {
*/
fun getByIdentifier(identifier: ResourceLocation) = this.speciesByIdentifier[identifier]
/**
* Finds the first [Species] in an evolution line
*
* @param species The [Species] evolution line to use
* @return The [Species] if existing.
*/
fun getFirstEvolution(species: Species): Species {
var returnSpecies = species
while (returnSpecies.preEvolution != null) {
returnSpecies = returnSpecies.preEvolution!!.species
}
return returnSpecies
}
/**
* Counts the currently loaded species.
*
@@ -0,0 +1,599 @@
/*
* 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.pokemon.breeding
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.abilities.AbilityTemplate
import com.cobblemon.mod.common.api.breeding.BreedingRule
import com.cobblemon.mod.common.api.breeding.BreedingRules
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.*
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbilityType
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.item.Item
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.Holder
import kotlin.random.Random
interface BreedingLogic {
/**
* Takes two [Pokemon] and returns a [BreedingResult].
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @return The [BreedingResult] of the two [Pokemon]
* @author Apion, Nick, whatsy, plastered_crab
*/
// TODO we may want to call this method "hatch" now since we store parents on the egg itself now
fun breed(mother: Pokemon, father: Pokemon): BreedingResult {
if (!this.canBreed(mother, father)) {
return BreedingResult()
}
// Crab note: TODO Clean this shit up it is ugly af and idk if we even want to do custom breeding rules this way.... HIRO HELP
// find matching breeding rule
val rule = BreedingRules.getBreedingRule(mother, father)
val fallbackForm: FormData = this.calculateForm(mother, father)
val finalSpecies: Species = if (rule?.species != null) {
PokemonSpecies.getByName(rule.species.lowercase()) ?: fallbackForm.species
} else {
fallbackForm.species
}
val finalForm: FormData = if (rule != null && rule.form != null) {
val speciesEntry = rule.species?.let { PokemonSpecies.getByName(it.lowercase()) } ?: fallbackForm.species
speciesEntry.forms.find { formData ->
formData.name.lowercase() == rule.form.lowercase()
} ?: speciesEntry.standardForm
} else {
fallbackForm
}
// Calculate fallback IVs, then override stats from the rule if non-null
val finalIVs = mergeIvs(rule, mother, father)
val finalNature: Nature = if (rule?.nature != null) {
Natures.getNature(rule.nature.lowercase()) ?: this.calculateNature(mother, father)
} else {
this.calculateNature(mother, father)
}
// TODO get the ability to actually apply
val finalAbility: Ability = if (rule?.ability != null) {
Abilities.get(rule.ability.lowercase())?.create(true, Priority.HIGHEST) ?: this.calculateAbility(mother, father, finalForm)
} else {
this.calculateAbility(mother, father, finalForm)
}
// Handle moves from the rule. We read move1..move4 if present, turn them into MoveTemplates,
// then pass them as `eggMoves` to our `calculateMoveset`.
val ruleMoveTemplates: List<MoveTemplate> = if (rule != null) {
listOfNotNull(
rule.moves.move1,
rule.moves.move2,
rule.moves.move3,
rule.moves.move4
).mapNotNull { moveString ->
Moves.getByName(moveString.lowercase())
}
} else {
emptyList()
}
// Pass these move templates to `calculateMoveset` as the optional egg moves.
val finalMoveSet = this.calculateMoveset(
mother,
father,
finalForm ?: fallbackForm,
eggMoves = ruleMoveTemplates
)
// Determine shiny based on masuda
val masudaActive = masudaActive(mother,father)
val isShiny = when {
masudaActive -> (Random.nextFloat() < 1f / Cobblemon.config.masudaShinyRate)
else -> (Random.nextFloat() < 1f / Cobblemon.config.shinyRate)
}
val finalGender: Gender = if (rule?.gender != null) {
Gender.entries.find { it.name.equals(rule.gender, ignoreCase = true) }
?: this.calculateGender(mother, father, finalForm)
} else {
this.calculateGender(mother, father, finalForm)
}
val finalPokeball: PokeBall = if (rule?.pokeball != null) {
PokeBalls.getPokeBall(cobblemonResource(rule.pokeball.lowercase())) ?: this.calculatePokeball(mother, father)
} else {
this.calculatePokeball(mother, father)
}
// create final egg from all this awful data
val newPoke = EggPokemon(
ivs = finalIVs,
nature = finalNature,
species = getPreEvoSpecies(finalSpecies),
formData = finalForm,
ability = finalAbility,
moveSet = finalMoveSet,
isShiny = isShiny,
gender = finalGender,
pokeball = finalPokeball
)
return BreedingResult(newPoke, calcTimeToHatch(mother, father))
}
private fun mergeIvs(
rule: BreedingRule?,
mother: Pokemon,
father: Pokemon
): IVs {
val fallbackIvs = this.calculateIVs(mother, father)
if (rule == null) return fallbackIvs
// 3. For each stat, if rule.ivs.<stat> is non-null, override fallback
rule.ivs.hp?.let { fallbackIvs[Stats.HP] = it }
rule.ivs.attack?.let { fallbackIvs[Stats.ATTACK] = it }
rule.ivs.defence?.let { fallbackIvs[Stats.DEFENCE] = it }
rule.ivs.special_attack?.let { fallbackIvs[Stats.SPECIAL_ATTACK] = it }
rule.ivs.special_defence?.let { fallbackIvs[Stats.SPECIAL_DEFENCE] = it }
rule.ivs.speed?.let { fallbackIvs[Stats.SPEED] = it }
return fallbackIvs
}
fun masudaActive(mother: Pokemon, father: Pokemon) : Boolean {
val isDifferentOT = (mother.originalTrainer != father.originalTrainer)
val bothMaxFriendship = (mother.friendship == 255 && father.friendship == 255)
val requireBothOTs = Cobblemon.config.requireOTsForMasuda
return when {
requireBothOTs -> (isDifferentOT && bothMaxFriendship)
else -> (isDifferentOT || bothMaxFriendship)
}
}
/**
* Determines if two [Pokemon] can breed.
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @return Whether the two [Pokemon] can breed
* @author Apion, whatsy, plastered_crab
*/
fun canBreed(mother: Pokemon, father: Pokemon): Boolean {
val canBreedUndiscovered = Cobblemon.config.canBreedUndiscovered
val canBreedGenderless = Cobblemon.config.canBreedGenderless
val canBreedDitto = Cobblemon.config.canBreedDitto
val areSameSpecies = mother.species == father.species
val sharedEggGroups = mother.form.eggGroups.intersect(father.form.eggGroups)
return when {
// check custom breeding rules first
(BreedingRules.getBreedingRule(mother, father) != null) -> true
// Check if config allows Undiscovered egg groups to breed
(canBreedUndiscovered && areSameSpecies && mother.form.eggGroups.contains(EggGroup.UNDISCOVERED)) -> true
// Check if either Pokémon is in the No Eggs Discovered egg group
(mother.form.eggGroups.contains(EggGroup.UNDISCOVERED) || father.form.eggGroups.contains(EggGroup.UNDISCOVERED)) -> false
// check if config allows ditto to breed with each other
(canBreedDitto && areSameSpecies && mother.form.eggGroups.contains(EggGroup.DITTO)) -> true
// Check if both Pokémon are Ditto
(mother.form.eggGroups.contains(EggGroup.DITTO) && father.form.eggGroups.contains(EggGroup.DITTO)) -> false
// check it config allows for same species genderless Pokemon can breed
(canBreedGenderless && (areSameSpecies && mother.gender == Gender.GENDERLESS)) -> true
// check for genderless breeding that is not with ditto
(mother.gender == Gender.GENDERLESS && !father.form.eggGroups.contains(EggGroup.DITTO)) ||
(father.gender == Gender.GENDERLESS && !mother.form.eggGroups.contains(EggGroup.DITTO)) -> false
// if either parent is ditto
(mother.form.eggGroups.contains(EggGroup.DITTO) || father.form.eggGroups.contains(EggGroup.DITTO)) -> true
// check if egg groups match
else -> sharedEggGroups.isNotEmpty() && mother.gender != father.gender
}
/*
// check custom breeding rules first
if (BreedingRules.getBreedingRule(mother, father) != null)
return true
// check if different species and Genderless
if (!areSameSpecies && (mother.gender == Gender.GENDERLESS))
return false
// Check if either Pokémon is in the No Eggs Discovered egg group
if (!canBreedUndiscovered && (mother.form.eggGroups.contains(EggGroup.UNDISCOVERED) || father.form.eggGroups.contains(EggGroup.UNDISCOVERED)))
return false
// Check if both Pokémon are Ditto
if (canBreedDitto && (mother.form.eggGroups.contains(EggGroup.DITTO) && father.form.eggGroups.contains(EggGroup.DITTO)))
return false
// check if both parents are Genderless and same species
if (canBreedGenderless && (areSameSpecies && mother.gender == Gender.GENDERLESS))
return true
// Check if at least one parent is Ditto
val anyDitto = mother.form.eggGroups.contains(EggGroup.DITTO) || father.form.eggGroups.contains(EggGroup.DITTO)
if (anyDitto)
return true
// Check for shared egg groups and opposite genders
val sharedEggGroups = mother.form.eggGroups.intersect(father.form.eggGroups)
return sharedEggGroups.isNotEmpty() && mother.gender != father.gender*/
}
/**
* Determines the base pre-evolution of a [Pokemon] species.
*
* @param species The Species of [Pokemon]
* @return The base pre-evolution Species of [Pokemon]
* @author Plastered_Crab
*/
private fun getPreEvoSpecies(species: Species): Species {
var currentSpecies = species
while (currentSpecies.preEvolution != null) {
currentSpecies = currentSpecies.preEvolution!!.species
}
return currentSpecies
}
/**
* Determines the [Gender] of a child
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @param form The [FormData] of the child
* @return The [Gender] of the child
* @author Apion, whatsy
*/
fun calculateGender(mother: Pokemon, father: Pokemon, form: FormData): Gender {
val maleRatio = form.maleRatio
if (maleRatio.toDouble() == -1.0) {
return Gender.GENDERLESS
}
val rand = Random.nextDouble(0.0, 1.0)
return if (rand < maleRatio) {
Gender.MALE
} else {
Gender.FEMALE
}
}
/**
* Determines the [FormData] of a child
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @return The [FormData] of the child
* @author Nick, Apion, whatsy
*/
fun calculateForm(mother: Pokemon, father: Pokemon): FormData {
/*
* In most cases, if a hatched species has multiple forms (not dependent on in-battle conditions), it will
* often inherit the form of the mother or non-Ditto parent. Burmy will always hatch with the same cloak as
* its mother, and if bred between Mothim and Ditto, it will always hatch with a Plant Cloak. Rockruff with the
* Ability Own Tempo, which evolves into Lycanroc's Dusk Form, is treated as a separate form and can pass down
* this trait accordingly to its offspring. Oricorio, which changes its form using nectars, can also pass down
* its form by breeding.
*
* There are a few exceptions to this inheritance pattern:
*
* - Rotom will always hatch into its normal form, but can be changed afterwards.
* - Vivillon's pattern depends on the real-world, geographic area of the save file it originated from, regardless of its parent's pattern.
* - The form that Scatterbug will evolve into is predetermined when the Egg is first obtained, not when hatched or evolved.
* - Scatterbug hatched in Scarlet and Violet will only evolve into Fancy Pattern Vivillon.
* - Furfrou, whose trims are temporary, always hatch in its Natural Form.
* - Sinistea will always hatch as a Phony Form, even if its parent is an Antique Form.
*
* In addition, species with regional forms will hatch into whichever form is native to that region. However,
* if a parent of a foreign form and the same family is holding an Everstone, the offspring will be of that
* parent's form instead.
*
* The Gigantamax Factor, Alpha, Jumbo and Mini marks cannot be passed down by breeding.
*
* Pokémon such as Dunsparce, Wurmple, and Tandemaus, where the species and form they will evolve into is
* predetermined when they are first generated, do not follow form inheritance rules. The forms of the parents
* have no influence on the personality value or encryption constant.
*
* TODO - Handle this via form data parameters?
*/
return nonDittoPreferMother(mother, father).form
}
/**
* Determines the [Nature] of a child
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @return The [Nature] of the child
* @author Nick, whatsy
*/
fun calculateNature(mother: Pokemon, father: Pokemon): Nature {
val motherHasEverstone = mother.heldItemNoCopy().item.let { BuiltInRegistries.ITEM.wrapAsHolder(it) }.`is`(CobblemonItemTags.EVERSTONE)
val fatherHasEverstone = father.heldItemNoCopy().item.let { BuiltInRegistries.ITEM.wrapAsHolder(it) }.`is`(CobblemonItemTags.EVERSTONE)
return when {
motherHasEverstone && fatherHasEverstone -> if (Random.nextInt(100) < 50) mother.nature else father.nature
motherHasEverstone -> mother.nature
fatherHasEverstone -> father.nature
else -> Natures.getRandomNature()
}
}
/**
* Determines the [Ability] of a child
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @param child The [FormData] of the child
* @return The [Ability] of the child
* @author Nick, whatsy
*/
fun calculateAbility(mother: Pokemon, father: Pokemon, child: FormData): Ability {
val parent = this.nonDittoPreferMother(mother, father)
val hidden = parent.form.abilities.filter { it.type == HiddenAbilityType }
.map { it.template.name }
.map { it == parent.ability.name }
.firstOrNull()
return if(hidden == true) {
val rng = Random.nextInt(0, 100) < 60
if(rng) {
parent.ability
} else {
child.abilities.select(child.species, child.aspects.toSet()).first
}
} else {
val rng = Random.nextInt(0, 100) < 80
if(rng) {
parent.ability
} else {
child.abilities.select(child.species, child.aspects.toSet()).first
}
}
}
/**
* Determines the [MoveSet] of a child
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @form the form of the [Pokemon]
* @return The [MoveSet] of the child
* @author Nick, Apion, whatsy
*/
// TODO: This, based off of https://pokemondb.net/mechanics/breeding
fun calculateMoveset(mother: Pokemon, father: Pokemon, form: FormData, eggMoves: List<MoveTemplate>? = null): MoveSet {
val babyLearnset = form.moves.levelUpMoves[1] ?: emptySet()
val inheritedMoves = mutableListOf<MoveTemplate>()
// Add Egg Moves using the JSON registry based on parent breeding rules
//val eggMoves = BreedingRules.getEggMoveList(mother, father, babySpecies.resourceIdentifier)
if (eggMoves != null) {
inheritedMoves.addAll(eggMoves)
}
// Add TM/HM Moves from both mother and father
val tmHmMoves = (mother.moveSet.getMoves() + father.moveSet.getMoves())
.map { it.template }
.filter { form.species.moves.tmMoves.contains(it) }
inheritedMoves.addAll(tmHmMoves)
// Add Inherited Level-Up Moves
inheritedMoves.addAll(mother.moveSet.getMoves()
.map { it.template }
.filter { it in babyLearnset })
inheritedMoves.addAll(father.moveSet.getMoves()
.map { it.template }
.filter { it in babyLearnset })
// Add Baby's Default Moves
inheritedMoves.addAll(babyLearnset)
// TODO sort the moves properly
// Limit to 4 moves, prioritizing certain moves first based off rules set by the games
val finalMoves = inheritedMoves.distinct().sortedWith(
compareByDescending<MoveTemplate> { it.name == "volt_tackle" } // Volt Tackle first
.thenByDescending { eggMoves?.contains(it) == true } // Egg moves next
.thenByDescending { tmHmMoves.contains(it) } // TM/HM moves after
.thenByDescending { babyLearnset.contains(it) } // Default moves last
).take(4)
val moveSet = MoveSet()
finalMoves.forEach { moveTemplate ->
moveSet.add(moveTemplate.create())
}
return moveSet
}
/**
* Determines the [IVs] of a child
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @return The [IVs] of the child
* @author Nick, whatsy, Apion
*/
fun calculateIVs(mother: Pokemon, father: Pokemon): IVs {
//FIXME: Use registry holder
val motherItem = mother.heldItemNoCopy().item.let { BuiltInRegistries.ITEM.wrapAsHolder(it) }
val fatherItem = father.heldItemNoCopy().item.let { BuiltInRegistries.ITEM.wrapAsHolder(it) }
val isMotherDestinyKnotted = motherItem.`is`(CobblemonItemTags.DESTINY_KNOT)
val isFatherDestinyKnotted = fatherItem.`is`(CobblemonItemTags.DESTINY_KNOT)
val passedStats = mutableSetOf<Stat>()
val childStats = IVs.createRandomIVs()
var numPassed = if(isMotherDestinyKnotted || isFatherDestinyKnotted) {
5
} else {
3
}
fun calcForcedStat(holder: Holder<Item>?, mon: Pokemon) {
fun passStat(stats: Stat) {
if (!passedStats.contains(stats)) {
childStats[stats] = mon.ivs[stats]!!
passedStats.add(stats)
numPassed--
}
}
if(holder == null) {
return
}
if(holder.`is`(CobblemonItemTags.POWER_ANKLET)) {
passStat(Stats.SPEED)
} else if(holder.`is`(CobblemonItemTags.POWER_BAND)) {
passStat(Stats.SPECIAL_DEFENCE)
} else if(holder.`is`(CobblemonItemTags.POWER_BELT)) {
passStat(Stats.DEFENCE)
} else if(holder.`is`(CobblemonItemTags.POWER_BRACER)) {
passStat(Stats.ATTACK)
} else if(holder.`is`(CobblemonItemTags.POWER_LENS)) {
passStat(Stats.SPECIAL_ATTACK)
} else if(holder.`is`(CobblemonItemTags.POWER_WEIGHT)) {
passStat(Stats.HP)
}
}
calcForcedStat(fatherItem, father)
calcForcedStat(motherItem, mother)
val parentIvs = mother.ivs.toMutableList()
parentIvs.addAll(father.ivs)
while (numPassed > 0 && parentIvs.isNotEmpty()) {
val statPicked: Map.Entry<Stat, Int> = parentIvs.random()
if (!passedStats.contains(statPicked.key)) {
passedStats.add(statPicked.key)
childStats[statPicked.key] = statPicked.value
parentIvs.removeAll {it.key == statPicked.key}
numPassed--
}
}
return childStats
}
/**
* Determines the [PokeBall] of a child
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @return The [PokeBall] of the child
* @author Nick, whatsy
*/
fun calculatePokeball(mother: Pokemon, father: Pokemon): PokeBall {
/*
* The Master Ball, Cherish Ball, and Strange Ball cannot be inherited via breeding; instead, the game treats
* any parent obtained in those balls as if they were in a standard Poké Ball for the purposes of inheritance.
* To add Poké Balls to this list, add to the defaulting_poke_balls.json item tag.
*/
val anyDitto = listOf(mother, father).any { it.form.eggGroups.contains(EggGroup.DITTO) }
val motherBallItem = mother.caughtBall.item().let { BuiltInRegistries.ITEM.wrapAsHolder(it) }
val fatherBallItem = father.caughtBall.item().let { BuiltInRegistries.ITEM.wrapAsHolder(it) }
println(motherBallItem.`is`(CobblemonItemTags.DEFAULTING_POKE_BALLS))
val motherBall = if (motherBallItem.`is`(CobblemonItemTags.DEFAULTING_POKE_BALLS)) PokeBalls.POKE_BALL else mother.caughtBall
val fatherBall = if (fatherBallItem.`is`(CobblemonItemTags.DEFAULTING_POKE_BALLS)) PokeBalls.POKE_BALL else father.caughtBall
val ball: PokeBall = if(anyDitto) {
if (nonDittoPreferMother(mother, father) == mother) motherBall else fatherBall
} else if(mother.species.resourceIdentifier == father.species.resourceIdentifier) {
if(Random.nextInt(100) < 50) {
motherBall
} else {
fatherBall
}
} else {
motherBall
}
return ball
}
/**
* If the mother is a Ditto, returns the father.
* Otherwise, returns the mother.
*
* @param mother The mother [Pokemon]
* @param father The father [Pokemon]
* @author Nick
*/
fun nonDittoPreferMother(mother: Pokemon, father: Pokemon): Pokemon {
return if(mother.form.eggGroups.contains(EggGroup.DITTO)) father else mother
}
fun resetBreedingCooldown(pokemon: Pokemon): Int {
val eggCycle = (pokemon.species.eggCycles).coerceAtLeast(1)
val minEggCycle = 5 // this is the lowest eggCycle (like caterpie)
val avgEggCycle = 40 // average eggCycle value
val minTime = 5 // in minutes
val avgTime = 20 // in minutes
val slope = (avgTime - minTime) / (avgEggCycle - minEggCycle)
val intercept = minTime - slope * minEggCycle
val cooldown = slope * eggCycle + intercept
return cooldown * 1200 // convert to ticks
}
fun calcTimeToHatch(mother: Pokemon, father: Pokemon): Int {
val avgEggCycle = ((mother.species.eggCycles + father.species.eggCycles) / 2).toInt()
val cycleToMinute = 2
return (avgEggCycle * cycleToMinute) * 1200 // convert to ticks
}
fun checkCompatibility(mother: Pokemon, father: Pokemon): Boolean {
val sameSpecies = mother.species == father.species
val sameTrainer = mother.getOwnerUUID() == father.getOwnerUUID()
val chance = when {
sameSpecies && !sameTrainer -> 70 // Same species, different trainer
sameSpecies && sameTrainer -> 50 // Same species, same trainer
!sameSpecies && !sameTrainer -> 50 // Different species, different trainer
!sameSpecies && sameTrainer -> 20
else -> 0 // Different species, same trainer
}
return Random.nextInt(100) < chance
}
}
@@ -0,0 +1,17 @@
/*
* 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.pokemon.breeding
import com.cobblemon.mod.common.pokemon.Pokemon
data class BreedingResult(
val pokemon: EggPokemon? = null,
val timeToHatch: Int = 20000,
val successful: Boolean = pokemon != null
)
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.breeding
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.item.components.EggComponent
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.resources.ResourceLocation
import java.util.*
data class Egg(
val species: ResourceLocation, // Species of the Pokémon
val parentOne: Pokemon?, // First parent's UUID
val parentTwo: Pokemon?, // Second parent's UUID
val form: String, // Form of the Pokémon
var timeToHatch: Float // Time in ticks to hatch
) {
companion object {
val CODEC: Codec<Egg?> = RecordCodecBuilder.create { instance ->
instance.group(
ResourceLocation.CODEC.optionalFieldOf("species", cobblemonResource("unknown")).forGetter { it?.species ?: cobblemonResource("unknown") },
Pokemon.CODEC.optionalFieldOf("parentOne").forGetter { it?.parentOne?.let { Optional.of(it) } ?: Optional.empty() },
Pokemon.CODEC.optionalFieldOf("parentTwo").forGetter { it?.parentTwo?.let { Optional.of(it) } ?: Optional.empty() },
Codec.STRING.optionalFieldOf("form", "").forGetter { it?.form ?: "" },
Codec.FLOAT.optionalFieldOf("timeToHatch", 0F).forGetter { it?.timeToHatch ?: 0F }
).apply(instance) { species, parentOneOpt, parentTwoOpt, form, timeToHatch ->
if (species != cobblemonResource("unknown") || timeToHatch != 0F || parentOneOpt.isPresent || parentTwoOpt.isPresent || form.isNotEmpty()) {
Egg(
species = species,
parentOne = parentOneOpt.orElse(null),
parentTwo = parentTwoOpt.orElse(null),
form = form,
timeToHatch = timeToHatch
)
} else {
null // Represents no egg.
}
}
}
}
fun createEggItemStack(): ItemStack {
// Create a new ItemStack using the Pokémon Egg item
val eggItemStack = CobblemonItems.POKEMON_EGG.defaultInstance
// Add the EggData component to the ItemStack
val eggComponent = EggComponent(this)
eggItemStack.set(CobblemonItemComponents.EGG_DATA, eggComponent)
return eggItemStack
}
}
/*fun getPattern(): EggPattern? {
return EggPatterns.patternMap[patternId]
}
companion object {
fun fromBlockNbt(blockNbt: CompoundTag): Egg {
val eggNbt = blockNbt.get(DataKeys.EGG) as CompoundTag
return fromNbt(eggNbt)
}
fun fromNbt(nbt: CompoundTag): Egg {
return Egg(
EggPokemon.fromNBT(nbt.get(DataKeys.HATCHED_POKEMON) as CompoundTag),
ResourceLocation.tryParse(nbt.getString(DataKeys.EGG_PATTERN))!!,
nbt.getString(DataKeys.PRIMARY_COLOR),
if (nbt.contains(DataKeys.SECONDARY_COLOR)) nbt.getString(DataKeys.SECONDARY_COLOR) else null,
nbt.getInt(DataKeys.TIME_TO_HATCH)
)
}
//Less expensive than deserializing the whole thing, used for the color provider since it gets
//called every frame
fun getColorsFromNbt(nbt: CompoundTag): Pair<String, String> {
return Pair(
nbt.getString(DataKeys.PRIMARY_COLOR),
nbt.getString(DataKeys.SECONDARY_COLOR)
)
}
}
}*/
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.breeding
import net.minecraft.resources.ResourceLocation
/**
* Represents a pattern on an egg
*
* @author Apion
* @since January 2nd, 2024
*/
data class EggPattern (
val model: ResourceLocation,
val baseTexturePath: ResourceLocation,
val overlayTexturePath: ResourceLocation?,
val baseInvSpritePath: ResourceLocation,
val overlayInvSpritePath: ResourceLocation?,
)
@@ -0,0 +1,41 @@
/*
* 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.pokemon.breeding
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.server.packs.PackType
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
object EggPatterns : JsonDataRegistry<EggPattern> {
override val id = cobblemonResource("egg_patterns")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<EggPatterns>()
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.create()
override val typeToken = TypeToken.get(EggPattern::class.java)
override val resourcePath = "egg_patterns"
val patternMap = hashMapOf<ResourceLocation, EggPattern>()
override fun reload(data: Map<ResourceLocation, EggPattern>) {
patternMap.putAll(data)
}
override fun sync(player: ServerPlayer) {
//TODO: Need to implement
}
}
@@ -0,0 +1,90 @@
/*
* 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.pokemon.breeding
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.*
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
class EggPokemon(
val ivs: IVs,
val nature: Nature,
val species: Species,
val formData: FormData,
val ability: Ability,
val moveSet: MoveSet,
val isShiny: Boolean,
val gender: Gender,
val pokeball: PokeBall
) {
fun generatePokemon(): Pokemon {
val result = Pokemon()
ivs.forEach {
result.setIV(it.key, it.value)
}
result.species = species
result.gender = gender
result.nature = nature
result.moveSet.clear()
result.moveSet.copyFrom(moveSet)
result.shiny = isShiny
result.caughtBall = pokeball
result.form = formData
result.ability = ability
return result
}
fun toNbt(): CompoundTag {
val result = CompoundTag()
val ivNbt = ivs.saveToNBT(CompoundTag())
result.put(DataKeys.POKEMON_IVS, ivNbt)
result.putString(DataKeys.POKEMON_NATURE, nature.name.toString())
result.putString(DataKeys.POKEMON_SPECIES_IDENTIFIER, species.resourceIdentifier.toString())
result.putString(DataKeys.POKEMON_FORM_ID, formData.formOnlyShowdownId())
val abilityNBT = ability.saveToNBT(CompoundTag())
result.put(DataKeys.POKEMON_ABILITY, abilityNBT)
result.put(DataKeys.POKEMON_MOVESET, moveSet.getNBT())
result.putBoolean(DataKeys.POKEMON_SHINY, isShiny)
result.putString(DataKeys.POKEMON_GENDER, gender.name)
result.putString(DataKeys.POKEMON_CAUGHT_BALL, pokeball.name.toString())
return result
}
companion object {
// I heard you like casts so I put some casts in your casts
fun fromNBT(nbt: CompoundTag): EggPokemon {
val species = PokemonSpecies.getByIdentifier(ResourceLocation.tryParse(nbt.getString(DataKeys.POKEMON_SPECIES_IDENTIFIER))!!)!!
val abilityNBT = nbt.getCompound(DataKeys.POKEMON_ABILITY) ?: CompoundTag()
val abilityName = abilityNBT.getString(DataKeys.POKEMON_ABILITY_NAME).takeIf { it.isNotEmpty() } ?: "runaway"
val moveSet = MoveSet()
val pokeball = nbt.getString(DataKeys.POKEMON_CAUGHT_BALL)
return EggPokemon(
IVs().loadFromNBT(nbt.get(DataKeys.POKEMON_IVS) as CompoundTag) as IVs,
Natures.getNature(ResourceLocation.tryParse(nbt.getString(DataKeys.POKEMON_NATURE))!!)!!,
species,
species.forms.firstOrNull { it.formOnlyShowdownId() == nbt.getString(DataKeys.POKEMON_FORM_ID) } ?: species.standardForm,
Abilities.getOrException(abilityName).create(abilityNBT),
moveSet.loadFromNBT(nbt),
nbt.getBoolean(DataKeys.POKEMON_SHINY),
Gender.valueOf(nbt.getString(DataKeys.POKEMON_GENDER)),
PokeBalls.getPokeBall(ResourceLocation.tryParse(pokeball)!!) ?: PokeBalls.POKE_BALL
)
}
}
}
@@ -18,37 +18,23 @@ import com.cobblemon.mod.common.api.spawning.condition.SeafloorSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.LavafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SeafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SurfaceSpawningContext
import com.cobblemon.mod.common.api.spawning.context.*
import com.cobblemon.mod.common.api.spawning.context.calculators.GroundedSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.LavafloorSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SeafloorSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SubmergedSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SurfaceSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.detail.NPCSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.*
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawner
import com.cobblemon.mod.common.api.spawning.fishing.NestSpawner
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.preset.BasicSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
import com.cobblemon.mod.common.api.spawning.preset.PokemonSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
import com.cobblemon.mod.common.api.spawning.spawner.AreaSpawner
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.TickingSpawner
import com.cobblemon.mod.common.api.spawning.spawner.*
import com.cobblemon.mod.common.entity.pokemon.CobblemonAgingDespawner
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.server.MinecraftServer
@@ -142,5 +128,12 @@ object BestSpawner {
CobblemonSpawnPools.onServerLoad(server)
spawnerManagers.forEach(SpawnerManager::onServerStarted)
fishingSpawner = FishingSpawner()
val grottoSpawner = GrottoSpawner(
name = "grotto",
fallbackSpawns = SpawnPool("grotto_empty"),
manager = CobblemonWorldSpawnerManager
)
CobblemonWorldSpawnerManager.registerSpawner(grottoSpawner)
}
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningInfluenceProspector
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
@@ -79,6 +81,7 @@ object CobblemonSpawningProspector : SpawningProspector {
val blocks = Array(area.length) { Array(height) { Array(area.width) { defaultBlockData } } }
val skyLevel = Array(area.length) { Array(area.width) { world.maxBuildHeight } }
val pos = BlockPos.MutableBlockPos()
val worldSlicedSpawningInfluences = mutableListOf<WorldSlicedSpawningInfluence>()
val chunks = mutableMapOf<Pair<Int, Int>, ChunkAccess?>()
val yRange = (baseY until baseY + height).reversed()
@@ -98,6 +101,7 @@ object CobblemonSpawningProspector : SpawningProspector {
light = world.getMaxLocalRawBrightness(pos),
skyLight = skyLight
)
worldSlicedSpawningInfluences.addAll(SpawningInfluenceProspector.prospectors.mapNotNull { it.prospectBlock(world, pos, state) })
if (canSeeSky) {
skyLevel[x - area.baseX][z - area.baseZ] = y
}
@@ -108,6 +112,10 @@ object CobblemonSpawningProspector : SpawningProspector {
}
}
for (prospector in SpawningInfluenceProspector.prospectors) {
worldSlicedSpawningInfluences.addAll(prospector.prospect(spawner, area))
}
return WorldSlice(
cause = area.cause,
world = world,
@@ -116,7 +124,8 @@ object CobblemonSpawningProspector : SpawningProspector {
baseZ = area.baseZ,
blocks = blocks,
skyLevel = skyLevel,
nearbyEntityPositions = nearbyEntityPositions
nearbyEntityPositions = nearbyEntityPositions,
influences = worldSlicedSpawningInfluences
)
}
}
@@ -0,0 +1,13 @@
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.Entity
class GrottoSpawnCause(
spawner: Spawner,
bucket: SpawnBucket,
entity: Entity? = null,
val structureCenter: BlockPos,
val grottoPos: BlockPos
) : SpawnCause(spawner, bucket, entity)
@@ -68,7 +68,7 @@ object SpawnLoader {
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(CompoundTag::class.java, CompoundTagAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(PossibleHeldItem::class.java, PossibleHeldItemAdapter)
.registerTypeAdapter(NPCClass::class.java, NPCClassReferenceAdapter)
@@ -9,6 +9,8 @@
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
@@ -35,7 +37,8 @@ class WorldSlice(
val baseZ: Int,
val blocks: Array<Array<Array<BlockData>>>,
val skyLevel: Array<Array<Int>>,
var nearbyEntityPositions: List<Vec3>
var nearbyEntityPositions: List<Vec3>,
val influences: List<WorldSlicedSpawningInfluence>
) {
class BlockData(
val state: BlockState,
@@ -129,7 +132,7 @@ class WorldSlice(
}
fun horizontalSpace(position: BlockPos, condition: (BlockState) -> Boolean, maximum: Int)
= horizontalSpace(position.x, position.y, position.z, condition, maximum)
= horizontalSpace(position.x, position.y, position.z, condition, maximum)
fun horizontalSpace(centerX: Int, centerY: Int, centerZ: Int, condition: (BlockState) -> Boolean, maximum: Int): Int {
var space = 1
var radius = 1
@@ -0,0 +1,56 @@
/*
* 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.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.NestSpawningContext
import com.cobblemon.mod.common.util.Merger
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.world.item.Item
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.Block
/**
* A spawning condition that applies to [NestSpawningContext]s.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class NestSpawningCondition: SpawningCondition<NestSpawningContext>() {
override fun contextClass() = NestSpawningContext::class.java
var neededNearbyBlocks: MutableList<RegistryLikeCondition<Block>>? = null
override fun fits(ctx: NestSpawningContext): Boolean {
if (!super.fits(ctx)) {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockTypes.any { cond.fits(it, ctx.blockRegistry) } }) {
return false
}
return true
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
super.copyFrom(other, merger)
if (other is NestSpawningCondition) {
neededNearbyBlocks = merger.merge(neededNearbyBlocks, other.neededNearbyBlocks)?.toMutableList()
}
}
override fun isValid(): Boolean {
val containsNullValues = neededNearbyBlocks != null && neededNearbyBlocks!!.any {it == null}
return super.isValid() && !containsNullValues
}
companion object {
const val NAME = "nest"
}
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.util.Merger
import com.cobblemon.mod.common.util.math.orMax
@@ -0,0 +1,42 @@
/*
* 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.context
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.nest.NestSpawnCause
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.world.level.block.Block
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
import net.minecraft.world.phys.AABB
/**
* A spawning context that was generated for a nest spawn action.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class NestSpawningContext(
cause: NestSpawnCause,
world: ServerLevel,
pos: BlockPos,
influences: MutableList<SpawningInfluence>
) : TriggerSpawningContext(
cause = cause,
world = world,
position = pos,
light = world.getMaxLocalRawBrightness(pos),
skyLight = world.getMaxLocalRawBrightness(pos.above()),
canSeeSky = world.canSeeSkyFromBelowWater(pos),
influences = influences
) {
val nearbyBlocks = world.getBlockStates(AABB.ofSize(pos.toVec3d(), 10.0, 10.0, 10.0))
val nearbyBlockTypes: List<Block> by lazy { nearbyBlocks.map { it.block }.distinct().toList() }
}
@@ -216,6 +216,4 @@ class FishingSpawnCause(
}
return super.affectWeight(detail, ctx, weight)
}
}
@@ -0,0 +1,44 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.habitats.Habitats
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import net.minecraft.resources.ResourceLocation
import org.slf4j.LoggerFactory
class GrottoInfluence(
private val structureId: ResourceLocation,
private val allowedSpawns: List<ResourceLocation>? = null // null = allow all from habitat
) : SpawningInfluence {
/*companion object {
private val LOGGER = LoggerFactory.getLogger("GrottoInfluence")
}
override fun affectSpawnable(detail: SpawnDetail, ctx: SpawningContext): Boolean {
if (detail !is PokemonSpawnDetail) return true
val detailId = ResourceLocation.tryParse(detail.pokemon.species!!) ?: return false
// If strict filtering enabled, reject anything not in allowed list
if (allowedSpawns != null && !allowedSpawns.contains(detailId)) {
LOGGER.info("[GrottoInfluence] Rejecting ${detailId.path} — not in current moon phase group.")
return false
}
return true
}
override fun affectWeight(detail: SpawnDetail, ctx: SpawningContext, weight: Float): Float {
if (detail !is PokemonSpawnDetail) return weight
val detailId = ResourceLocation.tryParse(detail.pokemon.species!!) ?: return weight
val boostTargetList = allowedSpawns ?: Habitats.getHabitat(structureId)?.spawnPool?.flatten() ?: emptyList()
return if (boostTargetList.contains(detailId)) {
LOGGER.info("[GrottoInfluence] Boosting spawn weight for ${detailId.path} (structure: $structureId)")
weight * 100f
} else {
weight
}
}*/
}
@@ -0,0 +1,30 @@
/*
* 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.influence
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
/**
* A type of [WorldSlicedSpawningInfluence] that is only applied to contexts within a particular radius
* of a position.
*
* @author Hiroku
* @since March 9th, 2025
*/
class WorldSlicedSpatialSpawningInfluence(
val pos: BlockPos,
val radius: Float,
influence: SpawningInfluence
) : WorldSlicedSpawningInfluence(influence) {
override fun appliesTo(context: SpawningContext): Boolean {
return sqrt(context.position.distSqr(pos)) <= radius
}
}
@@ -0,0 +1,23 @@
/*
* 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.influence
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
/**
* A wrapping around a [SpawningInfluence] that was produced for a [WorldSlice]. This is wrapped
* to provide a filtration mechanism on which of these can be applied to a [SpawningContext].
*
* @author Hiroku
* @since March 9th, 2025
*/
open class WorldSlicedSpawningInfluence(val influence: SpawningInfluence) {
/** Whether this influence will apply to */
open fun appliesTo(context: SpawningContext): Boolean = true
}
@@ -0,0 +1,52 @@
/*
* 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.nest
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.fishing.BobberSpawnPokemonEvent
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.api.types.tera.TeraTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.ItemStack
import kotlin.random.Random.Default.nextInt
/**
* A spawning cause that is embellished with nest information.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class NestSpawnCause(
spawner: Spawner,
bucket: SpawnBucket,
entity: Entity?
) : SpawnCause(spawner, bucket, entity) {
// Crab Note: todo what do we put here? a reference to the nest entity causing this spawn cause?
}
@@ -0,0 +1,39 @@
/*
* 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.fishing
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.NestSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.nest.NestSpawnCause
import com.cobblemon.mod.common.api.spawning.spawner.TriggerSpawner
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
/**
* A spawner that takes in a [FishingSpawnCause] and spawns things.
*
* @author Plastered_Crab
* @since February 3rd, 2024
*/
open class NestSpawner(
override val name: String = "nest",
pool: SpawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
) : TriggerSpawner<NestSpawnCause>(name, pool) {
override fun parseContext(cause: NestSpawnCause, world: ServerLevel, pos: BlockPos): NestSpawningContext? {
val ctx = NestSpawningContext(
cause = cause,
world = world,
pos = pos,
influences = influences
)
return ctx
}
}
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.habitats.Habitats
import com.cobblemon.mod.common.api.spawning.influence.GrottoInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpatialSpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
import com.cobblemon.mod.common.block.GrottoBlock
import com.cobblemon.mod.common.block.entity.GrottoBlockEntity
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
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
import kotlin.math.ceil
import kotlin.math.sqrt
object GrottoBlockProspector : SpawningInfluenceProspector {
@JvmField
val RANGE: Int = 48
// todo going with this prospect method for now since the GrottoSpawner might be what I want instead of handling it here
override fun prospect(spawner: Spawner, area: SpawningArea): MutableList<WorldSlicedSpawningInfluence> {
return mutableListOf() // GrottoSpawner handles spawning directly now
}
// todo this was some test code of a prospect function that would try to alter the already filtered spawn lists, but I doubt we want to go this direction
/*override fun prospect(spawner: Spawner, area: SpawningArea): MutableList<WorldSlicedSpawningInfluence> {
val world = area.world
val listOfInfluences = mutableListOf<WorldSlicedSpawningInfluence>()
val searchRange = RANGE + ceil(sqrt(((area.length pow 2) + (area.width pow 2)).toDouble())).toInt()
val grottoBlockPositions = world.poiManager.findAll({ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.GROTTO_BLOCK_KEY) },{ true },area.getCenter().toBlockPos(),searchRange, PoiManager.Occupancy.ANY).toList()
for (grottoBlockPos in grottoBlockPositions) {
val blockState = world.getBlockState(grottoBlockPos)
if (blockState.block !is GrottoBlock) continue
val blockEntity = world.getBlockEntity(grottoBlockPos) as? GrottoBlockEntity ?: continue
val structureId = blockEntity.detectedStructure ?: continue
if (structureId == null) {
println("[GrottoBlockProspector] Grotto at $grottoBlockPos has no detected structure.")
continue
}
val habitat = Habitats.getHabitat(structureId)
if (habitat == null) {
println("[GrottoBlockProspector] Grotto at $grottoBlockPos references unknown habitat: $structureId")
continue
}
println("[GrottoBlockProspector] Found Grotto at $grottoBlockPos")
println(" ↳ Structure: $structureId")
if (habitat.spawnPool.isEmpty()) {
println(" ↳ Spawn pool is empty.")
} else {
println(" ↳ Spawn pool:")
habitat.spawnPool.forEach {
println(" • $it")
}
}
val allowedSpawns = blockEntity.currentSpawnGroup
if (allowedSpawns.isNotEmpty()) {
listOfInfluences.add(
WorldSlicedSpatialSpawningInfluence(
grottoBlockPos,
RANGE.toFloat(),
influence = GrottoInfluence(structureId, allowedSpawns)
)
)
}
}
return listOfInfluences
}*/
override fun prospectBlock(world: ServerLevel, pos: BlockPos, blockState: BlockState): WorldSlicedSpatialSpawningInfluence? { return null }
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.prospecting
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.state.BlockState
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
/**
* Prospects for [WorldSlicedSpawningInfluence] in a world at a given position. Occurs as part of a
* [SpawningProspector] scanning a part of the world while formulating a [WorldSlice].
*
* @author Hiroku
* @since March 9th, 2025
*/
interface SpawningInfluenceProspector {
companion object {
@JvmStatic
val prospectors = mutableSetOf<SpawningInfluenceProspector>(
// todo commenting out the GrottoBlockProspector since I don't know if we want to use it at all anyways
//GrottoBlockProspector
)
}
fun prospect(spawner: Spawner, area: SpawningArea) : MutableList<WorldSlicedSpawningInfluence>
fun prospectBlock(world: ServerLevel, pos: BlockPos, blockState: BlockState): WorldSlicedSpawningInfluence?
}
@@ -0,0 +1,225 @@
package com.cobblemon.mod.common.api.spawning.spawner
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.habitats.Habitats
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.SpawnerManager
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.GrottoSpawnCause
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
import com.cobblemon.mod.common.block.entity.GrottoBlockEntity
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.server
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.ai.village.poi.PoiManager
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.max
import kotlin.math.min
class GrottoSpawner(
name: String,
fallbackSpawns: SpawnPool,
manager: SpawnerManager
) : AreaSpawner(name, fallbackSpawns, manager) {
companion object {
private val cache = ConcurrentHashMap<BlockPos, Pair<SpawnPool, Long>>()
private const val CACHE_DURATION_MS = 10_000
}
override var ticksBetweenSpawns: Float = 120F
// todo use this getArea to try to use a new GrottoSpawnCause and use that to send the structure center so spawns are around the structures
override fun getArea(cause: SpawnCause): SpawningArea? {
if (cause is GrottoSpawnCause) {
val center = cause.structureCenter
return SpawningArea(
cause = cause,
world = server()?.getLevel(cause.entityWorldId) ?: return null,
baseX = center.x - 8,
baseY = center.y - 4,
baseZ = center.z - 8,
length = 16,
height = 8,
width = 16
)
}
return null
}
override fun run(cause: SpawnCause): Pair<SpawningContext, SpawnDetail>? {
//println("[GrottoSpawner] run() starting for cause: $cause")
val area = getArea(cause)
//println("[GrottoSpawner] getArea() result: $area")
val constrainedArea = if (area != null) constrainArea(area) else null
//println("[GrottoSpawner] constrainArea() result: $constrainedArea")
if (constrainedArea != null) {
val slice = prospector.prospect(this, constrainedArea)
val contexts = resolver.resolve(this, contextCalculators, slice)
return getSpawningSelector().select(this, contexts)
}
//println("[GrottoSpawner] Area is null or invalid, skipping spawn")
return null
}
// todo test tick
override fun tick() {
super.tick()
ticksUntilNextSpawn -= tickTimerMultiplier
if (ticksUntilNextSpawn > 0) return
val server = server() ?: return
val world = server.overworld() ?: return
for (player in world.players()) {
val searchOrigin = player.blockPosition()
val poiManager = world.poiManager
val grottoPOIs = poiManager.findAll(
{ it.`is`(CobblemonPoiTypes.GROTTO_BLOCK_KEY) },
{ true },
searchOrigin,
128, // Search radius
PoiManager.Occupancy.ANY
)
for (grottoPos in grottoPOIs) {
val blockEntity = world.getBlockEntity(grottoPos) as? GrottoBlockEntity ?: continue
val structureCenter = blockEntity.structureCenter ?: continue
val cause = GrottoSpawnCause(this, chooseBucket(), player, structureCenter, grottoPos)
val result = run(cause)
//println("[GrottoSpawner] Grotto at $grottoPos spawn result: $result")
if (result != null) {
val (context, detail) = result
val action = detail.doSpawn(context)
//println("[GrottoSpawner] doSpawn: $detail -> $action")
val completion = action.complete()
//println("[GrottoSpawner] spawn complete: $completion")
}
}
}
ticksUntilNextSpawn = ticksBetweenSpawns
}
// todo we want to make it so that it ignores conditions of the area and just use the SpawnPool of the JSON registry that has been cycled during that moon cycle
override fun getSpawningSelector(): SpawningSelector {
return object : SpawningSelector {
override fun select(spawner: Spawner, contexts: List<SpawningContext>): Pair<SpawningContext, SpawnDetail>? {
val pool = when {
spawner is GrottoSpawner && contexts.firstOrNull()?.cause is GrottoSpawnCause ->
spawner.getSpawnPool(contexts.first().cause)
else -> spawner.getSpawnPool()
}
for (ctx in contexts) {
val details = pool.details.filterIsInstance<PokemonSpawnDetail>()
if (details.isEmpty()) continue
val chosen = weightedRandom(details) { it.weight.takeIf { w -> w > 0 } ?: 1.0f }
//println("[GrottoSpawner] Selected ${chosen.pokemon.species} for spawn")
return ctx to chosen
}
//println("[GrottoSpawner] No spawnable Pokémon found.")
return null
}
override fun getTotalWeights(spawner: Spawner, contexts: List<SpawningContext>): Map<SpawnDetail, Float> {
return spawner.getSpawnPool().details.associateWith { it.weight.takeIf { w -> w > 0 } ?: 1.0f }
}
private fun <T> weightedRandom(entries: List<T>, weightFn: (T) -> Float): T {
val totalWeight = entries.sumOf { weightFn(it).toDouble() }
var roll = Math.random() * totalWeight
for (entry in entries) {
roll -= weightFn(entry)
if (roll <= 0) return entry
}
return entries.last()
}
}
}
override fun getSpawnPool(): SpawnPool {
return spawns
}
// todo new getSpawnPool
fun getSpawnPool(cause: SpawnCause): SpawnPool {
val server = server() ?: return spawns
val world = server.overworld() ?: return spawns
val now = System.currentTimeMillis()
if (cause !is GrottoSpawnCause) return spawns
val center = cause.grottoPos
val targetEntity = world.getChunk(center)?.getBlockEntity(center) as? GrottoBlockEntity ?: return spawns
val structure = targetEntity.detectedStructure ?: return spawns
val spawnGroup = targetEntity.currentSpawnGroup
if (spawnGroup.isEmpty()) return spawns
val cached = cache[center]
if (cached != null && now - cached.second <= CACHE_DURATION_MS) {
println("[GrottoSpawner] Cache hit at $center — using group: $spawnGroup")
return cached.first
}
val habitat = Habitats.getHabitat(structure)
if (habitat == null) {
println("[GrottoSpawner] No habitat found for structure: $structure")
return spawns
}
val selectedGroup = habitat.spawnPool.find { group ->
group.spawns.toSet() == spawnGroup.toSet()
} ?: return spawns.also {
/*println("[GrottoSpawner] Could not match spawn group at $center to any group in habitat: $structure")
println(" ↳ Current spawnGroup: $spawnGroup")
println(" ↳ Habitat groups:")
habitat.spawnPool.forEachIndexed { i, g ->
println(" [$i]: ${g.spawns}")
}*/
}
val poolCacheName = "grotto_pool_${center.x}_${center.y}_${center.z}"
val pool = SpawnPool(poolCacheName)
for (entry in selectedGroup.spawns) {
val player = cause.entity as? ServerPlayer
val party = player?.party()?.filterNotNull()
val maxLevel = party?.maxOfOrNull { it.level } ?: 20
val variation = 5
val minLevel = max(1, maxLevel - variation)
val maxLevelFinal = min(100, maxLevel + variation)
val level = (minLevel..maxLevelFinal).random()
val detail = PokemonSpawnDetail().apply {
pokemon.species = entry.species.toString()
weight = entry.weight.toFloat()
pokemon.level = level
}
pool.details.add(detail)
}
/*println("[GrottoSpawner] Constructing pool for Grotto at ${cause.structureCenter}")
println(" ↳ Detected structure: $structure")
println(" ↳ Spawn group: $spawnGroup")
println(" ↳ Selected group: $selectedGroup")
println(" ↳ Pool size: ${pool.details.size}")*/
cache[center] = pool to now
return pool
}
}
@@ -142,7 +142,7 @@ class ReforgedConversion(val base: Path) : CobblemonConverter<CompoundTag> {
}
// TODO - Nicknames and Original Trainer Data
// result.nickname = this.find(nbt, "Nickname", NbtCompound::getString)
// result.nickname = this.find(nbt, "Nickname", CompoundTag::getString)
val ball = this.find(nbt, "CaughtBall", CompoundTag::getString)
result.caughtBall = if(ball != null) PokeBalls.getPokeBall(ResourceLocation.parse(ball)) ?: PokeBalls.POKE_BALL else PokeBalls.POKE_BALL
@@ -41,6 +41,40 @@ object CobblemonBiomeTags {
@JvmField
val HAS_REVIVAL_HERBS = create("has_feature/revival_herbs")
// Nests
@JvmField
val HAS_STICK_NEST = create("has_nest/nest_stick")
@JvmField
val HAS_STICK_NEST_TREES = create("has_nest/nest_stick_trees")
@JvmField
val HAS_LEAF_NEST_TREES = create("has_nest/nest_leaf_trees")
@JvmField
val HAS_SNOW_NEST = create("has_nest/nest_snow")
@JvmField
val HAS_SAND_NEST = create("has_nest/nest_sand")
@JvmField
val HAS_RED_SAND_NEST = create("has_nest/nest_red_sand")
@JvmField
val HAS_SEAGRASS_NEST = create("has_nest/nest_seagrass")
@JvmField
val HAS_WARM_OCEAN_NEST = create("has_nest/nest_warm_ocean")
@JvmField
val HAS_STONE_NEST = create("has_nest/nest_stone")
@JvmField
val HAS_STONE_NEST_CAVES = create("has_nest/nest_stone_caves")
@JvmField
val HAS_DEEPSLATE_NEST_CAVES = create("has_nest/nest_deepslate_caves")
@JvmField
val HAS_LUSH_NEST_CAVES = create("has_nest/nest_lush_caves")
@JvmField
val HAS_BASALT_NEST = create("has_nest/nest_basalt")
@JvmField
val HAS_NETHER_NEST = create("has_nest/nest_nether")
@JvmField
val HAS_SOUL_SAND_NEST = create("has_nest/nest_soul_sand")
@JvmField
val HAS_CHORUS_NEST = create("has_nest/nest_chorus")
// Has Ore tags
@JvmField
val HAS_DAWN_STONE_ORE = create("has_ore/ore_dawn_stone_normal")
@@ -103,6 +103,7 @@ object CobblemonItemTags {
@JvmField val ZINC_INGREDIENTS = create("zinc_ingredients")
// Held Item Tags
@JvmField val DEFAULTING_POKE_BALLS = create("defaulting_poke_balls")
@JvmField val CONSUMED_IN_NPC_BATTLE = create("held/consumed_in_npc_battle")
@JvmField val CONSUMED_IN_PVP_BATTLE = create("held/consumed_in_pvp_battle")
@JvmField val CONSUMED_IN_WILD_BATTLE = create("held/consumed_in_wild_battle")
@@ -14,9 +14,11 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.battles.actor.MultiPokemonBattleActor
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -297,6 +299,99 @@ object BattleBuilder {
}
}
@JvmOverloads
fun pveDoubles(
player: ServerPlayer,
pokemonEntity1: PokemonEntity,
pokemonEntity2: PokemonEntity,
leadingPokemon: UUID? = null,
battleFormat: BattleFormat = BattleFormat.GEN_9_DOUBLES,
cloneParties: Boolean = false,
healFirst: Boolean = false,
fleeDistance: Float = Cobblemon.config.defaultFleeDistance,
party: PartyStore = player.party()
): BattleStartResult {
// Prepare the player's team
val playerTeam = party.toBattleTeam(
clone = cloneParties,
healPokemon = healFirst,
leadingPokemon = leadingPokemon
).sortedBy { it.health <= 0 }
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
// Combine both wild Pokémon into a single BattleActor
val wildTeam = listOf(
BattlePokemon(pokemonEntity1.pokemon),
BattlePokemon(pokemonEntity2.pokemon)
)
val wildActor = MultiPokemonBattleActor(pokemonList = wildTeam, fleeDistance = Cobblemon.config.defaultFleeDistance)
val errors = ErroredBattleStart()
// Validate player's team
if (playerTeam.isNotEmpty() && playerTeam[0].health <= 0) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.pokemonList.size
)
}
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.pokemonList.size
)
}
// Validate opponent Pokémon entities
if (pokemonEntity1.battleId != null) {
errors.participantErrors[wildActor] += BattleStartError.alreadyInBattle(wildActor)
}
if (pokemonEntity2.battleId != null) {
errors.participantErrors[wildActor] += BattleStartError.alreadyInBattle(wildActor)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
// Set battle themes
playerActor.battleTheme = pokemonEntity1.getBattleTheme()
return if (errors.isEmpty) {
println("Player actor: ${playerActor.pokemonList.size} Pokémon")
println("WildActor Pokémon: ${wildActor.pokemonList.map { it.effectedPokemon.species.translatedName }}")
val side1 = BattleSide(playerActor)
val side2 = BattleSide(wildActor)
println("Side1 actors: ${side1.actors.map { it.uuid }}")
println("Side2 actors: ${side2.actors.map { it.uuid }}")
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = side1,
side2 = side2
).ifSuccessful { battle ->
println("Battle started with ID: ${battle.battleId}")
if (!cloneParties) {
pokemonEntity1.battleId = battle.battleId
pokemonEntity2.battleId = battle.battleId
}
}.ifErrored {
println("Battle start failed: ${it.errors}")
}
} else {
println("Battle failed due to validation errors: ${errors.errors}")
errors
}
}
/**
* Attempts to create a PvE battle against the given Pokémon.
*
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.battles.actor
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.ai.RandomBattleAI
@@ -17,13 +18,46 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.phys.Vec3
class MultiPokemonBattleActor(
pokemonList: List<BattlePokemon>,
artificialDecider: BattleAI = RandomBattleAI(),
uuid: UUID = UUID.randomUUID()
) : AIBattleActor(uuid, pokemonList, artificialDecider) {
pokemonList: List<BattlePokemon>,
override val fleeDistance: Float,
artificialDecider: BattleAI = RandomBattleAI(),
uuid: UUID = UUID.randomUUID()
) : AIBattleActor(uuid, pokemonList, artificialDecider), FleeableBattleActor {
override fun getName(): MutableComponent = "Wild Pokémon".text() // TODO probably remove by making it nullable
override fun nameOwned(name: String): MutableComponent = Component.literal(name)
override val type = ActorType.WILD
override fun getWorldAndPosition(): Pair<ServerLevel, Vec3>? {
// Ensure there are Pokémon in the list
if (pokemonList.isEmpty()) return null
// Gather all positions and ensure they share the same world
val positionsAndWorlds = pokemonList.mapNotNull { battlePokemon ->
val entity = battlePokemon.effectedPokemon.entity
val level = entity?.level() as? ServerLevel
val position = entity?.position()
if (level != null && position != null) {
level to position
} else {
null
}
}
if (positionsAndWorlds.isEmpty()) return null
// Ensure all Pokémon are in the same world
val world = positionsAndWorlds.first().first
if (positionsAndWorlds.any { it.first != world }) return null
// Compute the average position
val averagePosition = positionsAndWorlds
.map { it.second }
.reduce { acc, pos -> acc.add(pos) }
.scale(1.0 / positionsAndWorlds.size)
return world to averagePosition
}
}
@@ -0,0 +1,120 @@
/*
* 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.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.block.HealingMachineBlock.Companion
import com.cobblemon.mod.common.block.HealingMachineBlock.Companion.CHARGE_LEVEL
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.block.entity.EggBlockEntity
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.item.PokemonEggItem
import com.cobblemon.mod.common.item.components.EggComponent
import com.mojang.serialization.Dynamic
import com.mojang.serialization.MapCodec
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.component.DataComponents
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class EggBlock(properties: Properties) : BaseEntityBlock(properties) {
override fun setPlacedBy(
level: Level,
blockPos: BlockPos,
blockState: BlockState,
placer: LivingEntity?,
itemStack: ItemStack
) {
val entity = level.getBlockEntity(blockPos) as? EggBlockEntity
// Retrieve the EGG_COMPONENT from the item stack and set it in the block entity
val eggComponent = itemStack.get(CobblemonItemComponents.EGG_DATA)
eggComponent?.let { component ->
entity?.setEggData(component.egg) // Pass the Egg data to the block entity
println("Set egg in EggBlockEntity: ${component.egg?.species}")
}
super.setPlacedBy(level, blockPos, blockState, placer, itemStack)
}
override fun getShape(
state: BlockState, world: BlockGetter, pos: BlockPos, context: net.minecraft.world.phys.shapes.CollisionContext
): VoxelShape {
return Shapes.or(
Shapes.box(5.0 / 16.0, 0.0, 5.0 / 16.0, 11.0 / 16.0, 2.0 / 16.0, 11.0 / 16.0), // Bottom section
Shapes.box(4.5 / 16.0, 1.25 / 16.0, 4.5 / 16.0, 11.5 / 16.0, 5.25 / 16.0, 11.5 / 16.0), // Middle section
Shapes.box(5.0 / 16.0, 5.25 / 16.0, 5.0 / 16.0, 11.0 / 16.0, 7.25 / 16.0, 11.0 / 16.0), // Upper section
Shapes.box(6.0 / 16.0, 7.25 / 16.0, 6.0 / 16.0, 10.0 / 16.0, 8.25 / 16.0, 10.0 / 16.0) // Top small section
)
}
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
// Ensure this logic only runs on the server
if (level.isClientSide) {
return InteractionResult.SUCCESS // Client-side only needs to show the hand animation
}
val serverPlayer = player as? ServerPlayer ?: return InteractionResult.FAIL // Cast safely to ServerPlayer
val entity = level.getBlockEntity(blockPos) as? EggBlockEntity ?: return InteractionResult.FAIL
return entity.onUse(blockState, level, blockPos, serverPlayer, InteractionHand.MAIN_HAND, blockHitResult)
}
override fun playerWillDestroy(level: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
val entity = level.getBlockEntity(pos) as? EggBlockEntity
if (entity != null && !player.isCreative) {
entity.dropEgg(state, level, pos)
}
return super.playerWillDestroy(level, pos, state, player)
}
override fun codec(): MapCodec<out BaseEntityBlock> {
TODO("Not yet implemented")
}
override fun getRenderShape(blockState: BlockState): RenderShape {
return RenderShape.ENTITYBLOCK_ANIMATED
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = EggBlockEntity(pos, state)
}
@@ -32,7 +32,8 @@ import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
class FossilAnalyzerBlock(settings: Properties) : MultiblockBlock(settings), WorldlyContainerHolder {
class FossilAnalyzerBlock(settings: Properties) : MultiblockBlock(settings),
WorldlyContainerHolder {
init {
registerDefaultState(stateDefinition.any()
.setValue(HorizontalDirectionalBlock.FACING, Direction.NORTH)
@@ -0,0 +1,96 @@
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.block.entity.GrottoBlockEntity
import net.minecraft.core.BlockPos
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.storage.loot.LootContext
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import java.util.*
class GrottoBlock(properties: Properties) : Block(properties), EntityBlock {
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return GrottoBlockEntity(pos, state)
}
override fun <T : BlockEntity> getTicker(
level: Level,
state: BlockState,
type: BlockEntityType<T>
): BlockEntityTicker<T>? {
return if (!level.isClientSide && type == CobblemonBlockEntities.GROTTO_BLOCK) {
@Suppress("UNCHECKED_CAST")
GrottoBlockEntity.TICKER as BlockEntityTicker<T>
} else {
null
}
}
override fun useWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hitResult: BlockHitResult): InteractionResult {
if (!level.isClientSide) {
val be = level.getBlockEntity(pos) as? GrottoBlockEntity ?: return InteractionResult.PASS
return be.onUse(player, hitResult)
}
return InteractionResult.SUCCESS
}
override fun getDrops(state: BlockState, params: LootParams.Builder): MutableList<ItemStack> {
val blockEntity = params.getOptionalParameter(LootContextParams.BLOCK_ENTITY) as? GrottoBlockEntity
val player = params.getOptionalParameter(LootContextParams.THIS_ENTITY) as? Player
if (player?.isCreative == true) return mutableListOf()
val mimic = blockEntity?.mimickedState?.block ?: return mutableListOf()
return mutableListOf(ItemStack(mimic))
}
override fun getRenderShape(state: BlockState): RenderShape {
return RenderShape.INVISIBLE
}
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return box(0.0, 0.0, 0.0, 16.0, 16.0, 16.0)
}
override fun getCollisionShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape {
return box(0.0, 0.0, 0.0, 16.0, 16.0, 16.0)
}
override fun getVisualShape(
state: BlockState,
world: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape {
return box(0.0, 0.0, 0.0, 16.0, 16.0, 16.0)
}
override fun getOcclusionShape(state: BlockState, level: BlockGetter, pos: BlockPos): VoxelShape? {
return Shapes.block()
}
override fun useShapeForLightOcclusion(state: BlockState): Boolean {
return true
}
}
@@ -0,0 +1,183 @@
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.block.entity.EggBlockEntity
import com.cobblemon.mod.common.block.entity.IncubatorBlockEntity
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.AirItem
import net.minecraft.world.item.EggItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.PickaxeItem
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class IncubatorBlock(settings: Properties) : BaseEntityBlock(settings) {
companion object {
val FACING: DirectionProperty = DirectionProperty.create("facing", { dir -> dir.axis.isHorizontal })
val HAS_EGG: BooleanProperty = BooleanProperty.create("has_egg")
val CODEC: MapCodec<IncubatorBlock> = simpleCodec(::IncubatorBlock) as MapCodec<IncubatorBlock>
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, net.minecraft.core.Direction.NORTH)
.setValue(HAS_EGG, false)
)
}
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: net.minecraft.world.phys.shapes.CollisionContext): VoxelShape {
return Shapes.or(
// Base
Shapes.box(2.0 / 16.0, 0.0, 2.0 / 16.0, 14.0 / 16.0, 3.0 / 16.0, 14.0 / 16.0),
// Top
Shapes.box(2.0 / 16.0, 14.0 / 16.0, 2.0 / 16.0, 14.0 / 16.0, 1.0, 14.0 / 16.0),
// Unified Glass Section (Covers all four glass sides as one box)
Shapes.box(2.5 / 16.0, 3.0 / 16.0, 2.5 / 16.0, 13.5 / 16.0, 14.0 / 16.0, 13.5 / 16.0)
)
}
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
return defaultBlockState().setValue(FACING, ctx.horizontalDirection.opposite)
}
override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
stack: ItemStack
) {
val blockEntity = level.getBlockEntity(pos) as? IncubatorBlockEntity
if (placer is Player) {
blockEntity?.ownerUUID = placer.uuid
}
super.setPlacedBy(level, pos, state, placer, stack)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, HAS_EGG)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): IncubatorBlockEntity {
return IncubatorBlockEntity(pos, state)
}
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
// Ensure this logic only runs on the server
if (level.isClientSide) {
return InteractionResult.SUCCESS // Client-side only needs to show the hand animation
}
val serverPlayer = player as? ServerPlayer ?: return InteractionResult.FAIL // Cast safely to ServerPlayer
val entity = level.getBlockEntity(blockPos) as? IncubatorBlockEntity ?: return InteractionResult.FAIL
val egg = entity.getItem(0).get(CobblemonItemComponents.EGG_DATA)?.egg
// if the player interacting with the nest is not the owner of the nest we do not want them taking the egg or hatching
if (egg != null && entity.ownerUUID != player.uuid && entity.ownerUUID != null){
return InteractionResult.FAIL
}
// if the parent owner ID is null then something is wrong (maybe something happened to one of the parent datas)
if (egg != null && egg?.parentOne?.getOwnerUUID() == null || egg?.parentTwo?.getOwnerUUID() == null) {
return entity.onUse(blockState, level, blockPos, serverPlayer, InteractionHand.MAIN_HAND, blockHitResult)
}
// if the person interacting is not the owner of one of the parents
if (egg != null && player.uuid != egg?.parentOne?.getOwnerUUID() || player.uuid != egg?.parentTwo?.getOwnerUUID()) {
return InteractionResult.FAIL
}
return entity.onUse(blockState, level, blockPos, serverPlayer, InteractionHand.MAIN_HAND, blockHitResult)
}
override fun hasAnalogOutputSignal(state: BlockState): Boolean {
return true
}
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
val blockEntity = level.getBlockEntity(pos) as? IncubatorBlockEntity
return blockEntity?.getComparatorOutput() ?: 0
}
override fun getDestroyProgress(state: BlockState, player: Player, level: BlockGetter, pos: BlockPos): Float {
// Retrieve the block entity at the given position
val blockEntity = level.getBlockEntity(pos) as? IncubatorBlockEntity ?: return super.getDestroyProgress(state, player, level, pos)
// Check if the player is the owner of the nest
val isOwner = blockEntity.ownerUUID == player.uuid
// Check if there's an egg present in the nest
val hasEgg = !blockEntity.getItem(0).isEmpty
return if (!isOwner && hasEgg) {
// Reduce destroy speed by 80%, making it 5 times harder to break
super.getDestroyProgress(state, player, level, pos) * 0.2f
} else {
// Use the default destroy speed
super.getDestroyProgress(state, player, level, pos)
}
}
override fun playerWillDestroy(level: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
val entity = level.getBlockEntity(pos) as? IncubatorBlockEntity
val itemStack = player.mainHandItem
entity?.let {
if (!player.isCreative) {
it.dropItem(level, pos, state)
if (itemStack.item is PickaxeItem) {
val itemEntity = ItemEntity(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, CobblemonItems.INCUBATOR.defaultInstance)
level.addFreshEntity(itemEntity)
}
}
}
return super.playerWillDestroy(level, pos, state, player)
}
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
override fun codec(): MapCodec<out BaseEntityBlock> {
return CODEC
}
override fun <T : BlockEntity?> getTicker(level: Level, blockState: BlockState, blockEntityType: BlockEntityType<T>): BlockEntityTicker<T>? {
return createTickerHelper(blockEntityType, CobblemonBlockEntities.INCUBATOR, IncubatorBlockEntity.TICKER)
}
}
@@ -0,0 +1,397 @@
/*
* 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.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.block.entity.IncubatorBlockEntity
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.entity.player.Player
import net.minecraft.world.InteractionHand
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.PickaxeItem
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.material.Fluid
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class NestBlock(val variant: NestVariant, properties: Properties) : BaseEntityBlock(properties), SimpleWaterloggedBlock {
init {
registerDefaultState(
stateDefinition.any()
.setValue(BlockStateProperties.WATERLOGGED, false)
)
}
enum class NestVariant(val id: String) : StringRepresentable {
STICK("nest_stick"),
LEAF("nest_leaf"),
STONE("nest_stone"),
DEEPSLATE("nest_deepslate"),
BASALT("nest_basalt"),
SNOW("nest_snow"),
NETHER("nest_nether"),
SOUL_SAND("nest_soul_sand"),
WARM_OCEAN("nest_warm_ocean"),
SEAGRASS("nest_seagrass"),
LUSH("nest_lush"),
HAY("nest_hay"),
SAND("nest_sand"),
RED_SAND("nest_red_sand"),
CHORUS("nest_chorus"),
CAVE("cave_nest"),
//NETHER("nether_nest"),
WATER("water_nest"),
BASIC("basic_nest");
override fun getSerializedName() = id
}
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: net.minecraft.world.phys.shapes.CollisionContext): VoxelShape {
return when (variant) {
NestVariant.STICK -> Shapes.or(
Shapes.box(3.0 / 16.0, 0.2 / 16.0, 3.0 / 16.0, 13.0 / 16.0, 4.2 / 16.0, 13.0 / 16.0), // Base
Shapes.box(2.8 / 16.0, 0.0 / 16.0, 2.8 / 16.0, 13.2 / 16.0, 4.4 / 16.0, 13.2 / 16.0), // Hat layer
Shapes.box(12.0 / 16.0, 3.0 / 16.0, 11.5 / 16.0, 12.0 / 16.0, 6.0 / 16.0, 15.5 / 16.0), // Stick 1
Shapes.box(13.0 / 16.0, 1.0 / 16.0, 12.0 / 16.0, 15.0 / 16.0, 3.0 / 16.0, 12.0 / 16.0), // Stick 2
Shapes.box(1.5 / 16.0, 2.0 / 16.0, 12.0 / 16.0, 4.5 / 16.0, 6.0 / 16.0, 12.0 / 16.0), // Stick 3
Shapes.box(1.0 / 16.0, 1.0 / 16.0, 4.0 / 16.0, 4.0 / 16.0, 1.0 / 16.0, 9.0 / 16.0), // Stick 4
Shapes.box(4.0 / 16.0, 2.0 / 16.0, 13.0 / 16.0, 11.0 / 16.0, 2.0 / 16.0, 15.0 / 16.0), // Stick 5
Shapes.box(9.0 / 16.0, 3.0 / 16.0, 1.0 / 16.0, 15.0 / 16.0, 3.0 / 16.0, 6.0 / 16.0), // Stick 6
Shapes.box(6.0 / 16.0, 3.0 / 16.0, 0.5 / 16.0, 6.0 / 16.0, 6.0 / 16.0, 4.5 / 16.0) // Stick 7
)
NestVariant.LEAF -> Shapes.or(
Shapes.box(3.0 / 16.0, 0.2 / 16.0, 3.0 / 16.0, 13.0 / 16.0, 4.2 / 16.0, 13.0 / 16.0), // Base of the nest
Shapes.box(2.8 / 16.0, 0.0, 2.8 / 16.0, 13.2 / 16.0, 4.4 / 16.0, 13.2 / 16.0), // Hat layer
Shapes.box(11.975 / 16.0, 6.0 / 16.0, 12.0 / 16.0, 11.975 / 16.0, 10.0 / 16.0, 15.0 / 16.0), // Leaf 1
Shapes.box(12.025 / 16.0, 5.525 / 16.0, 6.0 / 16.0, 16.025 / 16.0, 5.525 / 16.0, 9.0 / 16.0), // Leaf 2
Shapes.box(12.025 / 16.0, 8.0 / 16.0, 4.0 / 16.0, 12.025 / 16.0, 12.0 / 16.0, 7.0 / 16.0), // Leaf 3
Shapes.box(0.0 / 16.0, 5.0 / 16.0, 11.975 / 16.0, 3.0 / 16.0, 9.0 / 16.0, 11.975 / 16.0), // Leaf 4
Shapes.box(5.25 / 16.0, 7.75 / 16.0, 12.025 / 16.0, 9.25 / 16.0, 10.75 / 16.0, 12.025 / 16.0), // Leaf 5
Shapes.box(6.025 / 16.0, 10.0 / 16.0, 2.25 / 16.0, 6.025 / 16.0, 14.0 / 16.0, 5.25 / 16.0), // Leaf 6
Shapes.box(0.525 / 16.0, 8.0 / 16.0, 6.5 / 16.0, 3.525 / 16.0, 12.0 / 16.0, 6.5 / 16.0), // Leaf 7
Shapes.box(13.975 / 16.0, 3.0 / 16.0, 4.0 / 16.0, 15.975 / 16.0, 3.0 / 16.0, 5.0 / 16.0) // Leaf 8
)
NestVariant.STONE -> Shapes.or(
Shapes.box(3.0 / 16.0, 0.05 / 16.0, 3.0 / 16.0, 13.0 / 16.0, 3.05 / 16.0, 13.0 / 16.0),
Shapes.box(10.0 / 16.0, 0.0 / 16.0, 1.0 / 16.0, 14.0 / 16.0, 4.0 / 16.0, 5.0 / 16.0),
Shapes.box(11.0 / 16.0, 0.0 / 16.0, 5.0 / 16.0, 14.0 / 16.0, 4.0 / 16.0, 9.0 / 16.0),
Shapes.box(1.0 / 16.0, -0.1 / 16.0, 7.0 / 16.0, 5.0 / 16.0, 4.9 / 16.0, 15.0 / 16.0),
Shapes.box(5.0 / 16.0, -0.1 / 16.0, 11.0 / 16.0, 8.0 / 16.0, 4.9 / 16.0, 15.0 / 16.0)
)
NestVariant.DEEPSLATE -> Shapes.or(
Shapes.box(3.0 / 16.0, 0.05 / 16.0, 3.0 / 16.0, 13.0 / 16.0, 3.05 / 16.0, 13.0 / 16.0),
Shapes.box(2.0 / 16.0, 0.0 / 16.0, 2.0 / 16.0, 6.0 / 16.0, 4.0 / 16.0, 5.0 / 16.0),
Shapes.box(1.0 / 16.0, -0.05 / 16.0, 5.0 / 16.0, 5.0 / 16.0, 5.95 / 16.0, 9.0 / 16.0),
Shapes.box(11.0 / 16.0, -0.1 / 16.0, 7.0 / 16.0, 15.0 / 16.0, 3.9 / 16.0, 15.0 / 16.0),
Shapes.box(10.0 / 16.0, -0.1 / 16.0, 14.0 / 16.0, 11.0 / 16.0, 3.9 / 16.0, 15.0 / 16.0),
Shapes.box(8.0 / 16.0, -0.125 / 16.0, 11.0 / 16.0, 11.0 / 16.0, 6.875 / 16.0, 14.0 / 16.0),
Shapes.box(8.0 / 16.0, 0.0 / 16.0, 1.0 / 16.0, 14.0 / 16.0, 2.0 / 16.0, 5.0 / 16.0)
)
NestVariant.BASALT -> Shapes.or(
Shapes.box(4.0 / 16.0, -0.1 / 16.0, 4.0 / 16.0, 12.0 / 16.0, 1.9 / 16.0, 12.0 / 16.0),
Shapes.box(10.55 / 16.0, 0.0 / 16.0, 5.95 / 16.0, 12.55 / 16.0, 5.0 / 16.0, 10.95 / 16.0),
Shapes.box(2.55 / 16.0, 0.0 / 16.0, 10.95 / 16.0, 12.55 / 16.0, 5.0 / 16.0, 12.95 / 16.0),
Shapes.box(2.55 / 16.0, 0.0 / 16.0, 9.95 / 16.0, 4.55 / 16.0, 5.0 / 16.0, 10.95 / 16.0),
Shapes.box(9.55 / 16.0, -0.2 / 16.0, 9.95 / 16.0, 13.55 / 16.0, 8.8 / 16.0, 13.95 / 16.0),
Shapes.box(9.55 / 16.0, -0.2 / 16.0, 0.95 / 16.0, 13.55 / 16.0, 3.8 / 16.0, 4.95 / 16.0),
Shapes.box(0.55 / 16.0, -0.2 / 16.0, 5.95 / 16.0, 4.55 / 16.0, 5.8 / 16.0, 9.95 / 16.0),
Shapes.box(1.55 / 16.0, -0.2 / 16.0, 2.95 / 16.0, 4.55 / 16.0, 8.8 / 16.0, 5.95 / 16.0),
Shapes.box(3.45 / 16.0, -0.2 / 16.0, 11.05 / 16.0, 6.45 / 16.0, 11.8 / 16.0, 14.05 / 16.0),
Shapes.box(11.55 / 16.0, -0.1 / 16.0, 4.95 / 16.0, 14.55 / 16.0, 5.9 / 16.0, 7.95 / 16.0)
)
NestVariant.SNOW -> Shapes.or(
Shapes.box(2.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0, 12.0 / 16.0, 4.0 / 16.0, 11.0 / 16.0),
Shapes.box(3.0 / 16.0, 1.1 / 16.0, 3.0 / 16.0, 13.0 / 16.0, 6.1 / 16.0, 13.0 / 16.0),
Shapes.box(5.0 / 16.0, 1.0 / 16.0, 5.0 / 16.0, 15.0 / 16.0, 5.0 / 16.0, 15.0 / 16.0),
Shapes.box(1.0 / 16.0, 1.0 / 16.0, 8.0 / 16.0, 5.0 / 16.0, 8.0 / 16.0, 14.0 / 16.0),
Shapes.box(5.0 / 16.0, 5.0 / 16.0, 11.0 / 16.0, 7.0 / 16.0, 8.0 / 16.0, 14.0 / 16.0),
Shapes.box(0.25 / 16.0, 7.6 / 16.0, 10.0 / 16.0, 5.25 / 16.0, 14.6 / 16.0, 10.0 / 16.0),
Shapes.box(13.5 / 16.0, 4.6 / 16.0, 6.0 / 16.0, 16.5 / 16.0, 8.6 / 16.0, 6.0 / 16.0),
Shapes.box(11.0 / 16.0, 4.6 / 16.0, 12.75 / 16.0, 11.0 / 16.0, 10.6 / 16.0, 16.75 / 16.0),
Shapes.box(0.0 / 16.0, 0.0 / 16.0, 0.0 / 16.0, 16.0 / 16.0, 2.0 / 16.0, 16.0 / 16.0)
)
NestVariant.CAVE -> Shapes.or(
Shapes.box(0.25, 0.0, 0.25, 0.75, 0.375, 0.75),
Shapes.box(0.625, 0.0, 0.625, 0.875, 0.5, 0.875)
)
NestVariant.NETHER -> Shapes.or(
Shapes.box(2.0 / 16.0, 0.0, 2.0 / 16.0, 14.0 / 16.0, 2.0 / 16.0, 14.0 / 16.0), // Main nest base
Shapes.box(0.0 / 16.0, 0.0, 6.0 / 16.0, 4.0 / 16.0, 3.0 / 16.0, 13.0 / 16.0), // Left extension
Shapes.box(7.0 / 16.0, 0.0, 0.0 / 16.0, 13.0 / 16.0, 3.0 / 16.0, 3.0 / 16.0), // Front extension
Shapes.box(11.0 / 16.0, 0.0, 11.0 / 16.0, 16.0 / 16.0, 4.0 / 16.0, 16.0 / 16.0) // Back right extension
)
NestVariant.WATER -> Shapes.box(0.2, 0.0, 0.2, 0.8, 0.3, 0.8)
NestVariant.BASIC -> Shapes.box(0.1, 0.0, 0.1, 0.9, 0.45, 0.9)
NestVariant.SOUL_SAND -> Shapes.or(
Shapes.box(1.0 / 16.0, 0.0, 1.0 / 16.0, 15.0 / 16.0, 2.0 / 16.0, 15.0 / 16.0), // Base layer
Shapes.box(2.0 / 16.0, 2.0 / 16.0, 2.0 / 16.0, 13.0 / 16.0, 4.0 / 16.0, 13.0 / 16.0), // Inner rim
Shapes.box(12.0 / 16.0, 0.0 / 16.0, 10.0 / 16.0, 16.0 / 16.0, 8.0 / 16.0, 14.0 / 16.0), // Side bump 1
Shapes.box(3.0 / 16.0, 0.0 / 16.0, 13.0 / 16.0, 6.0 / 16.0, 7.0 / 16.0, 16.0 / 16.0), // Side bump 2
Shapes.box(1.0 / 16.0, 2.0 / 16.0, 3.0 / 16.0, 4.0 / 16.0, 6.0 / 16.0, 6.0 / 16.0) // Side bump 3
)
NestVariant.WARM_OCEAN -> Shapes.box(3.0 / 16.0, 0.0, 3.0 / 16.0, 13.0 / 16.0, 3.0 / 16.0, 13.0 / 16.0)
NestVariant.SEAGRASS -> Shapes.or(
Shapes.box(3.0 / 16.0, 0.0, 3.0 / 16.0, 13.0 / 16.0, 3.0 / 16.0, 14.0 / 16.0), // Main base
Shapes.box(1.0 / 16.0, 0.0, 7.0 / 16.0, 5.0 / 16.0, 4.0 / 16.0, 13.0 / 16.0), // Side protrusion 1
Shapes.box(5.0 / 16.0, 0.0, 1.0 / 16.0, 15.0 / 16.0, 2.0 / 16.0, 9.0 / 16.0), // Side protrusion 2
Shapes.box(6.0 / 16.0, 0.0, 13.0 / 16.0, 16.0 / 16.0, 6.0 / 16.0, 14.0 / 16.0) // Backside bump
)
NestVariant.LUSH -> Shapes.or(
Shapes.box(4.0 / 16.0, 0.0, 0.0 / 16.0, 12.0 / 16.0, 2.0 / 16.0, 12.0 / 16.0), // Main base
Shapes.box(0.0 / 16.0, 0.0, 0.0 / 16.0, 4.0 / 16.0, 4.0 / 16.0, 12.0 / 16.0), // Side extension 1
Shapes.box(0.0 / 16.0, 0.0, 12.0 / 16.0, 12.0 / 16.0, 6.0 / 16.0, 16.0 / 16.0), // Side extension 2
Shapes.box(12.0 / 16.0, 0.0, 8.0 / 16.0, 16.0 / 16.0, 4.0 / 16.0, 16.0 / 16.0), // Side extension 3
Shapes.box(12.0 / 16.0, 0.0, 0.0 / 16.0, 16.0 / 16.0, 2.0 / 16.0, 8.0 / 16.0) // Side extension 4
)
NestVariant.HAY -> Shapes.box(3.0 / 16.0, 0.0, 3.0 / 16.0, 13.0 / 16.0, 4.0 / 16.0, 13.0 / 16.0)
NestVariant.SAND -> Shapes.or(
Shapes.box(3.0 / 16.0, 0.0, 3.0 / 16.0, 14.0 / 16.0, 4.0 / 16.0, 14.0 / 16.0), // Main nest base
Shapes.box(13.0 / 16.0, 0.0, 12.0 / 16.0, 16.0 / 16.0, 5.0 / 16.0, 16.0 / 16.0), // Large rock back right
Shapes.box(10.0 / 16.0, 0.0, 12.0 / 16.0, 13.0 / 16.0, 3.0 / 16.0, 16.0 / 16.0), // Medium rock back center
Shapes.box(14.0 / 16.0, 0.0, 9.0 / 16.0, 16.0 / 16.0, 2.0 / 16.0, 12.0 / 16.0), // Small rock right side
Shapes.box(4.0 / 16.0, 0.0, 0.0 / 16.0, 7.0 / 16.0, 3.0 / 16.0, 3.0 / 16.0) // Small rock front left
)
NestVariant.RED_SAND -> Shapes.or(
Shapes.box(3.0 / 16.0, 0.0, 3.0 / 16.0, 14.0 / 16.0, 4.0 / 16.0, 14.0 / 16.0), // Main nest base
Shapes.box(13.0 / 16.0, 0.0, 12.0 / 16.0, 16.0 / 16.0, 5.0 / 16.0, 16.0 / 16.0), // Large rock back right
Shapes.box(10.0 / 16.0, 0.0, 12.0 / 16.0, 13.0 / 16.0, 3.0 / 16.0, 16.0 / 16.0), // Medium rock back center
Shapes.box(14.0 / 16.0, 0.0, 9.0 / 16.0, 16.0 / 16.0, 2.0 / 16.0, 12.0 / 16.0), // Small rock right side
Shapes.box(4.0 / 16.0, 0.0, 0.0 / 16.0, 7.0 / 16.0, 3.0 / 16.0, 3.0 / 16.0) // Small rock front left
)
NestVariant.CHORUS -> Shapes.box(1.0 / 16.0, 0.0, 1.0 / 16.0, 15.0 / 16.0, 4.0 / 16.0, 15.0 / 16.0)
}
}
override fun onPlace(state: BlockState, level: Level, pos: BlockPos, oldState: BlockState, isMoving: Boolean) {
super.onPlace(state, level, pos, oldState, isMoving)
checkAndBreakIfSubmerged(state, level, pos)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(BlockStateProperties.WATERLOGGED)
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, level: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
checkAndBreakIfSubmerged(state, level, pos)
return super.updateShape(state, direction, neighborState, level, pos, neighborPos)
}
override fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
block: Block,
fromPos: BlockPos,
isMoving: Boolean
) {
super.neighborChanged(state, level, pos, block, fromPos, isMoving)
// Check if the block below is air
val blockBelowPos = pos.below()
if (fromPos == blockBelowPos && level.getBlockState(blockBelowPos).isAir) {
level.destroyBlock(pos, false)
}
checkAndBreakIfSubmerged(state, level, pos)
}
private fun checkAndBreakIfSubmerged(state: BlockState, levelAccessor: LevelAccessor, pos: BlockPos) {
val fluidState = levelAccessor.getFluidState(pos)
if ((fluidState.type == Fluids.WATER || fluidState.isSource) && !canBeWaterlogged()) {
if (levelAccessor is Level) {
levelAccessor.destroyBlock(pos, true)
}
}
}
private fun canBeWaterlogged(): Boolean {
return variant == NestVariant.WATER || variant == NestVariant.SEAGRASS || variant == NestVariant.WARM_OCEAN
}
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
// Ensure this logic only runs on the server
if (level.isClientSide) {
return InteractionResult.SUCCESS
}
val serverPlayer = player as? ServerPlayer ?: return InteractionResult.FAIL
val entity = level.getBlockEntity(blockPos) as? NestBlockEntity ?: return InteractionResult.FAIL
val egg = entity.getItem().get(CobblemonItemComponents.EGG_DATA)?.egg
// if the player interacting with the nest is not the owner of the nest we do not want them taking the egg or hatching
if (egg != null && entity.ownerUUID != player.uuid && entity.ownerUUID != null){
return InteractionResult.FAIL
}
// if the parent owner ID is null then something is wrong (maybe something happened to one of the parent datas)
if (egg != null && egg?.parentOne?.getOwnerUUID() == null || egg?.parentTwo?.getOwnerUUID() == null) {
return entity.onUse(blockState, level, blockPos, serverPlayer, InteractionHand.MAIN_HAND, blockHitResult)
}
// if the person interacting is not the owner of one of the parents
if (egg != null && player.uuid != egg?.parentOne?.getOwnerUUID() || player.uuid != egg?.parentTwo?.getOwnerUUID()) {
return InteractionResult.FAIL
}
return entity.onUse(blockState, level, blockPos, serverPlayer, InteractionHand.MAIN_HAND, blockHitResult)
}
override fun getDestroyProgress(state: BlockState, player: Player, level: BlockGetter, pos: BlockPos): Float {
// Retrieve the block entity at the given position
val blockEntity = level.getBlockEntity(pos) as? NestBlockEntity ?: return super.getDestroyProgress(state, player, level, pos)
// Check if the player is the owner of the nest
val isOwner = blockEntity.ownerUUID == player.uuid
// Check if there's an egg present in the nest
val hasEgg = !blockEntity.getItem(0).isEmpty
return if (!isOwner && hasEgg) {
// Reduce destroy speed making it 5 times harder to break
super.getDestroyProgress(state, player, level, pos) * 0.2f
} else {
super.getDestroyProgress(state, player, level, pos)
}
}
// todo we want destroying to take longer if it is not their nest
override fun playerWillDestroy(level: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
val entity = level.getBlockEntity(pos) as? NestBlockEntity
// todo we do silk touch now
/*entity?.let {
if (!player.isCreative) {
val itemEntity = when (this.variant) {
NestVariant.BASIC -> ItemEntity(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, CobblemonItems.BASE_NEST.defaultInstance)
NestVariant.WATER -> ItemEntity(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, CobblemonItems.WATER_NEST.defaultInstance)
NestVariant.CAVE -> ItemEntity(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, CobblemonItems.CAVE_NEST.defaultInstance)
NestVariant.NETHER -> ItemEntity(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, CobblemonItems.NETHER_NEST.defaultInstance)
}
level.addFreshEntity(itemEntity)
}
it.dropItem( level, pos, state)
}*/
return super.playerWillDestroy(level, pos, state, player)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = NestBlockEntity(pos, state)
override fun getRenderShape(blockState: BlockState) = RenderShape.MODEL
override fun canPlaceLiquid(
player: Player?,
level: BlockGetter,
pos: BlockPos,
state: BlockState,
fluid: Fluid
): Boolean {
return (variant == NestVariant.WATER || variant == NestVariant.SEAGRASS || variant == NestVariant.WARM_OCEAN) && super.canPlaceLiquid(player, level, pos, state, fluid)
}
override fun getFluidState(state: BlockState): FluidState {
return if (state.getValue(BlockStateProperties.WATERLOGGED)) {
Fluids.WATER.getSource(false)
} else super.getFluidState(state)
}
override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
stack: ItemStack
) {
val blockEntity = level.getBlockEntity(pos) as? NestBlockEntity
if (placer is Player) {
blockEntity?.ownerUUID = placer.uuid
}
super.setPlacedBy(level, pos, state, placer, stack)
}
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
val fluidState = ctx.level.getFluidState(ctx.clickedPos)
// If the nest variant can be waterlogged, set it accordingly
if (canBeWaterlogged()) {
return defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, fluidState.type == Fluids.WATER)
}
// Prevent placement if the block is submerged in water
if (!canBeWaterlogged() && fluidState.type == Fluids.WATER) {
return null
}
return defaultBlockState()
}
override fun <T : BlockEntity?> getTicker(level: Level, blockState: BlockState, blockEntityType: BlockEntityType<T>): BlockEntityTicker<T>? {
return createTickerHelper(blockEntityType, CobblemonBlockEntities.NEST, NestBlockEntity.TICKER)
}
override fun codec(): MapCodec<NestBlock> {
return RecordCodecBuilder.mapCodec { instance ->
instance.group(
Codec.STRING.fieldOf("variant").forGetter { block -> block.variant.id }, // Serializes the variant
Codec.BOOL.optionalFieldOf("waterlogged", false).forGetter { block ->
block.defaultBlockState().getValue(BlockStateProperties.WATERLOGGED)
}
).apply(instance) { variant, waterlogged ->
val block = NestBlock(NestVariant.valueOf(variant.uppercase()), Properties.of().noOcclusion())
block.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, waterlogged)
block
}
}
}
}
@@ -42,7 +42,7 @@ import net.minecraft.world.level.gameevent.GameEvent
class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.BERRY, pos, state) {
lateinit var berryIdentifier: ResourceLocation
private val ticksPerMinute = 1200
var renderState: RenderState? = null
var renderState: BlockEntityRenderState? = null
//The time left for the tree until its either age 3 or age 5 (check block state, if we are at age 0<x<3 unti 3 otherwise until 5)
var growthTimer: Int = 0
set(value) {
@@ -346,11 +346,6 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
return
}
//Using a similar approach to ComputerCraft, implement this client side
interface RenderState : AutoCloseable {
var needsRebuild: Boolean
}
companion object {
internal val TICKER = BlockEntityTicker<BerryBlockEntity> { world, pos, state, blockEntity ->
if (world.isClientSide) return@BlockEntityTicker
@@ -0,0 +1,14 @@
/*
* 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
//Using a similar approach to ComputerCraft, implement this client side
interface BlockEntityRenderState : AutoCloseable {
var needsRebuild: Boolean
}
@@ -0,0 +1,167 @@
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.pokemon.breeding.Egg
import com.cobblemon.mod.common.item.components.EggComponent
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.core.BlockPos
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.level.block.Block
import com.cobblemon.mod.common.util.codec.CodecUtils
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.phys.BlockHitResult
import java.util.*
class EggBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.EGG, pos, state) {
private var eggComponent: EggComponent? = null
var renderState: BlockEntityRenderState? = null
// Component data codec
companion object {
val CODEC: Codec<EggBlockEntity> = RecordCodecBuilder.create { instance ->
instance.group(
ResourceLocation.CODEC.optionalFieldOf("species", cobblemonResource("unknown")).forGetter { it.getEgg()?.species ?: cobblemonResource("unknown") },
Pokemon.CODEC.optionalFieldOf("parentOne").forGetter { Optional.ofNullable(it.getEgg()?.parentOne) },
Pokemon.CODEC.optionalFieldOf("parentTwo").forGetter { Optional.ofNullable(it.getEgg()?.parentTwo) },
Codec.STRING.optionalFieldOf("form", "").forGetter { it.getEgg()?.form ?: "" },
Codec.FLOAT.optionalFieldOf("timeToHatch", 0F).forGetter { it.getEgg()?.timeToHatch ?: 0F }
).apply(instance) { species, parentOneOpt, parentTwoOpt, form, timeToHatch ->
EggBlockEntity(BlockPos(0, 0, 0), Blocks.AIR.defaultBlockState()).apply {
setEgg(
Egg(
species = species,
parentOne = parentOneOpt.orElse(null),
parentTwo = parentTwoOpt.orElse(null),
form = form,
timeToHatch = timeToHatch
)
)
}
}
}
}
/**
* Sets the egg data and notifies the client.
*/
fun setEggData(egg: Egg?) {
this.eggComponent = EggComponent(egg)
setChanged()
level?.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS)
}
/**
* Retrieves the Egg data from the EggComponent.
*/
fun getEgg(): Egg? {
return eggComponent?.egg
}
/**
* Sets the EggComponent and synchronizes with the client.
*/
fun setEgg(egg: Egg?) {
this.eggComponent = EggComponent(egg)
setChanged()
level?.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS)
}
/**
* Save the EggComponent data manually to NBT.
*/
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
eggComponent?.egg?.let { egg ->
Egg.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, egg)
.result()
?.ifPresent { encoded -> tag.put("EggComponent", encoded) }
}
}
fun dropEgg(
state: BlockState,
world: Level,
pos: BlockPos
) {
val currentEgg = getEgg() ?: return
// Log before clearing
println("Dropping egg: ${currentEgg.species}, Form: ${currentEgg.form} at $pos")
// Create an ItemStack with the EggComponent
val eggItemStack = CobblemonItems.POKEMON_EGG.defaultInstance.apply {
set(CobblemonItemComponents.EGG_DATA, EggComponent(currentEgg))
}
// Debug log to verify component data
val component = eggItemStack.get(CobblemonItemComponents.EGG_DATA)
println("EggComponent on dropped ItemStack: $component")
// Spawn the egg as an entity
val itemEntity = ItemEntity(world, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, eggItemStack)
world.addFreshEntity(itemEntity)
// Remove egg block
world.removeBlockEntity(pos)
world.removeBlock(pos, false)
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
}
fun onUse(
state: BlockState,
world: Level,
pos: BlockPos,
player: ServerPlayer,
hand: InteractionHand,
hit: BlockHitResult
): InteractionResult {
val currentEgg = getEgg()
if (currentEgg != null) {
dropEgg(state, world, pos)
return InteractionResult.SUCCESS
}
return InteractionResult.FAIL
}
/**
* Load the EggComponent data manually from NBT.
*/
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
if (tag.contains("EggComponent")) {
val egg = Egg.CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, tag.get("EggComponent"))
.result()
?.orElse(null)
eggComponent = if (egg != null) EggComponent(egg) else null
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener>? {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
val tag = saveWithoutMetadata(registryLookup)
return tag
}
}
@@ -0,0 +1,251 @@
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.habitats.Habitats
import com.cobblemon.mod.common.api.habitats.WeightedSpecies
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.levelgen.structure.StructureStart
import net.minecraft.world.phys.BlockHitResult
class GrottoBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(CobblemonBlockEntities.GROTTO_BLOCK, pos, state) {
var detectedStructure: ResourceLocation? = null
var structureCenter: BlockPos? = null
var hasCheckedStructure: Boolean = false
var lastMoonPhaseChecked: Int = -1
var currentSpawnGroup: List<WeightedSpecies> = emptyList()
var mimicId: ResourceLocation = BuiltInRegistries.BLOCK.getKey(Blocks.SPONGE)
val mimickedState: BlockState
get() = BuiltInRegistries.BLOCK.get(mimicId).defaultBlockState() ?: Blocks.SPONGE.defaultBlockState()
fun setMimic(id: ResourceLocation) {
mimicId = id
setChanged()
level?.sendBlockUpdated(worldPosition, blockState, blockState, Block.UPDATE_ALL)
}
// todo to test with checking how it looks as other blocks
val mimicCycle: List<ResourceLocation> = listOf(
ResourceLocation.parse("minecraft:stone"),
ResourceLocation.parse("minecraft:cobblestone"),
ResourceLocation.parse("minecraft:granite"),
ResourceLocation.parse("minecraft:diorite"),
ResourceLocation.parse("minecraft:andesite"),
ResourceLocation.parse("minecraft:deepslate"),
ResourceLocation.parse("minecraft:polished_deepslate"),
ResourceLocation.parse("minecraft:oak_planks"),
ResourceLocation.parse("minecraft:spruce_planks"),
ResourceLocation.parse("minecraft:birch_planks"),
ResourceLocation.parse("minecraft:jungle_planks"),
ResourceLocation.parse("minecraft:acacia_planks"),
ResourceLocation.parse("minecraft:dark_oak_planks"),
ResourceLocation.parse("minecraft:mangrove_planks"),
ResourceLocation.parse("minecraft:cherry_planks"),
ResourceLocation.parse("minecraft:bamboo_planks"),
ResourceLocation.parse("minecraft:bricks"),
ResourceLocation.parse("minecraft:mossy_cobblestone"),
ResourceLocation.parse("minecraft:moss_block"),
ResourceLocation.parse("minecraft:bookshelf"),
ResourceLocation.parse("minecraft:loom"),
ResourceLocation.parse("minecraft:note_block"),
ResourceLocation.parse("minecraft:coal_block"),
ResourceLocation.parse("minecraft:iron_block"),
ResourceLocation.parse("minecraft:gold_block"),
ResourceLocation.parse("minecraft:diamond_block"),
ResourceLocation.parse("minecraft:emerald_block"),
ResourceLocation.parse("minecraft:lapis_block"),
ResourceLocation.parse("minecraft:redstone_block"),
ResourceLocation.parse("minecraft:netherite_block"),
ResourceLocation.parse("minecraft:obsidian"),
ResourceLocation.parse("minecraft:crying_obsidian"),
ResourceLocation.parse("minecraft:glowstone"),
ResourceLocation.parse("minecraft:blue_ice"),
ResourceLocation.parse("minecraft:packed_ice"),
ResourceLocation.parse("minecraft:ice"),
ResourceLocation.parse("minecraft:sandstone"),
ResourceLocation.parse("minecraft:red_sandstone"),
ResourceLocation.parse("minecraft:cut_sandstone"),
ResourceLocation.parse("minecraft:smooth_sandstone"),
ResourceLocation.parse("minecraft:black_concrete"),
ResourceLocation.parse("minecraft:white_concrete"),
ResourceLocation.parse("minecraft:lime_concrete"),
ResourceLocation.parse("minecraft:cyan_concrete"),
ResourceLocation.parse("minecraft:purple_concrete"),
ResourceLocation.parse("minecraft:light_blue_concrete"),
ResourceLocation.parse("minecraft:yellow_concrete"),
ResourceLocation.parse("minecraft:red_concrete")
)
var mimicIndex: Int = 0
fun cycleMimic() {
mimicIndex = (mimicIndex + 1) % mimicCycle.size
mimicId = mimicCycle[mimicIndex]
setChanged()
level?.sendBlockUpdated(worldPosition, blockState, blockState, Block.UPDATE_CLIENTS)
}
fun onUse(player: Player, hit: BlockHitResult): InteractionResult {
cycleMimic()
player.displayClientMessage(Component.literal("Mimicking: $mimicId"), true)
return InteractionResult.SUCCESS
}
override fun saveAdditional(tag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.saveAdditional(tag, registryLookup)
tag.putString("MimicId", mimicId.toString())
detectedStructure?.let { tag.putString("DetectedStructure", it.toString()) }
tag.putBoolean("HasCheckedStructure", hasCheckedStructure)
val list = ListTag()
currentSpawnGroup.forEach {
val entry = CompoundTag()
entry.putString("species", it.species.toString())
entry.putInt("weight", it.weight)
list.add(entry)
}
tag.put("CurrentSpawnGroup", list)
structureCenter?.let {
val centerTag = CompoundTag()
centerTag.putInt("X", it.x)
centerTag.putInt("Y", it.y)
centerTag.putInt("Z", it.z)
tag.put("StructureCenter", centerTag)
}
}
override fun loadAdditional(tag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.loadAdditional(tag, registryLookup)
tag.getString("MimicId").let {
mimicId = ResourceLocation.tryParse(it) ?: BuiltInRegistries.BLOCK.getKey(Blocks.SPONGE)
}
hasCheckedStructure = tag.getBoolean("HasCheckedStructure")
detectedStructure = tag.getString("DetectedStructure").takeIf { it.isNotEmpty() }?.let(ResourceLocation::tryParse)
val list = tag.getList("CurrentSpawnGroup", Tag.TAG_COMPOUND.toInt())
currentSpawnGroup = list.mapNotNull {
if (it !is CompoundTag) return@mapNotNull null
val species = ResourceLocation.tryParse(it.getString("species")) ?: return@mapNotNull null
val weight = it.getInt("weight")
WeightedSpecies(species, weight)
}
if (tag.contains("StructureCenter", Tag.TAG_COMPOUND.toInt())) {
val centerTag = tag.getCompound("StructureCenter")
val x = centerTag.getInt("X")
val y = centerTag.getInt("Y")
val z = centerTag.getInt("Z")
structureCenter = BlockPos(x, y, z)
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener> {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
return saveWithoutMetadata(registryLookup)
}
fun findNearbyStructure(level: ServerLevel, pos: BlockPos, radius: Int): ResourceLocation? {
val structureManager = level.structureManager()
val registry = level.registryAccess().registryOrThrow(Registries.STRUCTURE)
for (entry in registry.entrySet()) {
val structure = entry.value
val structureStart = structureManager.getStructureWithPieceAt(pos, structure)
if (structureStart != StructureStart.INVALID_START && structureStart.boundingBox.isInside(pos)) {
structureCenter = structureStart.boundingBox.center
return entry.key.location()
}
}
return null
}
companion object {
val TICKER = BlockEntityTicker<GrottoBlockEntity> { world, pos, state, blockEntity ->
if (world is ServerLevel) {
if (!blockEntity.hasCheckedStructure) {
blockEntity.hasCheckedStructure = true
val structure = blockEntity.findNearbyStructure(world, pos, radius = 5)
if (structure != null) {
blockEntity.detectedStructure = structure
blockEntity.setChanged()
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
println("[GrottoBlockEntity] Found structure at $pos: $structure")
}
}
val currentMoonPhase = world.moonPhase
if (currentMoonPhase != blockEntity.lastMoonPhaseChecked) {
blockEntity.lastMoonPhaseChecked = currentMoonPhase
if (currentMoonPhase == 7 || blockEntity.currentSpawnGroup.isEmpty()) {
val structure = blockEntity.detectedStructure
if (structure != null) {
blockEntity.detectedStructure = structure
blockEntity.setChanged()
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
println("[GrottoBlockEntity] Detected structure at $pos: $structure")
val habitat = Habitats.getHabitat(structure)
if (habitat != null && habitat.spawnPool.isNotEmpty()) {
val selectedGroup = weightedGroupRoll(habitat.spawnPool)
blockEntity.currentSpawnGroup = selectedGroup.spawns
blockEntity.lastMoonPhaseChecked = world.moonPhase
blockEntity.setChanged()
println("[GrottoBlockEntity] Selected spawn group for $structure: ${blockEntity.currentSpawnGroup.map { it.species }}")
}
}
}
}
}
}
private fun weightedGroupRoll(groups: List<com.cobblemon.mod.common.api.habitats.WeightedGroup>): com.cobblemon.mod.common.api.habitats.WeightedGroup {
val totalWeight = groups.sumOf { it.weight.toDouble() }
var roll = Math.random() * totalWeight
for (group in groups) {
roll -= group.weight
if (roll <= 0) return group
}
return groups.last() // fallback
}
}
}
@@ -0,0 +1,302 @@
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.block.IncubatorBlock
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.client.render.models.blockbench.egg.EggState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.components.EggComponent
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.*
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock.createTickerHelper
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import java.util.*
class IncubatorBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(CobblemonBlockEntities.INCUBATOR, pos, state), WorldlyContainer {
val eggState = EggState()
companion object {
val TICKER = BlockEntityTicker<IncubatorBlockEntity> { world, pos, state, blockEntity ->
// Retrieve the Egg from the EggComponent
val egg = blockEntity.getItem(0)?.get(CobblemonItemComponents.EGG_DATA)?.egg
if (egg != null) {
if (egg.timeToHatch <= 0) {
// todo egg is ready to hatch
blockEntity.setChanged()
return@BlockEntityTicker
}
// Decrease hatch time if the egg still has time left (faster if in nether)
val isNether = if (world != null && world.dimension() == Level.NETHER) true else false
if (egg.timeToHatch > 0F) {
egg.timeToHatch = when {
isNether -> egg.timeToHatch - Cobblemon.config.netherIncubatorHatchRate
else -> egg.timeToHatch - Cobblemon.config.incubatorHatchRate
}
}
// todo is this needed?
// Sync to client every 10 seconds (200 ticks)
/*if (egg.timeToHatch % 200F == 0F) {
blockEntity.setEgg(egg) // Update component and synchronize
blockEntity.setChanged()
}*/
}
}
}
private var eggItem: ItemStack = ItemStack.EMPTY
var ownerUUID: UUID? = null
override fun saveAdditional(tag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.saveAdditional(tag, registryLookup)
tag.put("EggItem", eggItem.saveOptional(registryLookup))
tag.putBoolean("IsHatching", eggState.isHatching)
tag.putBoolean("HatchingTriggered", eggState.hatchingTriggered)
eggState.renderingBlockPos?.let {
tag.putIntArray("RenderingBlockPos", listOf(it.x, it.y, it.z))
}
ownerUUID?.let { tag.putUUID("OwnerUUID", it) }
}
override fun loadAdditional(tag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.loadAdditional(tag, registryLookup)
eggItem = ItemStack.parseOptional(registryLookup, tag.getCompound("EggItem"))
eggState.isHatching = tag.getBoolean("IsHatching")
eggState.hatchingTriggered = tag.getBoolean("HatchingTriggered")
if (tag.contains("OwnerUUID")) {
ownerUUID = tag.getUUID("OwnerUUID")
}
if (tag.contains("RenderingBlockPos")) {
val posArray = tag.getIntArray("RenderingBlockPos")
if (posArray.size == 3) {
eggState.renderingBlockPos = BlockPos(posArray[0], posArray[1], posArray[2])
}
}
}
fun getComparatorOutput(): Int {
val eggComponent = eggItem.get(CobblemonItemComponents.EGG_DATA)
val egg = eggComponent?.egg ?: return 0
/*val maxHatchTime = 100F // todo replace this with the egg's species expected time to hatch (will need a new algorithm method. Maybe based off of egg cycle + other stuff)
val currentHatchTime = egg.timeToHatch.coerceAtLeast(0F).coerceAtMost(maxHatchTime)
// Calculate progress: 0 (ready) to 15
val progress = ((maxHatchTime - currentHatchTime) / maxHatchTime * 15).toInt()
return progress*/
if (egg.timeToHatch <= 0) return 15 else return 0 // send signal based on whether it is ready to hatch or now for now (we can make it more advanced later)
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener>? {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
val tag = saveWithoutMetadata(registryLookup)
return tag
}
fun dropItem(world: Level, pos: BlockPos, state: BlockState) {
if (!eggItem.isEmpty) {
val itemEntity = ItemEntity(world, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, eggItem.copy())
world.addFreshEntity(itemEntity)
eggItem = ItemStack.EMPTY
setChanged()
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
}
}
fun onUse(
state: BlockState,
world: Level,
pos: BlockPos,
player: ServerPlayer,
hand: InteractionHand,
hit: BlockHitResult
): InteractionResult {
val heldItem = player.getItemInHand(hand)
val eggComponent = eggItem.get(CobblemonItemComponents.EGG_DATA)
if (!eggItem.isEmpty && eggComponent != null) {
// Hatch the Pokémon if the time to hatch is 0 or below
if ((eggComponent.egg?.timeToHatch ?: 0F) <= 0F) {
eggState.isHatching = true // Trigger hatch animation
eggState.renderingBlockPos = this.blockPos // Pass block pos to renderer for packet
setChanged()
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
return InteractionResult.SUCCESS
}
// Otherwise, drop the egg
dropItem(world, pos, state)
world.playSound(null, pos, SoundEvents.ITEM_PICKUP, net.minecraft.sounds.SoundSource.BLOCKS, 1.0f, 1.0f)
return InteractionResult.SUCCESS
}
// Place a new egg if the player is holding one
if (!heldItem.isEmpty && heldItem.get(CobblemonItemComponents.EGG_DATA) != null) {
if (!player.isCreative) {
setItem(0, heldItem.split(1))
} else {
setItem(0, heldItem.copy())
}
world.playSound(null, pos, SoundEvents.ITEM_PICKUP, net.minecraft.sounds.SoundSource.BLOCKS, 1.0f, 1.0f)
return InteractionResult.SUCCESS
}
return InteractionResult.FAIL
}
fun hatchPokemon(
state: BlockState,
world: Level,
pos: BlockPos,
player: ServerPlayer
) {
val eggItem = getItem(0) // Retrieve the egg item from the container
val egg = eggItem.get(CobblemonItemComponents.EGG_DATA)?.egg ?: return
if (world !is ServerLevel) return
val parentOne = egg.parentOne
val parentTwo = egg.parentTwo
// Validate parent Pokémon
if (parentOne == null || parentTwo == null) {
player.displayClientMessage("Hatching failed: Parent Pokémon not found.".red(), true)
return
}
// Perform breeding to generate the Pokémon
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val hatchedPokemon = breedingResult.pokemon?.generatePokemon()
if (hatchedPokemon == null) {
player.displayClientMessage("Hatching failed: Unable to generate Pokémon.".red(), true)
return
}
val playerParty = Cobblemon.storage.getParty(player)
val playerPC = Cobblemon.storage.getPC(player)
// Ensure PC has space for the Pokémon
if (playerPC.getFirstAvailablePosition() == null) {
player.displayClientMessage("Hatching failed: Your PC is full.".red(), true)
return
}
// Add Pokémon to the party or PC
if (playerParty.getFirstAvailablePosition() != null) {
playerParty.add(hatchedPokemon)
player.displayClientMessage("The Pokémon was added to your party!".green(), true)
} else if (playerPC.add(hatchedPokemon)) {
player.displayClientMessage("The Pokémon was added to your PC storage.".green(), true)
} else {
player.displayClientMessage("Hatching failed: Unable to store Pokémon.".red(), true)
return
}
// Clear the egg item and synchronize
setItem(0, ItemStack.EMPTY)
setChanged()
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
}
override fun getContainerSize(): Int = 1
override fun isEmpty(): Boolean = eggItem.isEmpty
override fun getItem(slot: Int): ItemStack = if (slot == 0) eggItem else ItemStack.EMPTY
override fun removeItem(slot: Int, amount: Int): ItemStack {
if (slot == 0 && !eggItem.isEmpty) {
val removed = eggItem.split(amount)
setChanged()
return removed
}
return ItemStack.EMPTY
}
override fun removeItemNoUpdate(slot: Int): ItemStack {
if (slot == 0) {
val removed = eggItem
eggItem = ItemStack.EMPTY
return removed
}
return ItemStack.EMPTY
}
override fun setItem(slot: Int, stack: ItemStack) {
if (slot == 0) {
eggItem = stack
setChanged()
}
}
override fun setChanged() {
val hasEgg = !eggItem.isEmpty
level?.let {
if (blockState.getValue(IncubatorBlock.HAS_EGG) != hasEgg) {
// Update the block state with the new `HAS_EGG` value
val newState = blockState.setValue(IncubatorBlock.HAS_EGG, hasEgg)
level?.setBlock(worldPosition, newState, 3)
}
it.blockEntityChanged(worldPosition)
it.sendBlockUpdated(worldPosition, blockState, blockState, Block.UPDATE_ALL)
it.updateNeighborsAt(worldPosition, blockState.block)
}
}
override fun stillValid(player: Player): Boolean = true
override fun getSlotsForFace(direction: Direction): IntArray = intArrayOf(0)
override fun canPlaceItemThroughFace(slot: Int, stack: ItemStack, direction: Direction?): Boolean {
return slot == 0 && stack.get(CobblemonItemComponents.EGG_DATA) != null
}
override fun canTakeItemThroughFace(slot: Int, stack: ItemStack, direction: Direction): Boolean {
return slot == 0 && !eggItem.isEmpty
}
override fun clearContent() {
eggItem = ItemStack.EMPTY
}
}
@@ -0,0 +1,551 @@
/*
* 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.*
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.detail.EntitySpawnResult
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.spawning.fishing.NestSpawner
import com.cobblemon.mod.common.api.spawning.nest.NestSpawnCause
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.client.render.models.blockbench.egg.EggState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.components.EggComponent
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.toBlockPos
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.entity.player.Player
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.InteractionResult
import net.minecraft.world.InteractionHand
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.component.DataComponentType
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.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.Container
import net.minecraft.world.WorldlyContainer
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.Level
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.ticks.TickPriority
import java.util.*
import java.util.UUID
class NestBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.NEST, pos, state), WorldlyContainer {
var renderState: BlockEntityRenderState? = null
var parents: Pair<UUID?, UUID?> = Pair(null, null) // todo make sure this is synced
private var eggItem: ItemStack = ItemStack.EMPTY
var ownerUUID: UUID? = null // player UUID that placed this nest
val eggState = EggState()
var nestClaimedBy: Pair<UUID?, UUID?> = Pair(null, null)
/**
* Sets the ItemStack and synchronizes it.
*/
fun setItem(itemStack: ItemStack) {
this.eggItem = itemStack
setChanged() // Mark the block entity as changed.
level?.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS) // Notify the client.
}
/**
* Retrieves the ItemStack.
*/
fun getItem(): ItemStack = eggItem
/**
* Drops the ItemStack in the world.
*/
fun dropItem(world: Level, pos: BlockPos, state: BlockState) {
if (!eggItem.isEmpty) {
val itemEntity = ItemEntity(world, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, eggItem.copy())
world.addFreshEntity(itemEntity)
eggItem = ItemStack.EMPTY
setChanged()
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
}
}
/**
* Handles interaction with the block.
*/
fun onUse(
state: BlockState,
world: Level,
pos: BlockPos,
player: ServerPlayer,
hand: InteractionHand,
hit: BlockHitResult
): InteractionResult {
val heldItem = player.getItemInHand(hand)
val eggComponent = eggItem.get(CobblemonItemComponents.EGG_DATA)
if (!eggItem.isEmpty && eggComponent != null) {
// Hatch the Pokémon if the time to hatch is 0 or below
if ((eggComponent.egg?.timeToHatch ?: 0F) <= 0F) {
eggState.isHatching = true // Trigger hatch animation on the renderer
//playSound(CobblemonSounds.BREEDING_EGG_HATCH)
eggState.renderingBlockPos = this.blockPos // Pass block pos to renderer for packet
setChanged()
level?.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
return InteractionResult.SUCCESS
}
// Otherwise, drop the egg
dropItem(world, pos, state)
eggState.resetHatchingState()
world.playSound(null, pos, SoundEvents.ITEM_PICKUP, net.minecraft.sounds.SoundSource.BLOCKS, 1.0f, 1.0f)
return InteractionResult.SUCCESS
}
// Place a new egg if the player is holding one
if (!heldItem.isEmpty && heldItem.get(CobblemonItemComponents.EGG_DATA) != null) {
if (!player.isCreative) {
setItem(heldItem.split(1))
} else {
setItem(heldItem.copy())
}
eggState.resetHatchingState()
world.playSound(null, pos, SoundEvents.ITEM_PICKUP, net.minecraft.sounds.SoundSource.BLOCKS, 1.0f, 1.0f)
return InteractionResult.SUCCESS
}
return InteractionResult.FAIL
}
override fun saveAdditional(tag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.saveAdditional(tag, registryLookup)
//if (!eggItem.isEmpty) {
tag.put("EggItem", eggItem.saveOptional(registryLookup))
//}
ownerUUID?.let { tag.putUUID("OwnerUUID", it) }
tag.putBoolean("IsHatching", eggState.isHatching)
tag.putBoolean("HatchingTriggered", eggState.hatchingTriggered)
eggState.renderingBlockPos?.let {
tag.putIntArray("RenderingBlockPos", listOf(it.x, it.y, it.z))
}
}
override fun loadAdditional(tag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.loadAdditional(tag, registryLookup)
//if (tag.contains("EggItem")) {
eggItem = ItemStack.parseOptional(registryLookup, tag.getCompound("EggItem"))
//}
if (tag.contains("OwnerUUID")) {
ownerUUID = tag.getUUID("OwnerUUID")
}
eggState.isHatching = tag.getBoolean("IsHatching")
eggState.hatchingTriggered = tag.getBoolean("HatchingTriggered")
if (tag.contains("RenderingBlockPos")) {
val posArray = tag.getIntArray("RenderingBlockPos")
if (posArray.size == 3) {
eggState.renderingBlockPos = BlockPos(posArray[0], posArray[1], posArray[2])
}
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener>? {
//println("getUpdatePacket called for NestBlockEntity at $blockPos with egg: $eggComponent")
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
val tag = saveWithoutMetadata(registryLookup)
//println("getUpdateTag called for NestBlockEntity at $blockPos with tag: $tag")
return tag
}
/*fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}*/
fun hatchPokemon(
state: BlockState,
world: Level,
pos: BlockPos,
player: ServerPlayer
) {
val egg = getItem().get(CobblemonItemComponents.EGG_DATA)?.egg ?: return
if (world !is ServerLevel) return
val parentOne = egg.parentOne
val parentTwo = egg.parentTwo
if (parentOne == null || parentTwo == null) {
player.displayClientMessage("Hatching failed: Parent Pokémon not found.".red(), true)
return
}
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val hatchedPokemon = breedingResult.pokemon?.generatePokemon()
if (hatchedPokemon == null) {
player.displayClientMessage("Hatching failed: Unable to generate Pokémon.".red(), true)
return
}
val playerParty = Cobblemon.storage.getParty(player)
val playerPC = Cobblemon.storage.getPC(player)
// If PC is full, prevent hatching
// todo find maybe a better way to check if PC is full
if (playerPC.getFirstAvailablePosition() == null) {
player.displayClientMessage("Hatching failed: Your PC is full.".red(), true)
return
}
// Spawn the Pokémon entity at the egg's position
val spawnedEntity = PokemonEntity(world, hatchedPokemon).apply {
setPos(pos.x + 0.5, pos.y.toDouble(), pos.z + 0.5)
isNewlyHatched = true
world.addFreshEntity(this)
}
// Trigger cry animation
afterOnServer(seconds = 1.0f) {
spawnedEntity.cry()
}
// If party has space, add Pokémon to the party
if (playerParty.getFirstAvailablePosition() != null) {
playerParty.add(hatchedPokemon)
player.displayClientMessage("The Pokémon was added to your party!".green(), true)
}
// If party is full but PC has space, add to PC and recall Pokémon
else if (playerPC.add(hatchedPokemon)) {
player.displayClientMessage("The Pokémon was added to your PC storage.".green(), true)
// Schedule recall animation
spawnedEntity.after(seconds = 3.0f) {
spawnedEntity.recallWithAnimation()
}
/*// Remove the Pokémon entity after recall
spawnedEntity.after(seconds = 4.0f) {
spawnedEntity.remove(Entity.RemovalReason.DISCARDED)
}*/
}
// Clear the egg from the nest and update the block entity
setItem(Items.AIR.defaultInstance)
eggState.resetHatchingState()
setChanged()
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
}
/**
* Spawns a Pokémon of the given species around the specified position.
* @param world The world in which to spawn the Pokémon.
* @param pos The position around which to spawn the Pokémon.
* @param species The species of the Pokémon to spawn.
* @param isMale Whether the Pokémon should be male or female.
* @return The spawned Pokémon entity, or null if spawning failed.
*//*
fun spawnPokemon(
world: Level,
pos: BlockPos//,
//species: PokemonSpecies
): Pair<PokemonEntity?,PokemonEntity?> {
if (world !is ServerLevel) return null to null
// Generate a random offset within the radius
val randomOffset = {
world.random.nextDouble() * SPAWN_RADIUS * 2 - SPAWN_RADIUS
}
val spawnPos = pos.offset(randomOffset().toInt(), 0, randomOffset().toInt())
// todo Use Spawn files
// todo CHANGE THIS! We probably want a brand new spawner similar to the fishing spawner
val spawner = BestSpawner.nestSpawner
val buckets = Cobblemon.bestSpawner.config.buckets
val chosenBucket = chooseSpawnBucket(buckets)
val spawnCause = NestSpawnCause(
spawner = spawner,
bucket = chosenBucket,
entity = null
)
//
val result = spawner.run(spawnCause, level as ServerLevel, spawnPos)
var spawnedPokemon: PokemonEntity? = null
var partnerPokemon: PokemonEntity? = null
val resultingSpawn = result?.get()
if (resultingSpawn is EntitySpawnResult) {
for (entity in resultingSpawn.entities) {
// we can query the spawned pokemon here
spawnedPokemon = (entity as PokemonEntity)
// spawn a partner pokemon also in a random position
partnerPokemon = this.spawnPartnerPokemon(world, pos, spawnedPokemon)
// CRAB IDEA: todo maybe some grass particles? as if they appeared from the grass or forest?
}
}
return spawnedPokemon to partnerPokemon
}
fun spawnPartnerPokemon(
world: ServerLevel,
pos: BlockPos,
originalPokemon: PokemonEntity
): PokemonEntity? {
println("Attempting to spawn partner Pokémon...")
println("Original Pokémon being cloned: ${originalPokemon}")
val pokemon = originalPokemon.pokemon
println("Pokemon details before cloning: UUID=${pokemon.uuid}, Ability=${pokemon.ability}, Species=${pokemon.species}, Form=${pokemon.form}")
// Validate ability before cloning
if (pokemon.ability.template == Abilities.DUMMY) {
println("Original Pokémon has DUMMY ability. Rolling a new one before cloning...")
pokemon.rollAbility()
println("New ability assigned: ${pokemon.ability}")
}
// Perform the clone
val clonedPokemon = try {
pokemon.clone()
} catch (e: IllegalStateException) {
println("Error cloning Pokémon: ${e.message}")
return null // Skip spawning if cloning fails
}
// Apply further customization to the cloned Pokémon
clonedPokemon.gender = if (pokemon.gender == Gender.MALE) Gender.FEMALE else Gender.MALE
println("Cloned Pokémon details: ${clonedPokemon}")
// Generate a random offset for the partner Pokémon
val randomOffset = {
world.random.nextDouble() * SPAWN_RADIUS * 2 - SPAWN_RADIUS
}
val spawnPos = pos.offset(randomOffset().toInt(), 0, randomOffset().toInt())
// Create the partner PokemonEntity
val partnerPokemonEntity = PokemonEntity(world, clonedPokemon).apply {
setPos(spawnPos.x + 0.5, spawnPos.y.toDouble(), spawnPos.z + 0.5)
}
// Add the partner to the world
return if (world.addFreshEntity(partnerPokemonEntity)) partnerPokemonEntity else null
}
fun chooseSpawnBucket(buckets: List<SpawnBucket>): SpawnBucket {
val baseIncreases = listOf(2.5F, 1.0F, 0.6F) // Base increases for the first three buckets beyond the first
val adjustedWeights = buckets.mapIndexed { index, bucket ->
if (index == 0) {
// Placeholder, will be recalculated
0.0F
} else {
val increase = if (index < baseIncreases.size) baseIncreases[index] else baseIncreases.last() + (index - baseIncreases.size + 1) * 0.15F
bucket.weight + increase
}
}.toMutableList()
// Recalculate the first bucket's weight to ensure the total is 100%
val totalAdjustedWeight = adjustedWeights.sum() - adjustedWeights[0] // Corrected to ensure the list contains Floats
adjustedWeights[0] = 100.0F - totalAdjustedWeight + buckets[0].weight
// Random selection based on adjusted weights
val weightSum = adjustedWeights.sum()
val chosenSum = kotlin.random.Random.nextDouble(weightSum.toDouble()).toFloat() // Ensure usage of Random from kotlin.random package
var sum = 0.0F
adjustedWeights.forEachIndexed { index, weight ->
sum += weight
if (sum >= chosenSum) {
return buckets[index]
}
}
return buckets.first() // Fallback
}*/
// Hopper interaction methods
override fun getContainerSize(): Int = 1
override fun isEmpty(): Boolean = eggItem.isEmpty
override fun getItem(slot: Int): ItemStack = if (slot == 0) eggItem else ItemStack.EMPTY
override fun removeItem(slot: Int, amount: Int): ItemStack {
if (slot == 0 && !eggItem.isEmpty) {
val removed = eggItem.split(amount)
setChanged()
return removed
}
return ItemStack.EMPTY
}
override fun removeItemNoUpdate(slot: Int): ItemStack {
if (slot == 0) {
val removed = eggItem
eggItem = ItemStack.EMPTY
return removed
}
return ItemStack.EMPTY
}
override fun setItem(slot: Int, stack: ItemStack) {
if (slot == 0) {
eggItem = stack
setChanged()
}
}
override fun setChanged() {
// Marks the block entity as modified
level?.blockEntityChanged(worldPosition)
// Schedules a block update to synchronize with the client
level?.sendBlockUpdated(worldPosition, blockState, blockState, Block.UPDATE_ALL)
}
override fun getSlotsForFace(direction: Direction): IntArray {
return intArrayOf(0)
}
/**
* Allows hoppers to insert items into the container.
*/
override fun canPlaceItemThroughFace(slot: Int, stack: ItemStack, direction: Direction?): Boolean {
// Allow only items with EggComponent to be inserted
return slot == 0 && stack.get(CobblemonItemComponents.EGG_DATA) != null
}
/**
* Allows hoppers to pull items out of the container.
*/
override fun canTakeItemThroughFace(slot: Int, stack: ItemStack, direction: Direction): Boolean {
// Allow items to be pulled out only if the slot contains an egg item
return slot == 0 && !eggItem.isEmpty
}
override fun stillValid(player: Player): Boolean = true
override fun clearContent() {
eggItem = ItemStack.EMPTY
}
companion object {
private const val RANDOM_SPAWN_CHANCE = 10000 // 1 in 100 chance per random tick // todo remove this
private const val SPAWN_RADIUS = 5.0 // Radius around the nest to spawn Pokémon
val TICKER = BlockEntityTicker<NestBlockEntity> { world, pos, state, blockEntity ->
// Retrieve the Egg from the EggComponent
val egg = blockEntity.getItem().get(CobblemonItemComponents.EGG_DATA)?.egg
if (egg != null) {
/*if (egg.timeToHatch <= 0) {
// Try to find a nearby ServerPlayer to pass to hatchPokemon
val nearestPlayer = world.getNearestPlayer(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, 10.0, false)
if (nearestPlayer is ServerPlayer) {
// todo Crab Note: I removed this as it was auto-hatching eggs as soon as you got near them
//blockEntity.hatchPokemon(state, world, pos, nearestPlayer)
}
blockEntity.setChanged()
return@BlockEntityTicker
}*/
// Decrease hatch time if the egg still has time left (faster if in nether)
val isNether = if (world != null && world.dimension() == Level.NETHER) true else false
if (egg.timeToHatch > 0F) {
egg.timeToHatch = when {
isNether -> egg.timeToHatch - Cobblemon.config.netherNestHatchRate
else -> egg.timeToHatch - Cobblemon.config.nestHatchRate
}
}
// Sync to client every 10 seconds (200 ticks)
if (egg.timeToHatch % 200F == 0F) {
//blockEntity.setItem(egg) // Update component and synchronize
//blockEntity.setChanged()
val test = 1
}
} else {
// Attempt to spawn a random egg in the nest with a small chance
if (world.random.nextInt(RANDOM_SPAWN_CHANCE) == 0 && false) { // TODO remove the false part and remove this entirely later too
/*val (parent1, parent2) = blockEntity.spawnPokemon(world, pos)
val test = 1
// todo remove this as we are not having nests spawn pokemon anymore
/*val (parent1, parent2) = blockEntity.spawnPokemon(world, pos)
if (parent1 != null && parent2 != null) {
val childResult = BreedingLogicManager.breed(parent1.pokemon,parent2.pokemon)
// Create an egg with the parents
val egg = childResult.pokemon?.species?.let {
Egg(
species = it.resourceIdentifier,
parentOne = parent1.pokemon, // Parent one UUID
parentTwo = parent2.pokemon, // Parent two UUID
form = childResult.pokemon.formData.name.lowercase(),
timeToHatch = 200F // 10 seconds hatch time
)
}*/ // TODO KILL ALL OF THIS CODE ONCE WE HAVE A HABITAT SPAWNER
if (egg != null) {
blockEntity.setItem(egg.createEggItemStack())
}
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
}*/
}
}
}
}
}
@@ -11,21 +11,29 @@ package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.api.pasture.PastureLinkManager
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.pokemon.breeding.EggPatterns
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.block.NestBlock
import com.cobblemon.mod.common.block.PastureBlock
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.breeding.SimpleBreedingLogic
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.blockPositionsAsList
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.toVec3d
@@ -35,6 +43,8 @@ import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.Vec3i
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NbtUtils
@@ -46,12 +56,17 @@ import net.minecraft.world.entity.Pose
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.entity.EntityTypeTest
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.shapes.Shapes
import java.util.*
import kotlin.math.ceil
import kotlin.random.Random
class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(CobblemonBlockEntities.PASTURE, pos, state) {
@@ -95,9 +110,11 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
blockEntity.ticksUntilCheck--
if (blockEntity.ticksUntilCheck <= 0) {
blockEntity.checkPokemon()
//blockEntity.tryBreed()
}
blockEntity.togglePastureOn(blockEntity.getInRangeViewerCount(world, blockEntity.blockPos) > 0)
}
val DITTO_DEX_NUM = 132
}
var ticksUntilCheck = Cobblemon.config.pastureBlockUpdateTicks
@@ -106,6 +123,11 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
var maxRoamPos: BlockPos
var ownerId: UUID? = null
var ownerName: String = ""
//Maps a male pokemon to all of the female pokemon it can breed with
//val breedingSets: MutableMap<Tethering, MutableSet<Tethering>> = mutableMapOf()
//Basically here to decide whether to calc all breeding sets when updating the block entity
//We add breeding sets when pokemon are tethered but we need to calc them on world load too,
var initializedBreedingSets = false
init {
val radius = Cobblemon.config.pastureMaxWanderDistance
@@ -174,6 +196,7 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
pcId = pc.uuid,
entityId = entity.id
)
//calcBreedingSets(tethering)
pokemon.tetheringId = tethering.tetheringId
tetheredPokemon.add(tethering)
entity.tethering = tethering
@@ -283,6 +306,188 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
setChanged()
}
// todo the pasture block no longer tracks nests and is in control of breeding. it is all brains now
/*//Dear god this method needs to be cleaned up
fun tryBreed() {
val bredPokemon = mutableSetOf<Pokemon>()
//We should probably find a way to minimize how often this gets called
//Maybe cache the result and update it infrequently?
val nests = findUnusedNests()
if (nests.isNotEmpty()) {
breedingSets.forEach { father, mothers ->
if (father.getPokemon()?.breedingCooldown == 0) {
mothers.forEach { mother ->
if (mother.getPokemon()?.breedingCooldown == 0) {
val motherPoke = mother.getPokemon()!!
val fatherPoke = father.getPokemon()!!
if (BreedingLogicManager.canBreed(motherPoke, fatherPoke) && nests.isNotEmpty()) {
val breedResult = BreedingLogicManager.breed(motherPoke, fatherPoke)
if (breedResult.successful) {
// add cooldown to the parents
motherPoke.breedingCooldown = 240
// Add heart particles <3 <3 <3
val fatherPokemon = father.getPokemon()?.entity
val motherPokemon = mother.getPokemon()?.entity
if (fatherPokemon != null && motherPokemon != null) {
// Spawn heart particles for father
(level as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
fatherPokemon.getRandomX(1.0), // Particle X position with slight randomness
fatherPokemon.getRandomY() + 0.5, // Slightly raised Y position
fatherPokemon.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * fatherPokemon.random.nextGaussian(), // X velocity with slight variance
0.1 * fatherPokemon.random.nextGaussian(), // Y velocity with upwards variance
0.2 * fatherPokemon.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
// Spawn heart particles for mother
(level as net.minecraft.server.level.ServerLevel).sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
motherPokemon.getRandomX(1.0), // Particle X position with slight randomness
motherPokemon.getRandomY() + 0.5, // Slightly raised Y position
motherPokemon.getRandomZ(1.0), // Particle Z position with slight randomness
4 + kotlin.random.Random.nextInt(2), // Particle count
0.2 * motherPokemon.random.nextGaussian(), // X velocity with slight variance
0.1 * motherPokemon.random.nextGaussian(), // Y velocity with upwards variance
0.2 * motherPokemon.random.nextGaussian(), // Z velocity with slight variance
0.02 // Particle speed
)
}
println(breedResult.pokemon)
val nestTaken = nests.random()
val nestStal = level?.getBlockState(nestTaken)
val blockEntity = level?.getBlockEntity(nestTaken) as? NestBlockEntity
val avgEggCycle = (motherPoke.species.eggCycles + fatherPoke.species.eggCycles) / 2
if (blockEntity != null && breedResult.pokemon != null) {
val egg = Egg(
species = breedResult.pokemon!!.species.resourceIdentifier, // Species
parentOne = motherPoke, // Mother UUID
parentTwo = fatherPoke, // Father UUID
form = breedResult.pokemon.formData.name.lowercase(),
timeToHatch = 100F //breedResult.timeToHatch.toFloat() // 10 seconds hatch time
)
//Most params here need to be gotten from the form when implemented properly
blockEntity.setItem(egg.createEggItemStack())
val isClientSide = this.level?.isClientSide ?: false
if (isClientSide) {
//println("PokemonPastureBlockEntity: Running on the CLIENT side.")
} else {
//println("PokemonPastureBlockEntity: Running on the SERVER side.")
}
//println("Egg set in NestBlockEntity at ${nestTaken}: ${blockEntity.getEgg()!!.species}")
level?.getBlockState(nestTaken)?.let { level?.sendBlockUpdated(nestTaken, it, level?.getBlockState(nestTaken)!!, Block.UPDATE_CLIENTS) }
// remove the nest used from the nest consideration pool
nests.remove(nestTaken)
bredPokemon.add(fatherPoke)
bredPokemon.add(motherPoke)
if (Cobblemon.config.canDestinyKnotBreak) {
if (fatherPokemon?.pokemon?.heldItem?.item == CobblemonItems.DESTINY_KNOT.asItem())
fatherPokemon.pokemon.heldItemNoCopy().damageValue += 1
if (motherPokemon?.pokemon?.heldItem?.item == CobblemonItems.DESTINY_KNOT.asItem())
motherPokemon.pokemon.heldItemNoCopy().damageValue += 1
}
}
}
}
}
}
}
bredPokemon.forEach {
it.breedingCooldown = 20 // todo remove this when pasture doesn't do this anymore
}
}
}
}
//If new mon is passed, only calc/add breeding sets with that parent
//Otherwise, find all combos in the block (like on world load)
fun calcBreedingSets(newMon: Tethering? = null) {
initializedBreedingSets = true
if (newMon != null) {
for (t in tetheredPokemon) {
addIfBreedingPair(newMon, t)
}
}
else {
tetheredPokemon.forEachIndexed { i, mon ->
val listLen = tetheredPokemon.size
if (i != listLen - 1) {
tetheredPokemon.subList(i + 1, listLen).forEach {
addIfBreedingPair(mon, it)
}
}
}
}
}
fun addIfBreedingPair(monOne: Tethering, monTwo: Tethering) {
val pokemonOne = monOne.getPokemon()
val pokemonTwo = monTwo.getPokemon()
if (pokemonOne == null || pokemonTwo == null) {
return // Skip if either Pokémon is null
}
// Ensure distinct Pokémon
if (pokemonOne.uuid == pokemonTwo.uuid) {
return // Skip if the Pokémon have the same UUID
}
// Determine mother and father based on gender or if Ditto is involved
val mother = if (pokemonOne.gender == Gender.FEMALE || pokemonOne.species.nationalPokedexNumber == DITTO_DEX_NUM) monOne else monTwo
val father = if (mother == monOne) monTwo else monOne
// Ensure they can breed
if (SimpleBreedingLogic.canBreed(mother.getPokemon()!!, father.getPokemon()!!)) {
val fatherSet: MutableSet<Tethering> = breedingSets.getOrDefault(father, mutableSetOf())
fatherSet.add(mother)
breedingSets[father] = fatherSet
}
}
fun findUnusedNests(): MutableSet<BlockPos> {
val res = mutableSetOf<BlockPos>()
val cube = Shapes.box(
(this.blockPos.x - 2).toDouble(), (this.blockPos.y - 1).toDouble(), (this.blockPos.z - 2).toDouble(),
(this.blockPos.x + 3).toDouble(), (this.blockPos.y + 3).toDouble(), (this.blockPos.z + 3).toDouble()
)
cube.blockPositionsAsList().forEach {
val state = level?.getBlockState(it)
if (state?.block is NestBlock) {
val entity = level?.getBlockEntity(it) as NestBlockEntity
//Cobblemon.LOGGER.warn("Nest at ${it.toString()}")
if (entity.getItem().item != CobblemonItems.POKEMON_EGG.asItem()) {
res.add(it)
}
else
{
//Cobblemon.LOGGER.warn("Nest with egg at ${it.toString()}")
}
}
}
return res
}*/
fun onBroken() {
if (level is ServerLevel) {
tetheredPokemon.toList().forEach { releasePokemon(it.pokemonId) }
@@ -294,6 +499,11 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
val tethering = tetheredPokemon.find { it.pokemonId == pokemonId } ?: return
tethering.getPokemon()?.tetheringId = null
tetheredPokemon.remove(tethering)
// Recalculate breeding sets after removal
//breedingSets.clear()
//tetheredPokemon.forEach { calcBreedingSets(it) }
setChanged()
}
@@ -304,10 +514,16 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
tetheredPokemon.remove(it)
unpastured.add(it.pokemonId)
}
// Recalculate breeding sets after all removals
//breedingSets.clear()
//tetheredPokemon.forEach { calcBreedingSets(it) }
setChanged()
return unpastured
}
private fun getInRangeViewerCount(world: Level, pos: BlockPos, range: Double = 5.0): Int {
val box = AABB(
pos.x.toDouble() - range,
@@ -355,6 +571,9 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
}
this.minRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MIN_ROAM_POS).get()
this.maxRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MAX_ROAM_POS).get()
/*if (!initializedBreedingSets) {
calcBreedingSets()
}*/
}
override fun saveAdditional(nbt: CompoundTag, registryLookup: HolderLookup.Provider) {
@@ -0,0 +1,57 @@
/*
* 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.breeding
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.breeding.BreedingEligibilityEvent
import com.cobblemon.mod.common.api.events.breeding.BreedingResultEvent
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.breeding.BreedingLogic
import com.cobblemon.mod.common.api.pokemon.breeding.BreedingResult
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Nature
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Contains all breeding logics to be run through when doing the breeding logic
* Basically if a sidemod wants to override some aspect of breeding logic, they can implement the [BreedingLogic] interface
* then add it here
*/
object BreedingLogicManager : BreedingLogic {
override fun breed(mother: Pokemon, father: Pokemon): BreedingResult {
// todo this was not working
return super.breed(mother, father)
/*val baseBreedingResult = super.breed(mother, father)
var finalBreedingResult = baseBreedingResult
CobblemonEvents.BREEDING_RESULT.post(BreedingResultEvent(baseBreedingResult, mother, father)) {
finalBreedingResult = it.breedingResult
}
return finalBreedingResult*/
}
// todo this canBreed method is NOT working lmao
override fun canBreed(mother: Pokemon, father: Pokemon): Boolean {
// todo this was not working
return super.canBreed(mother, father)
/*val initBreedBool = super.canBreed(mother, father)
var finalBreedingResult = initBreedBool
CobblemonEvents.BREEDING_ELIGIBLE.post(BreedingEligibilityEvent(initBreedBool, mother, father) ) {
finalBreedingResult = it.canBreed
}
return super.canBreed(mother, father)*/
}
override fun resetBreedingCooldown(pokemon: Pokemon): Int {
return super.resetBreedingCooldown(pokemon)
}
}
@@ -0,0 +1,15 @@
/*
* 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.breeding
import com.cobblemon.mod.common.api.pokemon.breeding.BreedingLogic
object SimpleBreedingLogic : BreedingLogic {
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.client.render.block.GrottoBlockRenderer
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
@@ -15,7 +16,6 @@ import com.cobblemon.mod.common.CobblemonClientImplementation
import com.cobblemon.mod.common.CobblemonEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.molang.ObjectValue
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
@@ -26,13 +26,7 @@ import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
import com.cobblemon.mod.common.client.render.DeferredRenderer
import com.cobblemon.mod.common.client.render.block.BerryBlockRenderer
import com.cobblemon.mod.common.client.render.block.DisplayCaseRenderer
import com.cobblemon.mod.common.client.render.block.FossilAnalyzerRenderer
import com.cobblemon.mod.common.client.render.block.GildedChestBlockRenderer
import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.block.LecternBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.RestorationTankRenderer
import com.cobblemon.mod.common.client.render.block.*
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
import com.cobblemon.mod.common.client.render.generic.GenericBedrockRenderer
@@ -57,6 +51,7 @@ import com.cobblemon.mod.common.client.tooltips.TooltipManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.item.PokemonEggItem
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
@@ -73,14 +68,10 @@ import net.minecraft.client.renderer.blockentity.SignRenderer
import net.minecraft.client.renderer.entity.EntityRenderer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import java.util.UUID
import net.minecraft.client.player.AbstractClientPlayer
import net.minecraft.server.level.ServerPlayer
object CobblemonClient {
@@ -94,6 +85,16 @@ object CobblemonClient {
var checkedStarterScreen = false
var requests = ClientPlayerActionRequests()
var teamData = ClientPlayerTeamData()
/*var posableModelRepositories = listOf(
PokemonModelRepository,
PokeBallModelRepository,
NPCModelRepository,
FossilModelRepository,
BlockEntityModelRepository,
GenericBedrockEntityModelRepository,
EggModelRepo
)*/
val overlay: PartyOverlay by lazy { PartyOverlay() }
val battleOverlay: BattleOverlay by lazy { BattleOverlay() }
val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() }
@@ -127,7 +128,8 @@ object CobblemonClient {
this.registerBlockEntityRenderers()
registerBlockRenderTypes()
//registerColors()
registerBlockColors()
registerColors()
registerFlywheelRenderers()
this.registerEntityRenderers()
Berries.observable.subscribe {
@@ -136,7 +138,8 @@ object CobblemonClient {
this.registerTooltipManagers()
LOGGER.info("Registering custom BuiltinItemRenderers")
CobblemonBuiltinItemRendererRegistry.register(CobblemonItems.POKEMON_MODEL, PokemonItemRenderer())
registerBuiltinItemRenderers()
PlatformEvents.CLIENT_ITEM_TOOLTIP.subscribe { event ->
val stack = event.stack
@@ -187,16 +190,9 @@ 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.registerItemColors(PokemonEggItem::getColor, CobblemonItems.POKEMON_EGG)
}
*/
private fun registerBlockRenderTypes() {
@@ -243,6 +239,24 @@ object CobblemonClient {
CobblemonBlocks.PEP_UP_FLOWER,
CobblemonBlocks.POTTED_PEP_UP_FLOWER,
CobblemonBlocks.REVIVAL_HERB,
CobblemonBlocks.NEST_STICK,
CobblemonBlocks.NEST_LEAF,
CobblemonBlocks.NEST_STONE,
CobblemonBlocks.NEST_DEEPSLATE,
CobblemonBlocks.NEST_BASALT,
CobblemonBlocks.NEST_SNOW,
CobblemonBlocks.NEST_NETHER,
CobblemonBlocks.NEST_SOUL_SAND,
CobblemonBlocks.NEST_WARM_OCEAN,
CobblemonBlocks.NEST_SEAGRASS,
CobblemonBlocks.NEST_LUSH,
CobblemonBlocks.NEST_HAY,
CobblemonBlocks.NEST_SAND,
CobblemonBlocks.NEST_RED_SAND,
CobblemonBlocks.NEST_CHORUS,
CobblemonBlocks.BASIC_NEST,
CobblemonBlocks.CAVE_NEST,
CobblemonBlocks.WATER_NEST,
*CobblemonBlocks.berries().values.toTypedArray(),
CobblemonBlocks.POTTED_PEP_UP_FLOWER,
CobblemonBlocks.RESTORATION_TANK,
@@ -260,12 +274,23 @@ object CobblemonClient {
CobblemonBlocks.SKY_TUMBLESTONE_CLUSTER,
CobblemonBlocks.GIMMIGHOUL_CHEST,
CobblemonBlocks.DISPLAY_CASE,
CobblemonBlocks.INCUBATOR,
CobblemonBlocks.LECTERN
)
this.implementation.registerBlockRenderType(
RenderType.translucent(),
CobblemonBlocks.TUMBLESTONE_BLOCK
)
this.createBoatModelLayers()
}
fun registerBlockColors() {
this.implementation.registerBlockColors(NestLeafBlockColorProvider, CobblemonBlocks.NEST_LEAF)
}
fun beforeChatRender(context: GuiGraphics, partialDeltaTicks: Float) {
val partialDeltaTicks = Minecraft.getInstance().timer // Checking that this even works
// ClientTaskTracker.update(partialDeltaTicks / 20f)
@@ -291,6 +316,10 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.HANGING_SIGN, ::HangingSignRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.FOSSIL_ANALYZER, ::FossilAnalyzerRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.RESTORATION_TANK, ::RestorationTankRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.NEST, ::NestBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.GROTTO_BLOCK, ::GrottoBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.EGG, ::EggBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.INCUBATOR, ::IncubatorRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.GILDED_CHEST, ::GildedChestBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
@@ -315,6 +344,10 @@ object CobblemonClient {
this.implementation.registerEntityRenderer(CobblemonEntities.NPC, ::NPCRenderer)
}
private fun registerBuiltinItemRenderers() {
CobblemonBuiltinItemRendererRegistry.register(CobblemonItems.POKEMON_MODEL, PokemonItemRenderer())
}
fun reloadCodedAssets(resourceManager: ResourceManager) {
LOGGER.info("Loading assets...")
// Particles come first because animations need them.
@@ -26,6 +26,7 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.models.blockbench.JsonModelAdapter.Companion.model
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
@@ -385,6 +386,10 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
incrementAge(entity)
playWildShinySounds()
sendRidingChanges(entity)
if (entity.isNewlyHatched) {
hatch()
entity.isNewlyHatched = false
}
}
private fun sendRidingChanges(entity: PokemonEntity) {
@@ -398,6 +403,16 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
}
}
fun hatch() {
val animation = model?.getAnimation(this, "q.bedrock_stateful('hatch', 'hatch', 'endures_primary_animations');", runtime) ?: return
if (animation is PrimaryAnimation) {
addPrimaryAnimation(animation)
}
else {
addActiveAnimation(animation)
}
}
fun playWildShinySounds() {
val player = Minecraft.getInstance().player ?: return
val isWithinRange = player.position().distanceTo(currentEntity.position()) <= Cobblemon.config.shinyNoticeParticlesDistance
@@ -257,6 +257,7 @@ class PCGUI(
val y = (height - BASE_HEIGHT) / 2
// Render Portrait Background
blitk(
matrixStack = matrices,
texture = portraitBackgroundResource,
@@ -272,6 +273,7 @@ class PCGUI(
}
// Render Base Resource
blitk(
matrixStack = matrices,
texture = baseResource,
@@ -16,6 +16,11 @@ object CobblemonAtlases {
val atlases = mutableSetOf<TextureAtlasHolder>()
val BERRY_SPRITE_ATLAS = register("textures/atlas/berries.png", "berries")
val EGG_PATTERN_ATLAS = register("textures/atlas/egg_patterns.png", "egg_patterns")
//val EGG_PATTERN_SPRITE_ATLAS = register("textures/atlas/egg_pattern_sprites.png", "egg_pattern_sprites")
//val EGG_SPRITE_ATLAS = register("textures/atlas/eggs.png", "eggs")
val EGG_PATTERN_SPRITE_ATLAS = register("textures/atlas/egg_pattern_sprites.png", "egg_pattern_sprites")
fun register(atlasId: String, sourcePath: String): TextureAtlasHolder {
val atlas = CobblemonAtlas(
Minecraft.getInstance().textureManager,
@@ -8,10 +8,11 @@
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.block.entity.BlockEntityRenderState
import com.mojang.blaze3d.vertex.VertexBuffer
import net.minecraft.client.renderer.GameRenderer
class BerryBlockEntityRenderState : BerryBlockEntity.RenderState {
class BasicBlockEntityRenderState : BlockEntityRenderState {
val lastRenderFrame = -1
override var needsRebuild = true
val vbo: VertexBuffer = VertexBuffer(VertexBuffer.Usage.STATIC)
@@ -52,10 +52,10 @@ class BerryBlockRenderer(private val context: BlockEntityRendererProvider.Contex
if (!shouldRender(entity, entity.blockPos.toVec3d())) return
val blockState = entity.blockState
if (entity.renderState == null) {
entity.renderState = BerryBlockEntityRenderState()
entity.renderState = BasicBlockEntityRenderState()
}
val bf = vertexConsumers.getBuffer(CobblemonRenderLayers.BERRY_LAYER)
val renderState = entity.renderState as BerryBlockEntityRenderState
val renderState = entity.renderState as BasicBlockEntityRenderState
// if (renderState.needsRebuild || renderState.vboLightLevel != light) {
renderToBuffer(entity, matrices, light, overlay, renderState, bf)
// renderState.vboLightLevel = light
@@ -98,7 +98,7 @@ class BerryBlockRenderer(private val context: BlockEntityRendererProvider.Contex
}
fun renderToBuffer(entity: BerryBlockEntity, matrices: PoseStack, light: Int, overlay: Int, renderState: BerryBlockEntityRenderState, buffer: VertexConsumer) {
fun renderToBuffer(entity: BerryBlockEntity, matrices: PoseStack, light: Int, overlay: Int, renderState: BasicBlockEntityRenderState, buffer: VertexConsumer) {
if (entity.blockState.getValue(BerryBlock.AGE) == 0) {
renderBabyToBuffer(entity, matrices, light, overlay, buffer)
renderState.drawVbo = true
@@ -9,11 +9,18 @@
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.DisplayCaseBlock
import com.cobblemon.mod.common.block.EggBlock
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.block.entity.EggBlockEntity
import com.cobblemon.mod.common.client.render.helper.EggRenderHelper
import com.cobblemon.mod.common.client.render.models.blockbench.egg.EggState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.item.PokedexItem
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
@@ -22,6 +29,7 @@ import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.core.Direction
import net.minecraft.core.Position
import net.minecraft.core.component.DataComponents
import net.minecraft.world.item.*
import net.minecraft.world.item.component.CustomModelData
@@ -69,27 +77,50 @@ class DisplayCaseRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntit
*/
matrices.pushPose()
matrices.translate(0.5f, 0.4f, 0.5f)
matrices.scale(posType.scaleX, posType.scaleY, posType.scaleZ)
matrices.translate(posType.transX, posType.transY, posType.transZ)
try {
if (stack.`is`(CobblemonItems.POKEMON_EGG)) {
val eggComponent = stack.get(CobblemonItemComponents.EGG_DATA)
eggComponent?.egg?.let { egg ->
// Render the egg using the EggRenderHelper
EggRenderHelper.renderEgg(
egg = egg,
state = EggState(),
matrices = matrices,
vertexConsumers = vertexConsumers,
light = light,
overlay = overlay,
yRot = 0f,
scale = 1f,
translation = Triple(0.5f, 0.6f, 0.5f), // Adjust translation
rotation = Triple(180f, 0f, 0f), // Adjust rotation
tickDelta = 0F
)
}
return // Exit early after rendering the egg
}
matrices.mulPose(Axis.YP.rotationDegrees(-yRot))
matrices.mulPose(Axis.YP.rotationDegrees(posType.rotY))
matrices.translate(0.5f, 0.4f, 0.5f)
Minecraft.getInstance().itemRenderer.renderStatic(
stack,
ItemDisplayContext.GROUND,
light,
overlay,
matrices,
vertexConsumers,
entity.level,
0
)
matrices.scale(posType.scaleX, posType.scaleY, posType.scaleZ)
matrices.translate(posType.transX, posType.transY, posType.transZ)
matrices.popPose()
matrices.mulPose(Axis.YP.rotationDegrees(-yRot))
matrices.mulPose(Axis.YP.rotationDegrees(posType.rotY))
Minecraft.getInstance().itemRenderer.renderStatic(
stack,
ItemDisplayContext.GROUND,
light,
overlay,
matrices,
vertexConsumers,
entity.level,
0
)
} finally {
matrices.popPose() // Ensure pose stack is balanced
}
}
/*
private fun renderPokemon(
@@ -160,6 +191,7 @@ class DisplayCaseRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntit
stack.item is BedItem -> PositioningType.BED
stack.item is BannerItem -> PositioningType.BANNER
stack.`is`(CobblemonItemTags.POKE_BALLS) -> PositioningType.POKE_BALL
stack.`is`(CobblemonItems.POKEMON_EGG) -> PositioningType.EGG
stack.item is PokedexItem -> PositioningType.POKEDEX
stack.item == CobblemonItems.RELIC_COIN_POUCH -> PositioningType.COIN_POUCH
stack.item == CobblemonItems.PASTURE -> PositioningType.PASTURE
@@ -185,6 +217,7 @@ class DisplayCaseRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntit
MOB_HEAD(1f, 1f, 1f, 0f, -0.025f, 0f, 180f),
SHIELD(1f, 1f, 1f, 0f, -0.045f, 0f, 180f),
PASTURE(1f, 1f, 1f, 0f, 0.0375f, 0f),
COIN_POUCH(1f, 1f, 1f, 0f, 0.415f, 0f)
COIN_POUCH(1f, 1f, 1f, 0f, 0.415f, 0f),
EGG(1f, 1f, 1f, 0f, 1f, 0f)
}
}
@@ -0,0 +1,53 @@
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.block.entity.EggBlockEntity
import com.cobblemon.mod.common.client.render.helper.EggRenderHelper
import com.cobblemon.mod.common.client.render.models.blockbench.TexturedModel
import com.cobblemon.mod.common.client.render.models.blockbench.egg.EggState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.EggModelRepo
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
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.resources.ResourceLocation
class EggBlockRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<EggBlockEntity> {
override fun render(
entity: EggBlockEntity,
tickDelta: Float,
matrices: PoseStack,
vertexConsumers: MultiBufferSource,
light: Int,
overlay: Int
) {
// Ensure the renderer runs only on the client
val isClientSide = entity.level?.isClientSide ?: false
if (!isClientSide) return
entity.getEgg()?.let { egg ->
matrices.pushPose() // Start transformation stack
try {
// Render the egg model with the dynamically determined texture
EggRenderHelper.renderEgg(
egg = egg,
state = EggState(),
matrices = matrices,
vertexConsumers = vertexConsumers,
light = light,
overlay = overlay,
yRot = 0f,
scale = 1f,
translation = Triple(0.5f, 0.285f, 0.5f), // Adjust translation
rotation = Triple(180f, 0f, 0f), // Adjust rotation
tickDelta = 0F
)
} finally {
matrices.popPose() // Ensure this is always called
}
}
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.FossilAnalyzerBlockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
import com.mojang.authlib.minecraft.client.MinecraftClient
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
@@ -24,7 +25,6 @@ import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
class FossilAnalyzerRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<FossilAnalyzerBlockEntity> {
override fun render(
entity: FossilAnalyzerBlockEntity,
tickDelta: Float,
@@ -45,7 +45,7 @@ class FossilAnalyzerRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEn
struct.fossilInventory.forEachIndexed { index, fossilStack ->
matrices.pushPose()
val dirOffset = when (direction) {
Direction.NORTH -> Vec3(0.0, 0.0, 0.05)
Direction.SOUTH -> Vec3(0.0, 0.0, -0.05)
@@ -0,0 +1,126 @@
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.block.entity.GrottoBlockEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.LevelRenderer
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.world.level.block.state.BlockState
class GrottoBlockRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<GrottoBlockEntity> {
override fun render(
entity: GrottoBlockEntity,
partialTicks: Float,
poseStack: PoseStack,
bufferSource: MultiBufferSource,
packedLight: Int,
packedOverlay: Int
) {
val stateToRender: BlockState = entity.mimickedState
val level = entity.level ?: return
poseStack.pushPose()
Minecraft.getInstance().blockRenderer.renderBatched(
stateToRender,
entity.blockPos,
level,
poseStack,
bufferSource.getBuffer(RenderType.solid()),
true,
level.random
)
val overlayTexture = cobblemonResource("textures/block/grotto_overlay.png")
val overlayRenderType = RenderType.entityTranslucentCull(overlayTexture)
val overlayBuffer = bufferSource.getBuffer(overlayRenderType)
renderOverlayCube(
poseStack,
overlayBuffer,
packedLight,
OverlayTexture.NO_OVERLAY
)
poseStack.popPose()
}
}
fun renderOverlayCube(
poseStack: PoseStack,
buffer: VertexConsumer,
light: Int,
overlay: Int
) {
val matrix = poseStack.last().pose()
val min = -0.001f
val max = 1.001f
// Front (Z+)
buffer.addVertex(matrix, min, min, max).setColor(255, 255, 255, 128)
.setUv(min, max).setOverlay(overlay).setLight(light).setNormal(min, min, max)
buffer.addVertex(matrix, max, min, max).setColor(255, 255, 255, 128)
.setUv(max, max).setOverlay(overlay).setLight(light).setNormal(min, min, max)
buffer.addVertex(matrix, max, max, max).setColor(255, 255, 255, 128)
.setUv(max, min).setOverlay(overlay).setLight(light).setNormal(min, min, max)
buffer.addVertex(matrix, min, max, max).setColor(255, 255, 255, 128)
.setUv(min, min).setOverlay(overlay).setLight(light).setNormal(min, min, max)
// Back (Z-)
buffer.addVertex(matrix, max, min, min).setColor(255, 255, 255, 128)
.setUv(min, max).setOverlay(overlay).setLight(light).setNormal(min, min, -max)
buffer.addVertex(matrix, min, min, min).setColor(255, 255, 255, 128)
.setUv(max, max).setOverlay(overlay).setLight(light).setNormal(min, min, -max)
buffer.addVertex(matrix, min, max, min).setColor(255, 255, 255, 128)
.setUv(max, min).setOverlay(overlay).setLight(light).setNormal(min, min, -max)
buffer.addVertex(matrix, max, max, min).setColor(255, 255, 255, 128)
.setUv(min, min).setOverlay(overlay).setLight(light).setNormal(min, min, -max)
// Left (X-)
buffer.addVertex(matrix, min, min, min).setColor(255, 255, 255, 128)
.setUv(min, max).setOverlay(overlay).setLight(light).setNormal(-max, min, min)
buffer.addVertex(matrix, min, min, max).setColor(255, 255, 255, 128)
.setUv(max, max).setOverlay(overlay).setLight(light).setNormal(-max, min, min)
buffer.addVertex(matrix, min, max, max).setColor(255, 255, 255, 128)
.setUv(max, min).setOverlay(overlay).setLight(light).setNormal(-max, min, min)
buffer.addVertex(matrix, min, max, min).setColor(255, 255, 255, 128)
.setUv(min, min).setOverlay(overlay).setLight(light).setNormal(-max, min, min)
// Right (X+)
buffer.addVertex(matrix, max, min, max).setColor(255, 255, 255, 128)
.setUv(min, max).setOverlay(overlay).setLight(light).setNormal(max, min, min)
buffer.addVertex(matrix, max, min, min).setColor(255, 255, 255, 128)
.setUv(max, max).setOverlay(overlay).setLight(light).setNormal(max, min, min)
buffer.addVertex(matrix, max, max, min).setColor(255, 255, 255, 128)
.setUv(max, min).setOverlay(overlay).setLight(light).setNormal(max, min, min)
buffer.addVertex(matrix, max, max, max).setColor(255, 255, 255, 128)
.setUv(min, min).setOverlay(overlay).setLight(light).setNormal(max, min, min)
// Top (Y+)
buffer.addVertex(matrix, min, max, max).setColor(255, 255, 255, 128)
.setUv(min, max).setOverlay(overlay).setLight(light).setNormal(min, max, min)
buffer.addVertex(matrix, max, max, max).setColor(255, 255, 255, 128)
.setUv(max, max).setOverlay(overlay).setLight(light).setNormal(min, max, min)
buffer.addVertex(matrix, max, max, min).setColor(255, 255, 255, 128)
.setUv(max, min).setOverlay(overlay).setLight(light).setNormal(min, max, min)
buffer.addVertex(matrix, min, max, min).setColor(255, 255, 255, 128)
.setUv(min, min).setOverlay(overlay).setLight(light).setNormal(min, max, min)
// Bottom (Y-)
buffer.addVertex(matrix, min, min, min).setColor(255, 255, 255, 128)
.setUv(min, max).setOverlay(overlay).setLight(light).setNormal(min, -max, min)
buffer.addVertex(matrix, max, min, min).setColor(255, 255, 255, 128)
.setUv(max, max).setOverlay(overlay).setLight(light).setNormal(min, -max, min)
buffer.addVertex(matrix, max, min, max).setColor(255, 255, 255, 128)
.setUv(max, min).setOverlay(overlay).setLight(light).setNormal(min, -max, min)
buffer.addVertex(matrix, min, min, max).setColor(255, 255, 255, 128)
.setUv(min, min).setOverlay(overlay).setLight(light).setNormal(min, -max, min)
}
@@ -0,0 +1,51 @@
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.block.entity.IncubatorBlockEntity
import com.cobblemon.mod.common.client.render.helper.EggRenderHelper
import com.cobblemon.mod.common.client.render.models.blockbench.TexturedModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.EggModelRepo
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
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.resources.ResourceLocation
class IncubatorRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<IncubatorBlockEntity> {
override fun render(
entity: IncubatorBlockEntity,
tickDelta: Float,
matrices: PoseStack,
vertexConsumers: MultiBufferSource,
light: Int,
overlay: Int
) {
// Ensure the renderer runs only on the client
val isClientSide = entity.level?.isClientSide ?: false
if (!isClientSide) return
// Retrieve the egg data from the entity
val egg = entity.getItem(0).get(CobblemonItemComponents.EGG_DATA)?.egg ?: return
matrices.pushPose()
// Render the egg model with the dynamically determined texture
EggRenderHelper.renderEgg(
egg = egg,
state = entity.eggState,
matrices = matrices,
vertexConsumers = vertexConsumers,
light = light,
overlay = overlay,
yRot = 0f,
scale = 1f,
translation = Triple(0.5f, 0.54f, 0.5f), // Adjust translation
rotation = Triple(180f, 0f, 0f), // Adjust rotation
tickDelta = tickDelta
)
matrices.popPose() // Restore the matrix stack
}
}
@@ -0,0 +1,48 @@
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.client.render.helper.EggRenderHelper
import com.cobblemon.mod.common.client.render.models.blockbench.TexturedModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.EggModelRepo
import com.cobblemon.mod.common.item.components.EggComponent
import com.cobblemon.mod.common.util.cobblemonResource
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.resources.ResourceLocation
class NestBlockRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<NestBlockEntity> {
override fun render(
entity: NestBlockEntity,
tickDelta: Float,
matrices: PoseStack,
vertexConsumers: MultiBufferSource,
light: Int,
overlay: Int
) {
val isClientSide = entity.level?.isClientSide ?: false
if (!isClientSide) return
val egg = entity.getItem().get(CobblemonItemComponents.EGG_DATA)?.egg ?: return
EggRenderHelper.renderEgg(
egg = egg,
state = entity.eggState,
matrices = matrices,
vertexConsumers = vertexConsumers,
light = light,
overlay = overlay,
yRot = 0f,
scale = 1f,
translation = Triple(0.5f, 0.42f, 0.5f),
rotation = Triple(180f, 0f, 0f),
tickDelta = tickDelta
)
}
}
@@ -0,0 +1,23 @@
package com.cobblemon.mod.common.client.render.block
import net.minecraft.client.color.block.BlockColor
import net.minecraft.client.renderer.BiomeColors
import net.minecraft.core.BlockPos
import net.minecraft.world.level.BlockAndTintGetter
import net.minecraft.world.level.GrassColor
import net.minecraft.world.level.block.state.BlockState
object NestLeafBlockColorProvider : BlockColor {
override fun getColor(
blockState: BlockState,
blockAndTintGetter: BlockAndTintGetter?,
blockPos: BlockPos?,
i: Int
): Int {
if (blockAndTintGetter == null || blockPos == null) {
return GrassColor.get(0.5, 1.0)
}
return BiomeColors.getAverageFoliageColor(blockAndTintGetter, blockPos)
}
}
@@ -0,0 +1,146 @@
package com.cobblemon.mod.common.client.render.helper
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.egg.EggModel
import com.cobblemon.mod.common.client.render.models.blockbench.egg.EggState
import com.cobblemon.mod.common.client.render.models.blockbench.fossil.FossilModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.EggModelRepo
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.net.messages.server.breeding.TriggerEggHatchPacket
import com.cobblemon.mod.common.util.cobblemonResource
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.texture.OverlayTexture
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
object EggRenderHelper {
val context = RenderContext().also {
it.put(RenderContext.DO_QUIRKS, true)
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.RESURRECTION_MACHINE) // todo make new one that is false
}
/**
* Renders the given egg with animations based on its time to hatch.
*
* @param egg The egg to render.
* @param matrices The pose stack for transformations.
* @param vertexConsumers The buffer for rendering.
* @param light The light level.
* @param overlay The overlay level.
* @param yRot The rotation angle in degrees around the Y-axis.
* @param scale The scale of the egg model.
* @param translation Adjustments for translation in the form [x, y, z].
* @param rotation Adjustments for rotation in the form [x, y, z].
* @param animTime Current animation time in seconds.
*/
fun renderEgg(
egg: Egg,
state: EggState,
matrices: PoseStack,
vertexConsumers: MultiBufferSource,
light: Int,
overlay: Int,
yRot: Float,
scale: Float = 1.0f,
translation: Triple<Float, Float, Float> = Triple(0.5f, 0.5f, 0.5f),
rotation: Triple<Float, Float, Float> = Triple(0f, 0f, 0f),
tickDelta: Float
) {
// Determine the texture
val form = if (egg.form.lowercase() == "normal") "" else "_${egg.form.lowercase()}"
val texture = cobblemonResource("textures/egg/${egg.species.path}${form}_egg.png")
val eggState = state
eggState.updatePartialTicks(tickDelta)
val eggModel = VaryingModelRepository.getPoser(cobblemonResource("egg"), eggState) as EggModel
eggModel.context = context
val vertexConsumer = vertexConsumers.getBuffer(RenderType.entityCutout(texture))
eggState.currentModel = eggModel
val poseName = when { // todo polish these values
egg.timeToHatch > 9600 -> "stage0" // Above 8 minutes left has No animation
egg.timeToHatch > 2400 -> "stage1" // 8 minutes left
egg.timeToHatch > 1200 -> "stage2" // 2 minutes left
egg.timeToHatch > 20 -> "stage3" // 1 minute left
(egg.timeToHatch == 0F && eggState.isHatching) -> "hatching" // currently trying to hatch....
else -> "stage4" // Ready to hatch!
}
eggState.setPose(poseName)
if (poseName == "hatching" && eggState.primaryAnimation == null) {
val animation = eggModel.getAnimation(eggState, "hatch", eggModel.runtime)
if (animation is com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation) {
eggState.addPrimaryAnimation(animation)
}
}
try {
eggState.primaryAnimation?.animation?.applyEffects(
entity = null,
state = eggState,
previousSeconds = eggState.previousAnimationSeconds,
newSeconds = eggState.animationSeconds
)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to apply animation effects", e)
}
// Trigger hatch packet after animation finishes
if (state.isHatching && !state.hatchingTriggered && state.isHatchAnimationFinished()) {
println("Sending hatch packet for block at ${state.renderingBlockPos}")
state.hatchingTriggered = true
state.renderingBlockPos?.let {
CobblemonNetwork.sendToServer(TriggerEggHatchPacket(it))
}
eggState.resetHatchingState()
}
matrices.pushPose()
matrices.translate(translation.first, translation.second, translation.third)
matrices.mulPose(com.mojang.math.Axis.XP.rotationDegrees(rotation.first))
matrices.mulPose(com.mojang.math.Axis.YP.rotationDegrees(-yRot + rotation.second))
matrices.mulPose(com.mojang.math.Axis.ZP.rotationDegrees(rotation.third))
matrices.scale(scale, scale, scale)
context.put(RenderContext.TEXTURE, texture)
context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.RESURRECTION_MACHINE)
context.put(RenderContext.POSABLE_STATE, eggState)
matrices.pushPose()
eggModel.applyAnimations(
entity = null,
state = eggState,
headYaw = 0F,
headPitch = 0F,
limbSwing = 0F,
limbSwingAmount = 0F,
ageInTicks = eggState.animationSeconds * 20
)
eggModel.render(context, matrices, vertexConsumer, light, overlay, -0x1)
eggModel.withLayerContext(vertexConsumers, eggState, VaryingModelRepository.getLayers(cobblemonResource("egg"), eggState)) {
eggModel.render(context, matrices, vertexConsumer, light, OverlayTexture.NO_OVERLAY, -0x1)
}
eggModel.setDefault()
matrices.popPose()
matrices.popPose()
}
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.render.item
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.item.interactive.PokerodItem
import net.minecraft.client.renderer.item.ItemProperties
@@ -20,6 +21,7 @@ object CobblemonModelPredicateRegistry {
fun registerPredicates() {
val rods = listOf(
CobblemonItems.AZURE_ROD,
CobblemonItems.BEAST_ROD,
CobblemonItems.CHERISH_ROD,
@@ -90,7 +92,13 @@ object CobblemonModelPredicateRegistry {
}
}
ItemProperties.register(CobblemonItems.POKEMON_EGG, ResourceLocation.parse("species")) { stack, world, entity, seed ->
val eggComponent = stack.get(CobblemonItemComponents.EGG_DATA) ?: return@register 0.0f
val egg = eggComponent.egg ?: return@register 0.0f
val form = if (egg.form.lowercase() == "normal") "" else "_${egg.form.lowercase()}"
val modelId = PokemonEggItemModelRegistry.getModelId(egg.species.path + form)
//println("Evaluating species predicate for ${egg.species.path} -> $modelId")
return@register modelId
}
}
}
@@ -0,0 +1,613 @@
package com.cobblemon.mod.common.client.render.item
object PokemonEggItemModelRegistry {
private val speciesModelMap = mapOf(
// gen 1
"bulbasaur" to 0.0001f,
"charmander" to 0.0002f,
"squirtle" to 0.0003f,
"caterpie" to 0.0004f,
"weedle" to 0.0005f,
"pidgey" to 0.0006f,
"rattata" to 0.0007f,
"rattata_alola" to 0.0008f,
"spearow" to 0.0009f,
"ekans" to 0.0010f,
"sandshrew" to 0.0011f,
"sandshrew_alola" to 0.0012f,
"nidoranm" to 0.0013f,
"nidoranf" to 0.0014f,
"vulpix" to 0.0015f,
"vulpix_alola" to 0.0016f,
"zubat" to 0.0017f,
"oddish" to 0.0018f,
"paras" to 0.0019f,
"venonat" to 0.0020f,
"diglett" to 0.0021f,
"diglett_alola" to 0.0022f,
"meowth" to 0.0023f,
"meowth_alola" to 0.0024f,
"meowth_galar" to 0.0025f,
"psyduck" to 0.0026f,
"mankey" to 0.0027f,
"growlithe" to 0.0028f,
"growlithe_hisui" to 0.0029f,
"poliwag" to 0.0030f,
"abra" to 0.0031f,
"machop" to 0.0032f,
"bellsprout" to 0.0033f,
"tentacool" to 0.0034f,
"geodude" to 0.0035f,
"geodude_alola" to 0.0036f,
"ponyta" to 0.0037f,
"ponyta_galar" to 0.0038f,
"slowpoke" to 0.0039f,
"slowpoke_galar" to 0.0040f,
"magnemite" to 0.0041f,
"farfetchd" to 0.0042f,
"farfetchd_galar" to 0.0043f,
"doduo" to 0.0044f,
"seel" to 0.0045f,
"grimer" to 0.0046f,
"grimer_alola" to 0.0047f,
"shellder" to 0.0048f,
"gastly" to 0.0049f,
"onix" to 0.0050f,
"drowzee" to 0.0051f,
"krabby" to 0.0052f,
"voltorb" to 0.0053f,
"voltorb_hisui" to 0.0054f,
"exeggcute" to 0.0055f,
"cubone" to 0.0056f,
"lickitung" to 0.0057f,
"koffing" to 0.0058f,
"rhyhorn" to 0.0059f,
"tangela" to 0.0060f,
"kangaskhan" to 0.0061f,
"horsea" to 0.0062f,
"goldeen" to 0.0063f,
"staryu" to 0.0064f,
"scyther" to 0.0065f,
"pinsir" to 0.0066f,
"tauros" to 0.0067f,
"tauros_paldea" to 0.0068f,
"magikarp" to 0.0069f,
"lapras" to 0.0070f,
"ditto" to 0.0071f,
"eevee" to 0.0072f,
"porygon" to 0.0073f,
"omanyte" to 0.0074f,
"kabuto" to 0.0075f,
"aerodactyl" to 0.0076f,
"articuno" to 0.0077f,
"articuno_galar" to 0.0078f,
"zapdos" to 0.0079f,
"zapdos_galar" to 0.0080f,
"moltres" to 0.0081f,
"moltres_galar" to 0.0082f,
"dratini" to 0.0083f,
"mewtwo" to 0.0084f,
"mew" to 0.0085f,
// gen 2
"chikorita" to 0.0086f,
"cyndaquil" to 0.0087f,
"totodile" to 0.0088f,
"sentret" to 0.0089f,
"hoothoot" to 0.0090f,
"ledyba" to 0.0091f,
"spinarak" to 0.0092f,
"chinchou" to 0.0093f,
"pichu" to 0.0094f,
"cleffa" to 0.0095f,
"igglybuff" to 0.0096f,
"togepi" to 0.0097f,
"natu" to 0.0098f,
"mareep" to 0.0099f,
"hoppip" to 0.0100f,
"aipom" to 0.0101f,
"sunkern" to 0.0102f,
"yanma" to 0.0103f,
"wooper" to 0.0104f,
"wooper_paldea" to 0.0105f,
"murkrow" to 0.0106f,
"misdreavus" to 0.0107f,
"unown" to 0.0108f,
"girafarig" to 0.0109f,
"pineco" to 0.0110f,
"dunsparce" to 0.0111f,
"gligar" to 0.0112f,
"snubbull" to 0.0113f,
"qwilfish" to 0.0114f,
"qwilfish_hisui" to 0.0115f,
"shuckle" to 0.0116f,
"heracross" to 0.0117f,
"sneasel" to 0.0118f,
"sneasel_hisui" to 0.0119f,
"teddiursa" to 0.0120f,
"slugma" to 0.0121f,
"swinub" to 0.0122f,
"corsola" to 0.0123f,
"corsola_galar" to 0.0124f,
"remoraid" to 0.0125f,
"delibird" to 0.0126f,
"skarmory" to 0.0127f,
"houndour" to 0.0128f,
"phanpy" to 0.0129f,
"stantler" to 0.0130f,
"smeargle" to 0.0131f,
"tyrogue" to 0.0132f,
"smoochum" to 0.0133f,
"elekid" to 0.0134f,
"magby" to 0.0135f,
"miltank" to 0.0136f,
"raikou" to 0.0137f,
"entei" to 0.0138f,
"suicune" to 0.0139f,
"larvitar" to 0.0140f,
"lugia" to 0.0141f,
"hooh" to 0.0142f,
"celebi" to 0.0143f,
// gen 3
"treecko" to 0.0144f,
"torchic" to 0.0145f,
"mudkip" to 0.0146f,
"poochyena" to 0.0147f,
"zigzagoon" to 0.0148f,
"zigzagoon_galar" to 0.0149f,
"wurmple" to 0.0150f,
"lotad" to 0.0151f,
"seedot" to 0.0152f,
"taillow" to 0.0153f,
"wingull" to 0.0154f,
"ralts" to 0.0155f,
"surskit" to 0.0156f,
"shroomish" to 0.0157f,
"slakoth" to 0.0158f,
"nincada" to 0.0159f,
"whismur" to 0.0160f,
"makuhita" to 0.0161f,
"azurill" to 0.0162f,
"nosepass" to 0.0163f,
"skitty" to 0.0164f,
"sableye" to 0.0165f,
"mawile" to 0.0166f,
"aron" to 0.0167f,
"meditite" to 0.0168f,
"electrike" to 0.0169f,
"plusle" to 0.0170f,
"minun" to 0.0171f,
"volbeat" to 0.0172f,
"illumise" to 0.0173f,
"roselia" to 0.0174f,
"gulpin" to 0.0175f,
"carvanha" to 0.0176f,
"wailmer" to 0.0177f,
"numel" to 0.0178f,
"torkoal" to 0.0179f,
"spoink" to 0.0180f,
"spinda" to 0.0181f,
"trapinch" to 0.0182f,
"cacnea" to 0.0183f,
"swablu" to 0.0184f,
"zangoose" to 0.0185f,
"seviper" to 0.0186f,
"lunatone" to 0.0187f,
"solrock" to 0.0188f,
"barboach" to 0.0189f,
"corphish" to 0.0190f,
"baltoy" to 0.0191f,
"lileep" to 0.0192f,
"anorith" to 0.0193f,
"feebas" to 0.0194f,
"castform" to 0.0195f,
"kecleon" to 0.0196f,
"shuppet" to 0.0197f,
"duskull" to 0.0198f,
"tropius" to 0.0199f,
"chimecho" to 0.0200f,
"absol" to 0.0201f,
"wynaut" to 0.0202f,
"snorunt" to 0.0203f,
"spheal" to 0.0204f,
"clamperl" to 0.0205f,
"relicanth" to 0.0206f,
"luvdisc" to 0.0207f,
"bagon" to 0.0208f,
"beldum" to 0.0209f,
"regirock" to 0.0210f,
"regice" to 0.0211f,
"registeel" to 0.0212f,
"latias" to 0.0213f,
"latios" to 0.0214f,
"kyogre" to 0.0215f,
"groudon" to 0.0216f,
"rayquaza" to 0.0217f,
"jirachi" to 0.0218f,
"deoxys" to 0.0219f,
// gen 4
"turtwig" to 0.0220f,
"chimchar" to 0.0221f,
"piplup" to 0.0222f,
"starly" to 0.0223f,
"bidoof" to 0.0224f,
"kricketot" to 0.0225f,
"shinx" to 0.0226f,
"budew" to 0.0227f,
"cranidos" to 0.0228f,
"shieldon" to 0.0229f,
"burmy" to 0.0230f,
"combee" to 0.0231f,
"pachirisu" to 0.0232f,
"buizel" to 0.0233f,
"cherubi" to 0.0234f,
"shellos" to 0.0235f,
"shellos_east" to 0.0236f,
"drifloon" to 0.0237f,
"buneary" to 0.0238f,
"glameow" to 0.0239f,
"chingling" to 0.0240f,
"stunky" to 0.0241f,
"bronzor" to 0.0242f,
"bonsly" to 0.0243f,
"mimejr" to 0.0244f,
"happiny" to 0.0245f,
"chatot" to 0.0246f,
"spiritomb" to 0.0247f,
"gible" to 0.0248f,
"munchlax" to 0.0249f,
"riolu" to 0.0250f,
"hippopotas" to 0.0251f,
"skorupi" to 0.0252f,
"croagunk" to 0.0253f,
"carnivine" to 0.0254f,
"finneon" to 0.0255f,
"mantyke" to 0.0256f,
"snover" to 0.0257f,
"rotom" to 0.0258f,
"uxie" to 0.0259f,
"mesprit" to 0.0260f,
"azelf" to 0.0261f,
"dialga" to 0.0262f,
"palkia" to 0.0263f,
"heatran" to 0.0264f,
"regigigas" to 0.0265f,
"giratina" to 0.0266f,
"cresselia" to 0.0267f,
"phione" to 0.0268f,
"manaphy" to 0.0269f,
"darkrai" to 0.0270f,
"shaymin" to 0.0271f,
"arceus" to 0.0272f,
"victini" to 0.0273f,
// gen 5
"snivy" to 0.0274f,
"tepig" to 0.0275f,
"oshawott" to 0.0276f,
"patrat" to 0.0277f,
"lillipup" to 0.0278f,
"purrloin" to 0.0279f,
"pansage" to 0.0280f,
"pansear" to 0.0281f,
"panpour" to 0.0282f,
"munna" to 0.0283f,
"pidove" to 0.0284f,
"blitzle" to 0.0285f,
"roggenrola" to 0.0286f,
"woobat" to 0.0287f,
"drilbur" to 0.0288f,
"audino" to 0.0289f,
"timburr" to 0.0290f,
"tympole" to 0.0291f,
"throh" to 0.0292f,
"sawk" to 0.0293f,
"sewaddle" to 0.0294f,
"venipede" to 0.0295f,
"cottonee" to 0.0296f,
"petilil" to 0.0297f,
"basculin" to 0.0298f,
"sandile" to 0.0299f,
"darumaka" to 0.0300f,
"darumaka_galar" to 0.0301f,
"maractus" to 0.0302f,
"dwebble" to 0.0303f,
"scraggy" to 0.0304f,
"sigilyph" to 0.0305f,
"yamask" to 0.0306f,
"yamask_galar" to 0.0307f,
"tirtouga" to 0.0308f,
"archen" to 0.0309f,
"trubbish" to 0.0310f,
"zorua" to 0.0311f,
"zorua_hisui" to 0.0312f,
"minccino" to 0.0313f,
"gothita" to 0.0314f,
"solosis" to 0.0315f,
"ducklett" to 0.0316f,
"vanillite" to 0.0317f,
"deerling" to 0.0318f,
"emolga" to 0.0319f,
"karrablast" to 0.0320f,
"foongus" to 0.0321f,
"frillish" to 0.0322f,
"alomomola" to 0.0323f,
"joltik" to 0.0324f,
"ferroseed" to 0.0325f,
"klink" to 0.0326f,
"tynamo" to 0.0327f,
"elgyem" to 0.0328f,
"litwick" to 0.0329f,
"axew" to 0.0330f,
"cubchoo" to 0.0331f,
"cryogonal" to 0.0332f,
"shelmet" to 0.0333f,
"stunfisk" to 0.0334f,
"stunfisk_galar" to 0.0335f,
"mienfoo" to 0.0336f,
"druddigon" to 0.0337f,
"golett" to 0.0338f,
"pawniard" to 0.0339f,
"bouffalant" to 0.0340f,
"rufflet" to 0.0341f,
"vullaby" to 0.0342f,
"heatmor" to 0.0343f,
"durant" to 0.0344f,
"deino" to 0.0345f,
"larvesta" to 0.0346f,
"cobalion" to 0.0347f,
"terrakion" to 0.0348f,
"virizion" to 0.0349f,
"tornadus" to 0.0350f,
"thundurus" to 0.0351f,
"reshiram" to 0.0352f,
"zekrom" to 0.0353f,
"landorus" to 0.0354f,
"kyurem" to 0.0355f,
"keldeo" to 0.0356f,
"meloetta" to 0.0357f,
"genesect" to 0.0358f,
// gen 6
"chespin" to 0.0359f,
"fennekin" to 0.0360f,
"froakie" to 0.0361f,
"bunnelby" to 0.0362f,
"fletchling" to 0.0363f,
"scatterbug" to 0.0364f,
"litleo" to 0.0365f,
"flabebe" to 0.0366f,
"skiddo" to 0.0367f,
"pancham" to 0.0368f,
"furfrou" to 0.0369f,
"espurr" to 0.0370f,
"honedge" to 0.0371f,
"spritzee" to 0.0372f,
"swirlix" to 0.0373f,
"inkay" to 0.0374f,
"binacle" to 0.0375f,
"skrelp" to 0.0376f,
"clauncher" to 0.0377f,
"helioptile" to 0.0378f,
"tyrunt" to 0.0379f,
"amaura" to 0.0380f,
"hawlucha" to 0.0381f,
"dedenne" to 0.0382f,
"carbink" to 0.0383f,
"goomy" to 0.0384f,
"klefki" to 0.0385f,
"phantump" to 0.0386f,
"pumpkaboo" to 0.0387f,
"bergmite" to 0.0388f,
"noibat" to 0.0389f,
"xerneas" to 0.0390f,
"yveltal" to 0.0391f,
"zygarde" to 0.0392f,
"diancie" to 0.0393f,
"hoopa" to 0.0394f,
"volcanion" to 0.0395f,
// gen 7
"rowlet" to 0.0396f,
"litten" to 0.0397f,
"popplio" to 0.0398f,
"pikipek" to 0.0399f,
"yungoos" to 0.0400f,
"grubbin" to 0.0401f,
"crabrawler" to 0.0402f,
"oricorio" to 0.0403f,
"oricorio_pompom" to 0.0404f,
"oricorio_pau" to 0.0405f,
"oricorio_sensu" to 0.0406f,
"cutiefly" to 0.0407f,
"rockruff" to 0.0408f,
"wishiwashi" to 0.0409f,
"mareanie" to 0.0410f,
"mudbray" to 0.0411f,
"dewpider" to 0.0412f,
"fomantis" to 0.0413f,
"morelull" to 0.0414f,
"salandit" to 0.0415f,
"stufful" to 0.0416f,
"bounsweet" to 0.0417f,
"comfey" to 0.0418f,
"oranguru" to 0.0419f,
"passimian" to 0.0420f,
"wimpod" to 0.0421f,
"sandygast" to 0.0422f,
"pyukumuku" to 0.0423f,
"typenull" to 0.0424f,
"minior" to 0.0425f,
"komala" to 0.0426f,
"turtonator" to 0.0427f,
"togedemaru" to 0.0428f,
"mimikyu" to 0.0429f,
"bruxish" to 0.0430f,
"drampa" to 0.0431f,
"dhelmise" to 0.0432f,
"jangmoo" to 0.0433f,
"tapukoko" to 0.0434f,
"tapulele" to 0.0435f,
"tapubulu" to 0.0436f,
"tapufini" to 0.0437f,
"cosmog" to 0.0438f,
"nihilego" to 0.0439f,
"buzzwole" to 0.0440f,
"pheromosa" to 0.0441f,
"xurkitree" to 0.0442f,
"celesteela" to 0.0443f,
"kartana" to 0.0444f,
"guzzlord" to 0.0445f,
"necrozma" to 0.0446f,
"magearna" to 0.0447f,
"marshadow" to 0.0448f,
"poipole" to 0.0449f,
"stakataka" to 0.0450f,
"blacephalon" to 0.0451f,
"zeraora" to 0.0452f,
"meltan" to 0.0453f,
// gen 8
"grookey" to 0.0454f,
"scorbunny" to 0.0455f,
"sobble" to 0.0456f,
"skwovet" to 0.0457f,
"rookidee" to 0.0458f,
"blipbug" to 0.0459f,
"nickit" to 0.0460f,
"gossifleur" to 0.0461f,
"wooloo" to 0.0462f,
"chewtle" to 0.0463f,
"yamper" to 0.0464f,
"rolycoly" to 0.0465f,
"applin" to 0.0466f,
"silicobra" to 0.0467f,
"cramorant" to 0.0468f,
"arrokuda" to 0.0469f,
"toxel" to 0.0470f,
"sizzlipede" to 0.0471f,
"clobbopus" to 0.0472f,
"sinistea" to 0.0473f,
"hatenna" to 0.0474f,
"impidimp" to 0.0475f,
"milcery" to 0.0476f,
"falinks" to 0.0477f,
"pincurchin" to 0.0478f,
"snom" to 0.0479f,
"stonjourner" to 0.0480f,
"eiscue" to 0.0481f,
"indeedee" to 0.0482f,
"morpeko" to 0.0483f,
"cufant" to 0.0484f,
"dracozolt" to 0.0485f,
"arctozolt" to 0.0486f,
"dracovish" to 0.0487f,
"arctovish" to 0.0488f,
"duraludon" to 0.0489f,
"dreepy" to 0.0490f,
"zacian" to 0.0491f,
"zamazenta" to 0.0492f,
"eternatus" to 0.0493f,
"kubfu" to 0.0494f,
"zarude" to 0.0495f,
"regieleki" to 0.0496f,
"regidrago" to 0.0497f,
"glastrier" to 0.0498f,
"spectrier" to 0.0499f,
"calyrex" to 0.0500f,
"enamorus" to 0.0501f,
// gen 9
"sprigatito" to 0.0502f,
"fuecoco" to 0.0503f,
"quaxly" to 0.0504f,
"lechonk" to 0.0505f,
"tarountula" to 0.0506f,
"nymble" to 0.0507f,
"pawmi" to 0.0508f,
"tandemaus" to 0.0509f,
"fidough" to 0.0510f,
"smoliv" to 0.0511f,
"squawkabilly" to 0.0512f,
"squawkabilly_blue" to 0.0513f,
"squawkabilly_yellow" to 0.0514f,
"squawkabilly_white" to 0.0515f,
"nacli" to 0.0516f,
"charcadet" to 0.0517f,
"tadbulb" to 0.0518f,
"wattrel" to 0.0519f,
"maschiff" to 0.0520f,
"shroodle" to 0.0521f,
"bramblin" to 0.0522f,
"toedscool" to 0.0523f,
"klawf" to 0.0524f,
"capsakid" to 0.0525f,
"rellor" to 0.0526f,
"flittle" to 0.0527f,
"tinkatink" to 0.0528f,
"wiglett" to 0.0529f,
"bombirdier" to 0.0530f,
"finizen" to 0.0531f,
"varoom" to 0.0532f,
"cyclizar" to 0.0533f,
"orthworm" to 0.0534f,
"glimmet" to 0.0535f,
"greavard" to 0.0536f,
"flamigo" to 0.0537f,
"cetoddle" to 0.0538f,
"veluza" to 0.0539f,
"dondozo" to 0.0540f,
"tatsugiri" to 0.0541f,
"tatsugiri_droopy" to 0.0542f,
"tatsugiri_stretchy" to 0.0543f,
"greattusk" to 0.0544f,
"screamtail" to 0.0545f,
"brutebonnet" to 0.0546f,
"fluttermane" to 0.0547f,
"slitherwing" to 0.0548f,
"sandyshocks" to 0.0549f,
"irontreads" to 0.0550f,
"ironbundle" to 0.0551f,
"ironhands" to 0.0552f,
"ironjugulis" to 0.0553f,
"ironmoth" to 0.0554f,
"ironthorns" to 0.0555f,
"frigibax" to 0.0556f,
"gimmighoul" to 0.0557f,
"wochien" to 0.0558f,
"chienpao" to 0.0559f,
"tinglu" to 0.0560f,
"chiyu" to 0.0561f,
"roaringmoon" to 0.0562f,
"ironvaliant" to 0.0563f,
"koraidon" to 0.0564f,
"miraidon" to 0.0565f,
"walkingwake" to 0.0566f,
"ironleaves" to 0.0567f,
"poltchageist" to 0.0568f,
"okidogi" to 0.0569f,
"munkidori" to 0.0570f,
"fezandipiti" to 0.0571f,
"ogerpon" to 0.0572f,
"gougingfire" to 0.0573f,
"ragingbolt" to 0.0574f,
"ironboulder" to 0.0575f,
"ironcrown" to 0.0576f,
"terapagos" to 0.0577f,
"pecharunt" to 0.0578f
)
/**
* Gets the model ID for a Pokémon species.
*
* @param species The species path string.
* @return The model ID, or 0.0f if the species is not found.
*/
fun getModelId(species: String): Float {
val modelId = speciesModelMap[species] ?: 0.0f
//println("Getting model ID for species: $species at ID: $modelId")
return modelId
}
}
@@ -18,6 +18,7 @@ import java.util.function.Function
import net.minecraft.Util
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderStateShard.*
import net.minecraft.client.renderer.entity.layers.RenderLayer
object CobblemonRenderLayers {
val BERRY_LAYER = run {
@@ -85,5 +86,49 @@ object CobblemonRenderLayers {
multiPhaseParameters
)
};
val EGG_LAYER = run {
val multiPhaseParameters = RenderType.CompositeState.builder()
.setLightmapState(LIGHTMAP)
.setShaderState(RENDERTYPE_CUTOUT_SHADER)
.setTextureState(RenderStateShard.TextureStateShard(
cobblemonResource("textures/atlas/egg_patterns.png"),
false,
true
))
.setCullState(RenderStateShard.NO_CULL)
.createCompositeState(true)
RenderType.create(
"egg_patterns",
DefaultVertexFormat.BLOCK,
VertexFormat.Mode.QUADS,
512,
true,
false,
multiPhaseParameters
)
}
//For overlaying the 2D sprites for eggs in inventory
val EGG_SPRITE_LAYER = run {
val multiPhaseParameters = RenderType.CompositeState.builder()
.setShaderState(POSITION_COLOR_TEX_LIGHTMAP_SHADER)
.setTextureState(RenderStateShard.TextureStateShard(
cobblemonResource("textures/atlas/egg_pattern_sprites.png"),
false,
false
))
.setCullState(RenderStateShard.NO_CULL)
.createCompositeState(true)
RenderType.create(
"egg_patterns",
DefaultVertexFormat.POSITION_TEX_COLOR,
VertexFormat.Mode.QUADS,
512,
true,
false,
multiPhaseParameters
)
}
}

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