Compare commits

...
Author SHA1 Message Date
plastered_crab d287ce3612 Slow down particle emission and tweak scaling formuoli 2026-04-22 20:16:36 -07:00
plastered_crab 95b4dfcf43 Fix Habitat Block being a pesky boi for face culling of neighbor blocks 2026-04-22 00:06:00 -07:00
dhi_holo 03ca81abdf Visual Model Fixes 2026-04-22 13:45:27 +10:00
Plastered_Crab 7f117fae07 Add WIP spinning hell within Habitat block for the currently selected spawn pool 2026-04-21 18:29:28 -07:00
Plastered_Crab 0c90497abc Allow Habitat Block to use Cutout render type when not mimicing other blocks and fix some block types like Grass Block 2026-04-21 16:52:52 -07:00
Plastered_Crab 449461e0b6 Add obsidian explosion resistance to Habitat Block 2026-04-21 14:21:51 -07:00
Plastered_Crab 506214f858 Move the langs in en_us for the habitat block 2026-04-21 13:52:25 -07:00
Plastered_Crab 7ed7d23ed0 Add input validation and slight UI changes to Habitat Block 2026-04-21 13:38:53 -07:00
Paul 0cb367e7cb Render translucency on the habitat block 2026-04-19 20:48:17 +10:00
dhi_holo 05348c1f73 Habitat Block Models, Blockstates, and Texture
Begone, terrifying void monster!
2026-04-19 19:59:41 +10:00
Paul 80716f72b5 Properly update blockstate changes for habitat block I think 2026-04-19 18:40:30 +10:00
Paul 6693e8c66c Blockstate properties for habitat block (facing, activated_style [true/false], cancels_regular_spawns [true/false]) 2026-04-19 16:20:00 +10:00
Paul 5873cb203f Added habitat_mimics block tag and changed habitat disguise into a text input 2026-04-18 15:08:35 +10:00
Paul aa498450b5 Yet more work on temporary habitat block GUI 2026-02-23 23:12:14 +10:00
Paul e77124369e Some more work on temporary habitat block GUI 2026-02-23 14:13:37 +10:00
Paul 1aaf0d4be6 Some work on temporary habitat block GUI 2026-02-23 13:44:36 +10:00
Paul 1d496bee5b Implemented phase order for habitat blocks 2026-02-23 13:23:34 +10:00
Paul 244e1c0434 GUI instructions for habitat block editor 2026-02-22 21:13:37 +10:00
Paul 6d0e8dfbe5 Kill unused class 2026-02-22 21:01:06 +10:00
Paul a05380f95b Functional habitat blocks with packets for modifying them 2026-02-22 20:56:28 +10:00
Paul 283ff1ac8f Part one of new habitat blocks, data side 2026-02-14 22:21:40 +10:00
Plastered_Crab bd1628a104 Merge branch 'bigger-brains' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/biggerbrain/sense/Sense.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/objective/ObjectiveConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/sense/SenseConfig.kt
2026-02-11 12:51:20 -08:00
Plastered_Crab 49ba657f1c Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding 2026-02-11 12:40:03 -08:00
Plastered_Crab e4ccfd4dba Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/resources/data/cobblemon/species/generation1/mrmime.json
2026-02-11 12:36:17 -08:00
JazzMcNade 11452de369 class name typo 2026-02-09 01:35:25 -06:00
JazzMcNade fb4e5002a3 Migrate sweet berry bush harvesting 2026-02-08 21:44:28 -06:00
JazzMcNade 6dd7a7bc9a respect distraction rules 2026-02-08 13:30:52 -06:00
JazzMcNade ba718561aa MoveToRestingPlace Correction 2026-02-08 02:43:35 -06:00
Paul 4ae1b84798 Tiny formatting things 2026-02-08 11:20:51 +10:00
JazzMcNade 21411d26ac More clean up, added moveToRestingPlace Objective 2026-02-03 00:49:14 -06:00
Plastered_Crab 02b45e8b55 Push all the ai changes for the new system 2026-01-30 14:57:56 -08:00
JazzMcNade 3f6c5ebc96 Sleep blocks movement, Added comfort distractions, Removed null check from StayAfloat 2026-01-29 23:25:12 -06:00
Plastered_Crab d217dfee54 Merge remote-tracking branch 'origin/bigger-brains' into 1.21.1-breeding-bigger-brains
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonMemories.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/ai/OmniPathNavigation.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/ai/PokemonBrain.kt
#	common/src/main/resources/data/cobblemon/behaviours/pokemon/auto/pokemon_core.json
2026-01-29 14:58:34 -08:00
Plastered_Crab 9702cdebeb Add better placeholder textures and models for testing new Nest Variant system 2026-01-29 08:36:43 -08:00
Plastered_Crab dc6e2fae2e Alter and fix World Gen Nests to work with new ones 2026-01-28 10:32:49 -08:00
Plastered_Crab 2fec360805 Delete old nest models and textures 2026-01-28 10:31:48 -08:00
Plastered_Crab 5627233d3d Cleanup block registries for nests 2026-01-28 10:30:52 -08:00
Plastered_Crab 766328874d Remove old nests and cleanup files 2026-01-28 10:30:20 -08:00
Plastered_Crab 953e7cb7ff Add new Nests and Nest Variants 2026-01-27 16:51:46 -08:00
Plastered_Crab 01ce59c8c4 Alter Cobblemon Sensors and Memories 2026-01-27 16:50:48 -08:00
Plastered_Crab 7468b180e2 Add Lang 2026-01-27 16:50:16 -08:00
Plastered_Crab cad0a9490d Add Nest Types variable to species 2026-01-27 16:50:03 -08:00
Plastered_Crab a6e046e405 Alter Nest Creation Task Configs and old Pokemon Brain 2026-01-27 16:48:47 -08:00
Plastered_Crab fe3a893afc Kill old nest material gathering task configs 2026-01-27 16:47:38 -08:00
Plastered_Crab 5c9530e10e Alter Tasks and Sensors for nest building 2026-01-27 16:45:40 -08:00
Plastered_Crab 5c45cf595c Kill old nesting material sensor 2026-01-27 16:45:11 -08:00
Plastered_Crab 378f5434f7 Kill Old tasks for pokemon gathering nesting materials 2026-01-27 16:44:46 -08:00
Plastered_Crab 64f32bf4b7 Add stupid stinky temporary Nest Art Assets 2026-01-27 16:43:41 -08:00
Plastered_Crab 66ec46dcc6 Add stupid stinky temporary Nest Art Assets 2026-01-27 16:43:16 -08:00
Plastered_Crab 1c3d027877 Add BlockStates files for new Nests and their variants 2026-01-27 16:41:35 -08:00
Plastered_Crab 99cf6b8a65 Add JSON tags for the blocks that change the nest types into specific variants 2026-01-27 16:40:21 -08:00
Plastered_Crab 1025a583d9 Add JSON tag lists for blocks certain types of nests can be created on by pokemon 2026-01-27 16:39:08 -08:00
Plastered_Crab 749303f640 Scrape Nest Types onto all Species of pokemon from Behavior Spreadsheet 2026-01-27 16:37:54 -08:00
Plastered_Crab fa80edf178 Remove Material Gathering tasks from Breeding behavior 2026-01-27 16:27:15 -08:00
Plastered_Crab 80f31a13d8 Alter Behavior Scraper to pull Nest Types for each species 2026-01-27 09:53:43 -08:00
Plastered_Crab 62ae8285e2 Fix SpawnPokemonPacket 2026-01-26 14:17:51 -08:00
Plastered_Crab cba807be81 Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/battles/actor/MultiPokemonBattleActor.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/DisplayCaseRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
2026-01-26 10:01:52 -08:00
JazzMcNade 0bbdfd2c5e Added Sleeping 2026-01-26 02:28:20 -06:00
JazzMcNade f43b7d3dd7 Cleanup 2026-01-25 20:35:28 -06:00
JazzMcNade 6291ac0e23 Added MoveIntoWaterObjective 2026-01-25 02:50:46 -06:00
JazzMcNade 4f528c855b Created MoveOutOfWater Objective 2026-01-24 23:08:21 -06:00
Paul 29c291fd7a Minor doc change [skip-ci] 2026-01-18 13:28:13 +10:00
Paul 493f71302e Some brain clarification stuff and some docs for adding to the brains 2026-01-18 13:20:03 +10:00
Paul ab5a54f21e Fixed excessive wander chances 2026-01-17 18:25:59 +10:00
Paul 440e2855c0 Swimmers can tread water when not pathing on new brain 2026-01-17 16:43:44 +10:00
Paul 8ebecc4928 Migrated stay afloat task to new brain 2026-01-17 11:24:13 +10:00
Paul 4f0108c8a2 Replaced busy locks with fixations that implement LockFixation 2026-01-16 22:16:54 +10:00
Paul 32cd79baec Improved wandering so they can look around and (rarely) wander while in a herd, fixed look targeting for wandering 2026-01-15 22:25:19 +10:00
Plastered_Crab 6df0173cbe Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/PokemonPastureBlockEntity.kt
2026-01-14 10:36:52 -08:00
Paul a8ac858918 Migrated herding to bigger brain 2026-01-14 21:48:20 +10:00
Paul d46394cb1d Migrated sheep grass eating to bigger brains 2026-01-09 22:44:45 +10:00
Paul d5cc2de7e5 I want a license to kill this pipeline error 2026-01-03 17:03:01 +10:00
Paul a5088cf264 Migrated objective configs into the objective classes, moved OT type class out of the Pokemon file 2026-01-03 16:18:20 +10:00
Paul 6c5fd6ea1c Added draft of new AI system 2025-12-31 23:00:10 +10:00
plastered_crab 6b05ab19d4 Merge branch 'main' into 1.21.1-breeding 2025-12-28 14:36:34 -08:00
plastered_crab ad995ad33f Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
2025-12-15 10:33:49 -08:00
Paul 1a265fbe9a Compilation error fixes from merge 2025-12-05 16:52:26 +10:00
Paul 11b47e9c77 Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonActivities.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.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/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/nest/NestSpawner.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/position/NestSpawnablePosition.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/spawner/GrottoSpawner.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/NestBlockEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/EntityExtensions.kt
2025-12-05 16:49:02 +10:00
Paul bab7a5e011 merge conflict stuff 2025-12-05 16:47:05 +10:00
plastered_crab 0fca9b17ed Bye bye nest spawner 2025-12-02 14:35:13 -08:00
plastered_crab f648aa44a0 Merge main with Breeding branch! Yipeeeee (Hiro I need your help with the grotto spawner tho) 2025-12-02 11:26:01 -08:00
Paul 9b8663cb82 Merge remote-tracking branch 'origin/main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonActivities.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/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/keybind/keybinds/DebugKeybindings.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/EntityExtensions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/world/feature/CobblemonPlacedFeatures.kt
2025-11-24 19:40:22 +10:00
plastered_crab 0ff5ef70c5 Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonActivities.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/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/keybind/keybinds/DebugKeybindings.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/EntityExtensions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/world/feature/CobblemonPlacedFeatures.kt
2025-11-21 19:54:04 -08:00
plastered_crab b978a4447b Merge remote-tracking branch 'origin/1.21.1-breeding' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
2025-11-21 19:50:23 -08:00
Paul 9bd52006c6 Update from main 2025-10-24 22:26:03 +10:00
Paul f66c782e22 Merge remote-tracking branch 'origin/main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonMemories.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
2025-10-24 22:06:05 +10:00
plastered_crab 3f75e5307f Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
2025-10-06 11:15:11 -07:00
plastered_crab 7c7488910d Fix some conflicts 2025-09-22 11:23:28 -07:00
plastered_crab 6ef1dc29ae Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
#	common/src/main/resources/cobblemon-common.accesswidener
2025-09-22 11:10:45 -07:00
plastered_crab e8376ed1c9 why do i even have that lever 2025-09-08 22:08:44 -07:00
plastered_crab 538d186488 Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/BerryBlockRenderer.kt
#	common/src/main/resources/assets/cobblemon/particles/snowstorm.json
2025-09-08 21:54:01 -07:00
plastered_crab dd70d159fb Get Breeding on track with Main again (we do so much work) 2025-08-25 21:56:00 -07:00
plastered_crab eece4256b8 Get Breeding on track with Main again (we do so much work) 2025-08-25 21:51:49 -07:00
plastered_crab b773110e9f Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonMemories.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2025-08-25 21:39:42 -07:00
plastered_crab 1eceaa45b2 Get breeding up to date with main 2025-08-16 19:43:34 -07:00
plastered_crab 1ff2f16c61 Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonPoiTypes.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/resources/cobblemon-common.accesswidener
2025-08-16 19:36:33 -07:00
plastered_crab a4a1c0a4d5 Get breeding up to date with main 2025-08-10 16:52:48 -07:00
plastered_crab d80f0ed3c4 Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/util/EntityExtensions.kt
2025-08-10 15:48:38 -07:00
plastered_crab db4f6a04f3 FREE LUIGIA 2025-07-29 17:14:00 -07:00
plastered_crab 19448a18e7 merge main 2025-07-29 07:23:33 -07:00
plastered_crab a0e125fedb Merge branch 'main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/bedrock/animation/BedrockAnimation.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/ai/PokemonBrain.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/world/feature/CobblemonPlacedFeatures.kt
2025-07-29 07:02:56 -07:00
plastered_crab b3b039677d Finalized Gen 1 egg block textures 2025-07-29 06:56:15 -07:00
plastered_crab 621f539b98 Finalized Gen 1 egg item sprites 2025-07-29 06:50:33 -07:00
plastered_crab 79768a3407 Fix some classes 2025-07-09 06:06:38 -07:00
plastered_crab 117408ac20 Merge branch 'refs/heads/main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonPoiTypes.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/influence/detector/SpawningInfluenceDetector.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
#	common/src/main/resources/data/cobblemon/behaviours/pokemon/auto/pokemon_core.json
2025-07-09 05:23:25 -07:00
plastered_crab 2c0610757f Merge branch 'refs/heads/main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2025-06-29 20:52:12 -07:00
plastered_crab f94e5eb2d3 Merge remote-tracking branch 'refs/remotes/origin/main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonActivities.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/ai/PokemonBrain.kt
2025-06-20 06:10:22 -07:00
plastered_crab 075421856e my kid is asleep time to merge main 2025-06-10 21:46:47 -07:00
plastered_crab 97397aae7d Merge branch 'refs/heads/main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/ai/PokemonBrain.kt
2025-06-10 19:01:46 -07:00
Plastered_Crab 0c9b3ede11 Merge branch 'main' into 1.21.1-breeding 2025-05-29 15:13:46 -07:00
Plastered_Crab 78e15b0d8e Merge branch 'main' into 1.21.1-breeding 2025-05-28 10:54:16 -07:00
plastered_crab b31236dbfb merge main to keep this stuff up to date 2025-05-27 19:49:37 -07:00
plastered_crab da1e172164 Merge branch 'refs/heads/main' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/PokemonPastureBlockEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/item/CobblemonModelPredicateRegistry.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/bedrock/animation/BedrockAnimation.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2025-05-27 19:27:46 -07:00
Paul ab0f2cbd79 Bug fix for grotto block editor 2025-05-11 10:10:58 +10:00
Paul 62c7882469 Apply grotto structure markers that can be detected by spawn files 2025-04-28 20:09:34 +10:00
Paul b6c2c65a6c Integrated spawning upgrades from the other branch with surprisingly little pain 2025-04-27 21:43:01 +10:00
Paul 01206fb67a Merge branch 'spawning-upgrades' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/RidingManager.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/BestSpawner.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/CobblemonSpawningZoneGenerator.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawningZone.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/condition/SpawningCondition.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/fishing/FishingSpawnCause.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/influence/BucketNormalizingInfluence.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/influence/detector/GrottoBlockDetector.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/influence/detector/SpawningInfluenceDetector.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/position/AreaSpawnablePositionResolver.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/spawner/AreaSpawner.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/spawner/Spawner.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/spawner/TriggerSpawner.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/command/SpawnPokemonFromPool.kt
#	common/src/test/kotlin/com/cobblemon/mod/common/spawning/SpawnValidator.kt
2025-04-27 21:20:01 +10:00
Paul 07af63db25 Conflicto fixo 2025-04-27 21:15:53 +10:00
Paul 1047648f68 Merge remote-tracking branch 'origin/main' into 1.21.1-breeding 2025-04-27 20:55:53 +10:00
Paul 6523a610ff immediate compile error fix 2025-04-25 12:08:32 +10:00
Paul 6d132e3f39 Merge remote-tracking branch 'origin/smarterer-pokemons' into 1.21.1-breeding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.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/CobblemonPoiTypes.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/CobblemonSpawningZoneGenerator.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/SpawningZone.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/condition/FishingSpawningCondition.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/context/AreaContextResolver.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/fishing/FishingSpawnCause.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/preset/BestSpawnerConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/selection/FlatContextWeightedSelector.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/spawner/TickingSpawner.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/keybind/keybinds/DebugKeybindings.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
#	common/src/main/resources/cobblemon-common.accesswidener
#	fabric/src/main/kotlin/com/cobblemon/mod/fabric/CobblemonFabric.kt
2025-04-25 12:07:21 +10:00
Paul b311d092de Fixed tests that assume all spawn files are pokemon ones 2025-04-23 21:44:09 +10:00
Paul 6f134a50e0 Touch ups to herd logic with new controls. It works >:] 2025-04-23 20:58:04 +10:00
Paul a0531f2d2f Spawning refactors and herd spawning (untested!!!) 2025-04-22 22:16:15 +10:00
Paul 80f5050ecd Major rename of spawning terms and moved spawn bucket decision later in the process 2025-04-20 20:08:22 +10:00
Landon Wills e32a2da8a4 Fix animation sometimes not playing 2025-04-19 21:05:24 -04:00
Aram e92609b6c7 Fixed Grotto Mimic Particles/Sound 2025-04-19 22:43:16 +02:00
Aram abc0c72707 Small cleanup again 2025-04-19 22:22:48 +02:00
Aram e55f46c990 small cleanup 2025-04-19 21:48:58 +02:00
Aram 6ee5714bf9 Grotto Debug GUI 2025-04-19 21:47:20 +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
2964 changed files with 41905 additions and 1063 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

+11
View File
@@ -0,0 +1,11 @@
### Additions
### Changes
- Replaced the entity AI for Pokémon and NPCs with an entirely custom one. Yeah we changed the entity AI in 1.7 too, you're remembering right.
- Improved performance of entity AI.
### Fixes
### Developer
### Molang & Datapacks
+6
View File
@@ -0,0 +1,6 @@
### Developer
- Spawning Influences now have the context of what the other buckets are when adjusting bucket weights. This will break existing influences that do bucket weight adjustment.
- Renamed heaps of things in the spawning system to make more sense.
+223
View File
@@ -0,0 +1,223 @@
# Habitat Block GUI Editor - Developer Instructions
These instructions are brought to you by AI slop with Hiroku reviewing it afterwards. It does seem fairly accurate
though.
## Overview
The Habitat Block is a spawning tool that allows creative mode players to configure custom Pokémon spawning behavior. We need a GUI editor so read below to understand what it needs to do.
Phases might need a bit of explanation. The idea is that each habitat block will have a current "phase", which changes every day. The number of different phases is something that can be configured per block, as well as what order the phase will move through when it changes.
In the case of 3 phases, the different phase orders do the following:
SIMPLE: 1-2-3-1-2-3-1-2-3
FIXED_RANDOM: 1-3-2-1-3-2-1-3-2 (or some other repeating pattern, based on the position of the habitat block)
FULL_RANDOM: 1-3-1-2-3-1-3-1-2 (completely random value each day)
The idea is like, if you had set 3 phases for a habitat block, you could make Rattata spawn in phase 1, Patrat in phase 2, and Zigzagoon in phase 3.
## Entry Point
- GUI opens when a creative mode player right-clicks a Habitat Block
- Packet: `OpenHabitatBlockEditorPacket` sends current settings to client
- When you're done you send a `SaveHabitatBlockSettingsPacket` back to the server with the same settings DTO instance (but modified, one would assume)
- Settings are applied via `HabitatBlockEntity.applySettings(HabitatSettingsDTO)`
## Data Structure
### Root: `HabitatSettingsDTO`
All settings are contained in this DTO which is sent between client and server.
#### Common Settings (always present)
```kotlin
numberOfPhases: Int = 1 // Number of spawn phases (TODO: not yet implemented)
phaseOrder: HabitatPhaseOrder // How the passage of phases will work with each day
levelRange: IntRange // Min..Max level for spawned Pokémon
modifiers: String // PokemonProperties string (e.g., "shiny gender=male")
mimicId: ResourceLocation // Block appearance (e.g., "minecraft:stone")
```
#### Spawning Mode Toggle
```kotlin
isActivatedSpawning: Boolean // true = Activated mode, false = Natural mode
```
Based on this boolean, EITHER `activatedSettings` OR `naturalSettings` will be populated (never both).
---
## Spawning Modes
### Mode 1: Activated Spawning (`ActivatedHabitatSettingsDTO`)
Triggered spawning system - spawns occur based on specific triggers.
```kotlin
trigger: Enum // REDSTONE, TICK, or RANDOM_TICK
chance: Float = 1.0 // Probability (0.0-1.0) spawn attempt occurs on trigger
cancelledNaturalSpawningRange: Int // Radius (blocks) where natural spawning is disabled
spawnRange: Int // Radius (blocks) where spawning can occur
maxSpawns: Int = -1 // Max concurrent spawns from this block (-1 = infinite)
maxSpawnsPerActivation: Int = 1 // Max spawns per single trigger event
habitatPool: HabitatPoolSettingsDTO // What Pokémon can spawn (see below)
```
**Trigger Types:**
- `REDSTONE`: Spawns on redstone pulse (rising edge)
- `TICK`: Attempts spawn every game tick (very frequent)
- `RANDOM_TICK`: Attempts spawn on random tick events
### Mode 2: Natural Spawning (`NaturalHabitatSettingsDTO`)
Modifies the natural spawning system in the area.
```kotlin
replaceSpawns: Boolean // true = only this pool spawns, false = adds to natural spawns
rangeOfInfluence: Int // Radius (blocks) where this influences spawning
habitatPool: HabitatPoolSettingsDTO // What Pokémon can spawn (see below)
```
---
## Habitat Pool Settings (`HabitatPoolSettingsDTO`)
Defines what Pokémon can spawn. Can reference existing pools or define custom ones.
```kotlin
id: ResourceLocation // Pool identifier
isReference: Boolean // true = use existing pool, false = custom pool
isActivated: Boolean // Must match parent spawning mode
// Only present when isReference = false:
name: String? // Display name for custom pool
spawns: List<HabitatSpawn>? // List of spawn entries
```
**Pool Types:**
- **Reference Pool**: `isReference = true` - Uses a pre-defined pool by ID (e.g., `cobblemon:forest_day`)
- **Custom Pool**: `isReference = false` - Defines spawns inline with `name` and `spawns` list
**Custom Pool IDs**: Must start with `custom__` prefix (e.g., `cobblemon:custom__my_pool`)
---
## GUI Requirements
### Layout Sections
#### 1. Common Settings Panel
- **Number of Phases**: Integer input greater than zero (default: 1)
- **Phase Order**: Habitat Phase Order dropdown (default: SIMPLE)
- **Level Range**: Two integer inputs (Min/Max) - Default: 1 to config max Pokémon level
- **Modifiers**: Text input for PokemonProperties string
- Examples: `shiny`, `gender=male`, `shiny gender=female level=50`
- **Mimic Block**: Block selector/dropdown
- Default: `minecraft:stone`
- See `HabitatBlockEntity.mimicCycle` for suggested blocks
#### 2. Spawning Mode Selector
Radio buttons or toggle:
- **Activated Spawning** (trigger-based)
- **Natural Spawning** (world spawning influenced)
#### 3. Mode-Specific Settings
**If Activated Spawning:**
- **Trigger**: Dropdown (REDSTONE / TICK / RANDOM_TICK)
- **Chance**: Float slider (0.0 - 1.0, default: 1.0)
- **Cancel Natural Spawning Range**: Integer input (blocks)
- **Spawn Range**: Integer input (blocks)
- **Max Spawns**: Integer input (-1 for infinite, default: -1)
- **Max Spawns Per Activation**: Integer input (default: 1)
- **Habitat Pool**: Pool selector (see below)
**If Natural Spawning:**
- **Replace Spawns**: Checkbox (true = exclusive, false = additive)
- **Range of Influence**: Integer input (blocks)
- **Habitat Pool**: Pool selector (see below)
#### 4. Habitat Pool Selector
Two options:
- **Use Existing Pool**: Dropdown of registered pools
- Set `isReference = true`
- Set `id` to selected pool
- Ensure `isActivated` matches spawning mode
- **Create Custom Pool**: Opens pool editor
- Set `isReference = false`
- Generate ID with `custom__` prefix
- Set `name` and `spawns` list
- User defines spawn entries (implementation depends on `HabitatSpawn` structure)
---
## Validation Rules
1. **Level Range**: Min must be ≥ 1, Max must be ≤ config max, Min ≤ Max
2. **Number of Phases**: Must be greater than zero
3. **Chance**: Must be between 0.0 and 1.0
4. **Ranges**: All range values must be ≥ 0
5. **Pool Validation**:
- If `isActivated = true`, pool must be `ActivatedHabitatPool`
- If `isActivated = false`, pool must be `NaturalHabitatPool`
- If reference pool not found, server logs error and rejects settings
6. **Custom Pool IDs**: Must start with `custom__`
---
## Network Flow
1. **Opening GUI**: Server sends `OpenHabitatBlockEditorPacket` with current `HabitatSettingsDTO`
2. **Saving Changes**: Client sends updated `HabitatSettingsDTO` back to server
3. **Server Processing**:
- `HabitatBlockEntity.applySettings()` validates and applies changes
- Calls `refreshFromSettings()` to regenerate spawn details
- Marks block entity as changed and syncs to clients
---
## Implementation Notes
- **Creative Mode Only**: GUI should only be accessible to creative mode players
---
## Example Configurations
### Example 1: Redstone-Triggered Shiny Spawner
```kotlin
isActivatedSpawning = true
phaseOrder = FIXED_RANDOM
numberOfPhases = 4
levelRange = 50..60
modifiers = "shiny"
mimicId = "minecraft:diamond_block"
activatedSettings:
trigger = REDSTONE
chance = 1.0
spawnRange = 10
maxSpawns = 1
maxSpawnsPerActivation = 1
habitatPool = (reference to existing pool)
```
### Example 2: Natural Spawn Replacement Zone
```kotlin
isActivatedSpawning = false
levelRange = 1..100
phaseOrder = SIMPLE
numberOfPhases = 1
modifiers = ""
mimicId = "minecraft:moss_block"
naturalSettings:
replaceSpawns = true
rangeOfInfluence = 32
habitatPool = (custom pool with specific Pokémon)
```
---
## Related Files
- `HabitatBlockEntity.kt` - Main block entity logic
- `HabitatSettingsDTO.kt` - Root settings container
- `ActivatedHabitatSettingsDTO.kt` - Activated mode settings
- `NaturalHabitatSettingsDTO.kt` - Natural mode settings
- `HabitatPoolSettingsDTO.kt` - Pool configuration
- `ActivatedHabitatSpawning.kt` - Activated spawning implementation
- `NaturalHabitatSpawning.kt` - Natural spawning implementation
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.drop.EvolutionItemDropEntry
import com.cobblemon.mod.common.api.drop.ItemDropEntry
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.CobblemonEvents.DATA_SYNCHRONIZED
import com.cobblemon.mod.common.api.habitats.HabitatPools
import com.cobblemon.mod.common.api.interaction.RequestManager
import com.cobblemon.mod.common.api.molang.MoLangLoadedFilesCache
import com.cobblemon.mod.common.api.molang.ObjectValue
@@ -182,6 +183,8 @@ object Cobblemon {
var wallpapers = mutableMapOf<UUID, Set<ResourceLocation>>()
val serverPlayerStructs = mutableMapOf<UUID, ObjectValue<Player>>()
/** Player UUID to entity UUID that they're brain debuggin' */
val serverPlayerDebuggingPokemonBrainEntities = mutableMapOf<UUID, UUID>()
val statistics: CobblemonStats = CobblemonStats
@@ -204,6 +207,8 @@ object Cobblemon {
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
}
this.loadConfig()
implementation.registerRecipeSerializers()
implementation.registerRecipeTypes()
implementation.registerPermissionValidator()
@@ -232,7 +237,6 @@ object Cobblemon {
ExperienceGroups.registerDefaults()
CaptureCalculators.registerDefaults()
this.loadConfig()
// CobblemonBlockPredicates.touch()
CobblemonOrePlacedFeatures.register()
CobblemonPlacedFeatures.register()
@@ -256,6 +260,7 @@ object Cobblemon {
playerDataManager.onPlayerDisconnect(it.player)
RequestManager.onLogoff(it.player)
serverPlayerStructs.remove(it.player.uuid)
serverPlayerDebuggingPokemonBrainEntities.remove(it.player.uuid)
}
PlatformEvents.PLAYER_DEATH.subscribe {
PCLinkManager.removeLink(it.player.uuid)
@@ -363,6 +368,7 @@ object Cobblemon {
val server = event.server
MoLangLoadedFilesCache.initialize(server)
playerDataManager = PlayerInstancedDataStoreManager().also { it.setup(server) }
HabitatPools.onServerLoading(server)
val mongoClient: MongoClient?
@@ -29,6 +29,8 @@ object CobblemonActivities {
val POKEMON_HERD = register(Activity("pokemon_herd"))
@JvmField
val POKEMON_POLLINATION = register(Activity("pokemon_pollination"))
@JvmField
val GUARD_NEST = register(Activity("guard_nest"))
@JvmStatic
fun register(activity: Activity): Activity {
@@ -12,6 +12,8 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.ai.CobblemonBehaviour
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.config.task.TaskConfig
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.data.JsonDataRegistry.Companion.JSON_EXTENSION
@@ -26,6 +28,8 @@ import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionOrEntityVariableAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.MemoryModuleTypeAdapter
import com.cobblemon.mod.common.util.adapters.ObjectiveConfigAdapter
import com.cobblemon.mod.common.util.adapters.SenseConfigAdapter
import com.cobblemon.mod.common.util.adapters.SensorTypeAdapter
import com.cobblemon.mod.common.util.adapters.TaskConfigAdapter
import com.cobblemon.mod.common.util.adapters.TranslatedTextAdapter
@@ -55,6 +59,8 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
.registerTypeAdapter(SensorType::class.java, SensorTypeAdapter)
.registerTypeAdapter(BehaviourConfig::class.java, BehaviourConfigAdapter)
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
.registerTypeAdapter(ObjectiveConfig::class.java, ObjectiveConfigAdapter)
.registerTypeAdapter(SenseConfig::class.java, SenseConfigAdapter)
.registerTypeAdapter(
TypeToken.getParameterized(Either::class.java, Expression::class.java, MoLangConfigVariable::class.java).type,
ExpressionOrEntityVariableAdapter
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.block.entity.RestorationTankBlockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
import com.cobblemon.mod.common.block.entity.*
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
@@ -49,6 +50,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, CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN).build(null))
@JvmField
val HABITAT_BLOCK: BlockEntityType<HabitatBlockEntity> = this.create("habitat_block",
BlockEntityType.Builder.of(::HabitatBlockEntity, CobblemonBlocks.HABITAT_BLOCK).build(null)
)
@JvmField
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
CobblemonBlocks.GILDED_CHEST,
@@ -82,6 +88,35 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
).build(null)
)
val NEST: BlockEntityType<NestBlockEntity> = this.create("nest",
BlockEntityType.Builder.of(
::NestBlockEntity,
CobblemonBlocks.NEST_FIERY,
CobblemonBlocks.NEST_GRASSY,
CobblemonBlocks.NEST_ICY,
CobblemonBlocks.NEST_ROCKY,
CobblemonBlocks.NEST_SANDY,
CobblemonBlocks.NEST_SHROOMY,
CobblemonBlocks.NEST_TWIGGY,
CobblemonBlocks.NEST_WATERY
).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)
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.apricorn.Apricorn
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.block.*
import com.cobblemon.mod.common.api.nest.WildNestType
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.LecternBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
@@ -24,6 +25,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
import com.cobblemon.mod.common.block.habitat.HabitatBlock
import com.cobblemon.mod.common.mixin.invoker.*
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.util.cobblemonResource
@@ -46,7 +48,6 @@ import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.block.state.properties.WoodType
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.phys.shapes.Shapes
@Suppress("SameParameterValue", "HasPlatformType", "MemberVisibilityCanBePrivate", "unused")
object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<Block>>, Block>() {
@@ -813,6 +814,72 @@ 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_FIERY = this.create(
"nest_fiery",
WildNestBlock(WildNestType.FIERY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_GRASSY = this.create(
"nest_grassy",
WildNestBlock(WildNestType.GRASSY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_ICY = this.create(
"nest_icy",
WildNestBlock(WildNestType.ICY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_PLAYER = this.create(
"nest_player",
WildNestBlock(WildNestType.PLAYER, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_ROCKY = this.create(
"nest_rocky",
WildNestBlock(WildNestType.ROCKY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_SANDY = this.create(
"nest_sandy",
WildNestBlock(WildNestType.SANDY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_SHROOMY = this.create(
"nest_shroomy",
WildNestBlock(WildNestType.SHROOMY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_TWIGGY = this.create(
"nest_twiggy",
WildNestBlock(WildNestType.TWIGGY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_WATERY = this.create(
"nest_watery",
WildNestBlock(WildNestType.WATERY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val EGG = this.create("egg", EggBlock(BlockBehaviour.Properties
.ofFullCopy(Blocks.DRAGON_EGG)
.pushReaction(PushReaction.BLOCK)
.sound(SoundType.BONE_BLOCK)
))
@JvmField
val HABITAT_BLOCK = this.create(
name = "habitat_block",
entry = HabitatBlock(
properties = BlockBehaviour.Properties.of()
.mapColor(MapColor.STONE)
.strength(1.5F, Blocks.OBSIDIAN.explosionResistance)
.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.
@@ -853,6 +920,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
*/
@@ -890,4 +963,4 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val block = BlocksInvoker.createLeavesBlock(SoundType.GRASS)
return this.create(name, block)
}
}
}
@@ -61,6 +61,8 @@ object CobblemonCommands {
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
GivePokemonEggCommand.register(dispatcher)
GiveAllPokemonEggCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
UnlockPCBoxWallpaperCommand.register(dispatcher)
@@ -76,5 +78,6 @@ object CobblemonCommands {
// with a certain system environment variable set
CobblemonInfoCommand.register(dispatcher)
CalculateSeatPositionCommand.register(dispatcher)
DebugCobblemonAICommand.register(dispatcher)
}
}
@@ -68,6 +68,12 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.persistent(FoodColourComponent.CODEC)
.networkSynchronized(FoodColourComponent.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())
@JvmField
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
@@ -104,6 +110,7 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:egg_data"), EGG_DATA)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:held_item_effect"), HELD_ITEM_EFFECT)
}
@@ -51,13 +51,13 @@ 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
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.HangingSignItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.Item.Properties
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
@@ -599,6 +599,23 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val BERRY_JUICE = this.create("berry_juice", BerryJuiceItem())
val NEST_GRASSY = blockItem("nest_grassy", CobblemonBlocks.NEST_GRASSY)
val NEST_FIERY = blockItem("nest_fiery", CobblemonBlocks.NEST_FIERY)
val NEST_ICY = blockItem("nest_icy", CobblemonBlocks.NEST_ICY)
val NEST_PLAYER = blockItem("nest_player", CobblemonBlocks.NEST_PLAYER)
val NEST_ROCKY = blockItem("nest_rocky", CobblemonBlocks.NEST_ROCKY)
val NEST_SANDY = blockItem("nest_sandy", CobblemonBlocks.NEST_SANDY)
val NEST_SHROOMY = blockItem("nest_shroomy", CobblemonBlocks.NEST_SHROOMY)
val NEST_TWIGGY = blockItem("nest_twiggy", CobblemonBlocks.NEST_TWIGGY)
val NEST_WATERY = blockItem("nest_watery", CobblemonBlocks.NEST_WATERY)
@JvmField
val INCUBATOR = blockItem("incubator", CobblemonBlocks.INCUBATOR)
@JvmField
val HABITAT_BLOCK = blockItem("habitat_block", CobblemonBlocks.HABITAT_BLOCK)
@JvmField
val GALARICA_NUTS = compostableItemNameBlockItem("galarica_nuts", CobblemonBlocks.GALARICA_NUT_BUSH)
@@ -952,7 +969,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
@@ -1472,6 +1489,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)))
@@ -1561,9 +1582,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return create(name, RegionalFoodItem(properties))
}
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)
}
@@ -12,15 +12,17 @@ import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
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 java.util.Optional
import java.util.UUID
import net.minecraft.core.BlockPos
import net.minecraft.core.GlobalPos
import net.minecraft.core.UUIDUtil
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import java.util.*
import net.minecraft.world.entity.player.Player
object CobblemonMemories {
val memories = mutableMapOf<String, MemoryModuleType<*>>()
@@ -39,6 +41,16 @@ object CobblemonMemories {
@JvmField
val ACTIVE_ACTION_EFFECT = register<ActionEffectContext>("active_action_effect")
@JvmField
val POTENTIAL_MATE = register<PokemonEntity>("potential_mate")
@JvmField
val MATE_SCAN_COOLDOWN = register("mate_scan_cooldown", PrimitiveCodec.BOOL)
@JvmField
val NEST_LOCATION = register<BlockPos>("nest_location")
@JvmField
val PATERNAL_NEST_LOCATION = register<BlockPos>("paternal_nest")
@JvmField
val IRRITATED_AT = register<UUID>("irritated_at")
@JvmField
val POKEMON_FLYING = register("pokemon_flying", PrimitiveCodec.BOOL)
@JvmField
val POKEMON_DROWSY = register("pokemon_drowsy", PrimitiveCodec.BOOL)
@@ -55,6 +67,26 @@ object CobblemonMemories {
@JvmField
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
@JvmField
val EGG_THIEF = register<Player>("egg_thief") // Stores the player stealing the egg
@JvmField
val EGG_THIEF_LAST_SEEN_TICK = register("egg_thief_last_seen_tick", PrimitiveCodec.LONG)
@JvmField
val EGG_LOCATION = register<BlockPos>("egg_location") // Stores the last known location of the egg
@JvmField
val EGG_IN_NEST = register("egg_in_nest", PrimitiveCodec.BOOL)
@JvmField
val READY_TO_GRAB_EGG = register("ready_to_grab_egg", PrimitiveCodec.BOOL)
@JvmField
val HOLDING_EGG = register("holding_egg", PrimitiveCodec.BOOL)
@JvmField
val EGG_SEARCH_COUNTDOWN = register ("egg_search_countdown", PrimitiveCodec.BOOL)
@JvmField
val IDEAL_NEST_LOCATION = register<BlockPos>("ideal_nest_location")
@JvmField
val READY_TO_BUILD_NEST = register("ready_to_build_nest", PrimitiveCodec.BOOL)
@JvmField
val NEST_DESPAWN_IMMUNE_UNTIL_TICK = register("nest_despawn_immune_until_tick", PrimitiveCodec.LONG)
@JvmField
val NEARBY_SWEET_BERRY_BUSH = register<BlockPos>("nearest_sweet_berry_bush")
@JvmField
val DISABLE_WALK_TO_BERRY_BUSH = register<Boolean>("disable_walk_to_berry_bush")
@@ -63,8 +95,6 @@ object CobblemonMemories {
@JvmField
val IS_CONSUMING_ITEM = register<Boolean>("is_consuming_item")
@JvmField
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
@JvmField
val HIVE_LOCATION = register<BlockPos>("hive_location", BlockPos.CODEC)
@JvmField
val HIVE_BLACKLIST = register<List<BlockPos>>("hive_blacklist")
@@ -106,4 +136,4 @@ object CobblemonMemories {
memories[id] = memoryModule
return memoryModule
}
}
}
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
import com.cobblemon.mod.common.client.net.debug.UpdateTrackedBrainDataHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
@@ -34,6 +35,7 @@ import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.habitat.OpenHabitatBlockEditorHandler
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
@@ -74,6 +76,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
@@ -81,6 +84,7 @@ import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyn
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
import com.cobblemon.mod.common.net.messages.client.debug.UpdateTrackedBrainDataPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
@@ -88,6 +92,7 @@ import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacke
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.habitat.OpenHabitatBlockEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
@@ -140,7 +145,13 @@ import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
import com.cobblemon.mod.common.net.messages.server.BattleChallengeResponsePacket
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
@@ -150,6 +161,7 @@ import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
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
@@ -162,6 +174,7 @@ import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidin
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatsPacket
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
import com.cobblemon.mod.common.net.messages.server.dialogue.InputToDialoguePacket
import com.cobblemon.mod.common.net.messages.server.habitat.SaveHabitatBlockSettingsPacket
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
@@ -205,6 +218,7 @@ import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHan
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
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
@@ -218,9 +232,9 @@ import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRiding
import com.cobblemon.mod.common.net.serverhandling.dialogue.EscapeDialogueHandler
import com.cobblemon.mod.common.net.serverhandling.dialogue.InputToDialogueHandler
import com.cobblemon.mod.common.net.serverhandling.evolution.AcceptEvolutionHandler
import com.cobblemon.mod.common.net.serverhandling.habitat.SaveHabitatBlockSettingsHandler
import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
import com.cobblemon.mod.common.net.serverhandling.orientation.ServerboundUpdateOrientationHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.PartyTutorialPacketHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.SetPastureConflictHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
@@ -243,6 +257,7 @@ import com.cobblemon.mod.common.net.serverhandling.storage.RequestMoveSwapHandle
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.SwapPCPartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.PartyTutorialPacketHandler
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.*
@@ -414,6 +429,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpawnBaitRegistrySyncPacket.ID, SpawnBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SeasoningRegistrySyncPacket.ID, SeasoningRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BreedingRuleRegistrySyncPacket.ID, BreedingRuleRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CallbackRegistrySyncPacket.ID, CallbackRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -488,8 +504,13 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
// Habitat blocks
list.add(PacketRegisterInfo(OpenHabitatBlockEditorPacket.ID, OpenHabitatBlockEditorPacket::decode, OpenHabitatBlockEditorHandler))
// Debug
list.add(PacketRegisterInfo(OpenRidingStatsDebugGUIPacket.ID, OpenRidingStatsDebugGUIPacket::decode, OpenRidingStatsDebugGUIHandler))
list.add(PacketRegisterInfo(UpdateTrackedBrainDataPacket.ID, UpdateTrackedBrainDataPacket::decode, UpdateTrackedBrainDataHandler))
return list
}
@@ -606,13 +627,20 @@ object CobblemonNetwork {
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
// Breeding Packets
list.add(PacketRegisterInfo(TriggerEggHatchPacket.ID, TriggerEggHatchPacket::decode, TriggerEggHatchHandler))
// Behaviour Packets
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
// Debug
// Habitat blocks
list.add(PacketRegisterInfo(SaveHabitatBlockSettingsPacket.ID, SaveHabitatBlockSettingsPacket::decode, SaveHabitatBlockSettingsHandler))
// Debug / cheats
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
return list
}
@@ -30,6 +30,10 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
// TODO after 1.7
// @JvmField
// val INCENSE_SWEET_KEY = createKey("incense_sweet", CobblemonBlocks.INCENSE_SWEET, 0, 1)
@JvmField
val HABITAT_BLOCK_KEY: ResourceKey<PoiType> = createKey("habitat_block", CobblemonBlocks.HABITAT_BLOCK, 0, 1)
@JvmField
val HABITAT_BLOCK = create(HABITAT_BLOCK_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.HABITAT_BLOCK), 0, 1))
private fun createKey(string: String, block: Block, maxTickets: Int, validRange: Int ): ResourceKey<PoiType> =
ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string)).also { resourceKey ->
@@ -24,6 +24,10 @@ object CobblemonSensors {
val NPC_BATTLING = register("npc_battling", ::NPCBattlingSensor)
@JvmField
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 IDEAL_NEST_LOCATION_SENSOR = register("ideal_nest_location_sensor", ::IdealNestPlacementSensor)
// val NPC_BATTLING = register("npc_battling", ::NPCBattlingSensor)
@JvmField
val POKEMON_DROWSY = register("pokemon_drowsy", ::DrowsySensor)
@@ -61,4 +65,4 @@ object CobblemonSensors {
sensors[id] = sensor
return sensor
}
}
}
@@ -94,6 +94,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
@@ -12,13 +12,16 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.util.activityRegistry
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
@@ -46,8 +49,15 @@ class BehaviourConfigurationContext {
val memories = mutableSetOf<MemoryModuleType<*>>()
val sensors = mutableSetOf<SensorType<*>>()
val objectives = mutableMapOf<Int, MutableList<Objective>>()
val senses = mutableSetOf<Sense>()
val onAdd = mutableListOf<ExpressionLike>()
init {
memories.add(CobblemonMemories.NEST_DESPAWN_IMMUNE_UNTIL_TICK)
}
fun getOrCreateActivity(activity: Activity): ActivityConfigurationContext {
return activities.firstOrNull { it.activity == activity } ?: ActivityConfigurationContext(activity).also(activities::add)
}
@@ -68,6 +78,22 @@ class BehaviourConfigurationContext {
this.sensors.addAll(sensors)
}
fun addSenses(vararg senses: Sense) {
addSenses(senses.toList())
}
fun addSenses(senses: Collection<Sense>) {
outer@for (sense in senses) {
for (existingSense in this.senses) {
if (existingSense.merge(sense)) {
continue@outer
}
}
this.senses.add(sense)
this.addMemories(sense.memories)
}
}
fun addOnAddScript(script: ExpressionLike) {
onAdd.add(script)
}
@@ -88,8 +114,23 @@ class BehaviourConfigurationContext {
var brain = entity.brain
// Apply the brain config
if (entity is MoLangScriptingEntity) {
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, sensors)
val useBiggerBrain = objectives.isNotEmpty() || senses.isNotEmpty()
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, if (useBiggerBrain) emptySet() else sensors)
onAdd.forEach { runtime.resolve(it) }
if (useBiggerBrain) {
val wereDebugging = entity.biggerBrain.debuggingPlayers ?: emptySet()
val biggerBrain = BiggerBrain(entity)
entity.biggerBrain = biggerBrain
addSenses(biggerBrain.senses)
val orderedObjectives = objectives.entries.sortedBy { it.key }.flatMap { it.value }
orderedObjectives.forEach(biggerBrain::registerObjective)
biggerBrain.senses.addAll(senses)
biggerBrain.debuggingPlayers.addAll(wereDebugging)
return
} else {
val biggerBrain = BiggerBrain(entity)
entity.biggerBrain = biggerBrain
}
}
activities.forEach { it.apply(entity) }
brain.setCoreActivities(coreActivities)
@@ -136,4 +177,4 @@ class BehaviourConfigurationContext {
}
return struct
}
}
}
@@ -9,44 +9,40 @@
package com.cobblemon.mod.common.api.ai
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.util.getBooleanOrNull
import com.cobblemon.mod.common.util.getDoubleOrNull
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.PathfinderMob
/**
* Used to maintain some settings around how the entity should wander this tick. The idea is you
* reset this with some script task at priority 0 then some number of steps before it gets to the wander
* tasks you can have other conditional tasks jump in to configure parts of this.
* Used to maintain some settings around how the entity should wander this tick. Anything left null
* will fall back to defaults specified by the logic of the WanderAround objective.
*
* @author Hiroku
* @since July 18, 2025
*/
class CobblemonWanderControl(
var center: WanderCenter? = null,
var maxAttempts: Int = 4, // How many times to try to find a suitable point before giving up on that tick.
var allowLand: Boolean = true,
var allowWater: Boolean = false,
var allowAir: Boolean = false,
var pathCooldownTicks: Int = 20 * 5, // Used to mitigate the cost of pathfinding.
var wanderSpeed: Float = 0.35F,
var maxAttempts: Int? = null, // How many times to try to find a suitable point before giving up on that tick.
var allowLand: Boolean? = null,
var allowWater: Boolean? = null,
var allowAir: Boolean? = null,
var wanderSpeed: Float? = null,
var wanderChanceMultiplier: Float? = null
) {
class WanderCenter(val x: Number, val y: Number, val z: Number, val minRange: Float, val maxRange: Float)
@Transient
val struct: QueryStruct = QueryStruct(hashMapOf())
.addFunction("center_x") { center?.x ?: 0.0 }
.addFunction("center_y") { center?.y ?: 0.0 }
.addFunction("center_z") { center?.z ?: 0.0 }
.addFunction("max_attempts") { maxAttempts.toDouble() }
.addFunction("min_range") { center?.minRange ?: 0.0 }
.addFunction("max_range") { center?.maxRange ?: 0.0 }
.addFunction("allow_land") { if (allowLand) 1.0 else 0.0 }
.addFunction("allow_water") { if (allowWater) 1.0 else 0.0 }
.addFunction("allow_air") { if (allowAir) 1.0 else 0.0 }
.addFunction("path_cooldown_ticks") { pathCooldownTicks.toDouble() }
.addFunction("wander_speed") { wanderSpeed }
.addFunction("center_x") { DoubleValue(center?.x ?: 0.0) }
.addFunction("center_y") { DoubleValue(center?.y ?: 0.0) }
.addFunction("center_z") { DoubleValue(center?.z ?: 0.0) }
.addFunction("max_attempts") { DoubleValue(maxAttempts?.toDouble() ?: 0.0) }
.addFunction("min_range") { DoubleValue(center?.minRange ?: 0.0) }
.addFunction("max_range") { DoubleValue(center?.maxRange ?: 0.0) }
.addFunction("allow_land") { DoubleValue(if (allowLand == true) 1.0 else 0.0) }
.addFunction("allow_water") { DoubleValue(if (allowWater == true) 1.0 else 0.0) }
.addFunction("allow_air") { DoubleValue(if (allowAir == true) 1.0 else 0.0) }
.addFunction("wander_speed") { DoubleValue(wanderSpeed ?: 0.0) }
.addFunction("set_center") { params ->
if (params.params.size < 3) {
throw IllegalArgumentException("set_center requires at least 3 parameters: x, y, z")
@@ -68,26 +64,6 @@ class CobblemonWanderControl(
.addFunction("set_allow_land") { params -> allowLand = params.getBooleanOrNull(0) ?: true }
.addFunction("set_allow_water") { params -> allowWater = params.getBooleanOrNull(0) ?: false }
.addFunction("set_allow_air") { params -> allowAir = params.getBooleanOrNull(0) ?: false }
.addFunction("set_path_cooldown_ticks") { params -> pathCooldownTicks = params.getDoubleOrNull(0)?.toInt() ?: (20 * 5) }
.addFunction("set_wander_speed") { params -> wanderSpeed = params.getDoubleOrNull(0)?.toFloat() ?: 0.35F }
.addFunction("reset") { reset() }
fun isSuitable(position: BlockPos): Boolean {
val center = center ?: return true
val centerPos = BlockPos(center.x.toInt(), center.y.toInt(), center.z.toInt())
val distance = sqrt(centerPos.distSqr(position))
val suitable = distance >= center.minRange && distance <= center.maxRange
return suitable
}
fun reset() {
center = null
allowLand = false
allowWater = false
allowAir = false
pathCooldownTicks = 20 * 5
wanderSpeed = 0.35F
maxAttempts = 4
}
}
@@ -9,9 +9,23 @@
package com.cobblemon.mod.common.api.ai
import com.bedrockk.molang.Expression
import com.bedrockk.molang.ast.NumberExpression
import com.bedrockk.molang.ast.StringExpression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.resolveString
import com.mojang.datafixers.util.Either
import kotlin.jvm.optionals.getOrNull
import net.minecraft.world.entity.LivingEntity
/**
* The issue in a lot of the behaviour datapacking is that there are times when a property should be exposed to
@@ -31,6 +45,50 @@ import kotlin.jvm.optionals.getOrNull
*/
typealias ExpressionOrEntityVariable = Either<Expression, MoLangConfigVariable>
fun ExpressionOrEntityVariable.asExpression() = map({ it }, { "q.entity.config.${it.variableName}".asExpression() })
fun ExpressionOrEntityVariable.resolveString(runtime: MoLangRuntime) = runtime.resolveString(asExpression())
fun ExpressionOrEntityVariable.resolveBoolean(runtime: MoLangRuntime) = runtime.resolveBoolean(asExpression())
fun ExpressionOrEntityVariable.resolveInt(runtime: MoLangRuntime) = runtime.resolveInt(asExpression())
fun ExpressionOrEntityVariable.resolveDouble(runtime: MoLangRuntime) = runtime.resolveDouble(asExpression())
fun ExpressionOrEntityVariable.resolveFloat(runtime: MoLangRuntime) = runtime.resolveFloat(asExpression())
private fun variable(category: String, name: String, type: MoLangConfigVariable.MoLangVariableType, default: String) = MoLangConfigVariable(
variableName = name,
category = lang("entity.variable.category.$category"),
displayName = lang("entity.variable.$name.name"),
description = lang("entity.variable.$name.desc"),
type = type,
defaultValue = default
)
fun stringVariable(category: String, name: String, default: String) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.TEXT, default = default)
fun numberVariable(category: String, name: String, default: Number) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.NUMBER, default = default.toString())
fun booleanVariable(category: String, name: String, default: Boolean) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.BOOLEAN, default = default.toString())
fun getVariableExpression(name: String) = "q.entity.config.$name".asExpression()
fun resolveBooleanVariable(name: String, runtime: MoLangRuntime) = runtime.resolveBoolean(getVariableExpression(name))
fun checkCondition(runtime: MoLangRuntime, expressionOrEntityVariable: ExpressionOrEntityVariable): Boolean {
return expressionOrEntityVariable.resolveBoolean(runtime)
}
fun ExpressionOrEntityVariable.asSimplifiedExpression(entity: LivingEntity): Expression {
return map(
{ it },
{
if (entity is MoLangScriptingEntity) {
val variable = entity.config.map[it.variableName]
if (variable is DoubleValue) {
return@map NumberExpression(variable.value)
} else if (variable is StringValue) {
return@map StringExpression(variable)
}
}
return@map "q.entity.config.${it.variableName}".asExpression()
}
)
}
/**
* In the case of [ExpressionOrEntityVariable] the right side is the only one that requires visibility for a user,
* this function is a shorthand for finding all of the values in the list that have something to show the user to
@@ -0,0 +1,182 @@
/*
* 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.ai.biggerbrain
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.net.messages.client.debug.UpdateTrackedBrainDataPacket
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.withQueryValue
import java.util.UUID
import kotlin.collections.mutableListOf
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
/**
* A better* version of Minecraft's [net.minecraft.world.entity.ai.Brain] system that makes development more
* straightforward and allows easier debugging.
*
* We could name this thing better.
*
* There are three main principles inside this system:
*
* 1: Senses
*
* Senses are simple ticking objects that are instantiated per entity and are expected to just update memories based
* on properties about the entity or its surroundings. This is an analogue of SensorType from Brains.
*
* 2: Objectives
*
* Objectives are also ticking objects that are instantiated per entity. They represent a goal for the entity and
* run in a fixed order.
*
* 3: Fixations
*
* Fixations are a task that can be set on the brain that represents a more total commitment to a certain task. Only
* a single fixation can be active at a time, and they can be 'distracted' by other objectives or potentially external
* factors.
*
* These can also be debugged using the /debugcobblemonai command while looking at a Pokémon or NPC entity.
*
* *that remains to be seen
*
* @author Hiroku
* @since December 29th, 2025
*/
class BiggerBrain(val entity: LivingEntity) {
val runtime = MoLangRuntime().setup().withQueryValue("entity", entity.asMostSpecificMoLangValue())
val senses = mutableListOf<Sense>()
val objectives = mutableListOf<Objective>()
val observedMemories = mutableMapOf<MemoryModuleType<*>, MutableList<Objective>>()
var fixation: EntityFixation? = null
var debuggingPlayers = mutableSetOf<UUID>()
fun registerObjective(objective: Objective) {
objectives.add(objective)
for (memory in objective.observedMemories) {
val list = this.observedMemories.getOrPut(memory, ::ArrayList)
list.add(objective)
}
}
fun isFixation(fixation: EntityFixation?): Boolean {
return fixation != null && this.fixation == fixation
}
fun sendDebugMessage(message: String) {
debuggingPlayers.forEach {
it.getPlayer()?.sendSystemMessage(message.text())
}
}
fun tick() {
senses.forEach {
it.ticksUntilNextExecution--
if (it.ticksUntilNextExecution <= 0) {
it.tick(entity, this)
}
}
if (fixation?.tick(entity, this) == false) {
fixation = null
}
val objectiveStatuses = mutableMapOf<Objective, ObjectiveStatus>()
val activeObjectiveTags = mutableSetOf<String>()
for (objective in objectives) {
val status = objective.perform(entity, this, activeObjectiveTags)
objectiveStatuses[objective] = status
}
if (debuggingPlayers.isNotEmpty()) {
val state = BiggerBrainState(entity = entity, brain = this, objectiveStatuses = objectiveStatuses)
val packet = UpdateTrackedBrainDataPacket(entity.id, state)
debuggingPlayers.removeIf { uuid ->
val player = uuid.getPlayer()
if (player != null && Cobblemon.serverPlayerDebuggingPokemonBrainEntities[player.uuid] != entity.uuid) {
return@removeIf true
} else {
player?.sendPacket(packet)
}
return@removeIf player == null
}
}
}
fun fixate(fixation: EntityFixation): EntityFixation {
this.fixation = fixation
return fixation
}
fun distract(distraction: Distraction): Boolean {
if (fixation?.canDistract(distraction, entity) != false) {
fixation?.onDistract(distraction, entity)
this.fixation = null
return true
}
return false
}
fun canDistract(distraction: Distraction): Boolean {
return fixation?.canDistract(distraction, entity) != false
}
fun <T> setMemory(memory: MemoryModuleType<T>, value: T, expiry: Long? = null) {
if (expiry != null) {
entity.brain.setMemoryWithExpiry(memory, value, expiry)
} else {
entity.brain.setMemory(memory, value)
}
observedMemories[memory]?.forEach { objective ->
objective.onMemoryChanged(entity, this, memory, value)
}
}
fun <T> setMemoryIfChanged(memory: MemoryModuleType<T>, value: T, expiry: Long? = null) {
if (entity.brain.getMemorySafely(memory).orElse(null) != value) {
setMemory(memory, value, expiry)
}
}
fun <T> eraseMemory(memory: MemoryModuleType<T>) {
entity.brain.eraseMemory(memory)
observedMemories[memory]?.forEach { objective ->
objective.onMemoryChanged(entity, this, memory, null)
}
}
fun <T> eraseMemoryIfPresent(memory: MemoryModuleType<T>) {
if (entity.brain.hasMemoryValue(memory)) {
eraseMemory(memory)
}
}
fun hasMemoryValue(memory: MemoryModuleType<*>): Boolean {
return entity.brain.checkMemory(memory, MemoryStatus.VALUE_PRESENT)
}
fun <T> getMemory(memory: MemoryModuleType<T>): T? {
return entity.brain.getMemorySafely(memory).orElse(null)
}
}
@@ -0,0 +1,74 @@
/*
* 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.ai.biggerbrain
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import kotlin.reflect.jvm.jvmName
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.world.entity.LivingEntity
/**
* A DTO of sorts that is sent to debugging players to keep them informed about the state of an entity's Cobblemon AI.
*
* @author Hiroku
* @since December 29th, 2025
*/
class BiggerBrainState(
val senses: List<SenseState>,
val objectives: List<ObjectiveState>,
val fixation: String? = null
) {
class SenseState(val className: String, val ticksUntilNextExecution: Int) {
fun encode(buffer: FriendlyByteBuf) {
buffer.writeUtf(className)
buffer.writeInt(ticksUntilNextExecution)
}
companion object {
fun decode(buffer: FriendlyByteBuf): SenseState {
return SenseState(buffer.readUtf(), buffer.readInt())
}
}
}
class ObjectiveState(val description: String, val status: ObjectiveStatus) {
fun encode(buffer: FriendlyByteBuf) {
buffer.writeUtf(description)
buffer.writeEnum(status)
}
companion object {
fun decode(buffer: FriendlyByteBuf): ObjectiveState {
return ObjectiveState(buffer.readUtf(), buffer.readEnum(ObjectiveStatus::class.java))
}
}
}
constructor(entity: LivingEntity, brain: BiggerBrain, objectiveStatuses: Map<Objective, ObjectiveStatus>): this(
senses = brain.senses.map { SenseState(className = it::class.simpleName ?: it::class.jvmName, it.ticksUntilNextExecution) },
objectives = objectiveStatuses.entries.map { ObjectiveState(it.key.getDescription(entity), it.value) },
fixation = brain.fixation?.getDescription(entity)
)
fun encode(buffer: FriendlyByteBuf) {
buffer.writeCollection(senses) { _, sense -> sense.encode(buffer) }
buffer.writeCollection(objectives) { _, objective -> objective.encode(buffer) }
buffer.writeNullable(fixation) { _, fixation -> buffer.writeUtf(fixation) }
}
companion object {
fun decode(buffer: FriendlyByteBuf): BiggerBrainState {
val senses = buffer.readList { SenseState.decode(it) }
val objectives = buffer.readList { ObjectiveState.decode(it) }
val fixation = buffer.readNullable { buffer.readUtf() }
return BiggerBrainState(senses, objectives, fixation)
}
}
}
@@ -0,0 +1,38 @@
/*
* 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.ai.biggerbrain
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.behavior.PositionTracker
import net.minecraft.world.entity.ai.memory.WalkTarget
/**
* A trivial wrapper around an entity and a position tracker to make the entity look at the position each tick.
*
* @author Hiroku
* @since December 29th, 2025
*/
class LookingTracker(val entity: PathfinderMob, val lookTarget: PositionTracker) {
companion object {
fun forTargetEntity(entity: PathfinderMob, target: LivingEntity, trackEyeHeight: Boolean = true): LookingTracker {
return LookingTracker(entity, EntityTracker(target, trackEyeHeight))
}
fun fromNavigationPathOrWalkTarget(entity: PathfinderMob, walkTarget: WalkTarget): LookingTracker {
val destinationPoint = entity.navigation.path?.endNode?.asVec3() ?: walkTarget.target.currentPosition()
return LookingTracker(entity, BlockPosTracker(destinationPoint.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
}
}
fun tick() {
entity.lookControl.setLookAt(lookTarget.currentPosition())
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain
/**
* A simple enum representing the status of a movement action.
*
* @author Hiroku
* @since December 29th, 2025
*/
enum class MovementStatus {
/** Represents when the entity is still en route to the destination. */
PENDING,
/** Represents when the entity has successfully reached the destination. */
COMPLETED,
/**
* Represents when the entity has failed to reach the destination. This can be due to
* a change in surroundings or taking too long or all sorts of things.
*/
FAILED
}
@@ -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.ai.biggerbrain
import net.minecraft.world.entity.ai.memory.WalkTarget
/**
* A wrapping around logic that maintains an entity's journey towards a specific destination. Mainly used by fixations
* to keep track of when pathing is done, and whether it failed or succeeded. Can also be told to stop to do any
* necessary clean up. Implemented only by [RegularMovementTracker] which has me questioning if it should be an interface
* or just a simple class like the [LookingTracker].
*
* @author Hiroku
* @since December 29th, 2025
*/
interface MovementTracker {
val walkTarget: WalkTarget
/** The last known status of this movement tracker. */
var lastStatus: MovementStatus
/** Performs a tick of movement tracking logic. Returns the current [MovementStatus]. */
fun tick(): MovementStatus
/** Stops the movement and does any necessary clean up. */
fun stop()
}
@@ -0,0 +1,209 @@
/*
* 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.ai.biggerbrain
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.pokemon.ai.OmniPathNodeMaker
import com.cobblemon.mod.common.util.deleteNode
import java.util.function.Predicate
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.ai.util.DefaultRandomPos
import net.minecraft.world.level.pathfinder.Path
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
/**
* The default movement tracker used for tracking movement towards a [WalkTarget]. This is pretty much entirely
* just the code for FollowWalkTargetTask from Minecraft brain stuff.
*
* @author Hiroku
* @since December 29th, 2025
*/
class RegularMovementTracker(
val entity: PathfinderMob,
override val walkTarget: WalkTarget
) : MovementTracker {
companion object {
/**
* Attempts to create a tracker for the given entity and target. Returns null if the path
* couldn't be created.
*/
fun attempt(entity: PathfinderMob, walkTarget: WalkTarget): RegularMovementTracker? {
return RegularMovementTracker(entity, walkTarget).takeIf { it.isValid }
}
}
var isValid = false
private set
var started = false
override var lastStatus: MovementStatus = MovementStatus.PENDING
private var path: Path? = null
private var lastKnownTargetPos: BlockPos? = null
init {
val hasReached = hasReached(entity, walkTarget)
if (!hasReached && hasFinishedPath(entity, walkTarget)) {
this.lastKnownTargetPos = walkTarget.target.currentBlockPosition()
isValid = true
} else {
lastStatus = MovementStatus.FAILED
}
}
override fun tick(): MovementStatus {
val path = path
val status = if (path == null || isTargetSpectator(walkTarget)) {
stop(entity)
MovementStatus.FAILED
} else if (hasReached(entity, walkTarget)) {
stop(entity)
MovementStatus.COMPLETED
} else if (started && entity.navigation.isDone) {
stop(entity)
MovementStatus.FAILED
} else {
if (!started) {
startOnPath(entity)
started = true
}
tick(entity)
MovementStatus.PENDING
}
this.lastStatus = status
return status
}
fun startOnPath(entity: PathfinderMob) {
entity.brain.setMemory(MemoryModuleType.PATH, this.path)
entity.navigation.moveTo(this.path, walkTarget.speedModifier.toDouble())
}
override fun stop() {
stop(entity)
}
fun stop(entity: PathfinderMob) {
entity.navigation.stop()
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
entity.brain.eraseMemory(MemoryModuleType.PATH)
}
private fun tick(entity: PathfinderMob) {
val path = entity.navigation.path
if (this.path !== path) {
this.path = path
this.startOnPath(entity)
}
if (path != null && lastKnownTargetPos != null) {
if (walkTarget.target.currentBlockPosition().distSqr(lastKnownTargetPos!!) > 4.0) {
// The target has moved away from where we are pathing, try pathing to its new location
if (hasFinishedPath(entity, walkTarget)) {
lastKnownTargetPos = walkTarget.target.currentBlockPosition()
startOnPath(entity)
} else {
// Uh oh, we can't get closer? Maybe next tick will be better?
}
}
}
}
private fun withNodeFilter(
entity: PathfinderMob,
nodeFilter: Predicate<PathType>,
block: () -> Unit) {
val nodeEvaluator = entity.navigation.nodeEvaluator
if (nodeEvaluator is OmniPathNodeMaker) {
nodeEvaluator.nodeFilter = nodeFilter
block()
nodeEvaluator.nodeFilter = Predicate { true }
} else {
block()
}
}
private fun hasFinishedPath(entity: PathfinderMob, walkTarget: WalkTarget): Boolean {
val blockPos = walkTarget.target.currentBlockPosition()
this.path = generatePath(entity, walkTarget, blockPos)
val hasReached = hasReached(entity, walkTarget)
if (!hasReached && path == null) {
// Desperate attempt to still path to the destination by going somewhere in the vicinity
val vec3d = DefaultRandomPos.getPosTowards(
entity,
10,
7,
Vec3.atBottomCenterOf(blockPos),
Math.PI / 2F
)
if (vec3d != null) {
this.path = generatePath(entity, walkTarget, BlockPos.containing(vec3d))
return this.path != null
}
} else if (!hasReached) {
return true
}
return false
}
private fun generatePath(
entity: PathfinderMob,
walkTarget: WalkTarget,
target: BlockPos
): Path? {
var path: Path? = null
if (walkTarget is CobblemonWalkTarget) {
withNodeFilter(
entity = entity,
nodeFilter = walkTarget.nodeTypeFilter,
) {
var resolvedPath = entity.navigation.createPath(
walkTarget.target.currentBlockPosition(),
0
)
if (resolvedPath != null) {
// A CobblemonWalkTarget may have a destinationNodeTypeFilter, which is used to filter the end node of the path.
// Mainly useful for when the thing setting the target did it a bit crudely or didn't know how far the navigator
// was going to get, and might land on something that is technically traversable by the entity but it doesn't
// fit the intention of the task. Example, a little bird wandering might end up finishing on a flight node,
// but it doesn't really want to fly in place so it'd prefer not ending there.
while (resolvedPath.endNode?.type?.let { walkTarget.destinationNodeTypeFilter(it) } == false) {
resolvedPath.deleteNode(resolvedPath.nodeCount - 1)
}
if (resolvedPath.nodeCount == 0) {
resolvedPath = null
}
}
path = resolvedPath
}
} else {
path = entity.navigation.createPath(target, 0)
}
return path
}
private fun hasReached(entity: PathfinderMob, walkTarget: WalkTarget): Boolean {
return walkTarget.target.currentBlockPosition().distManhattan(entity.blockPosition()) <= walkTarget.closeEnoughDist
}
fun isTargetSpectator(target: WalkTarget): Boolean {
val lookTarget = target.target
return if (lookTarget is EntityTracker) {
lookTarget.entity.isSpectator
} else {
false
}
}
}
@@ -0,0 +1,223 @@
/*
* 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.ai.biggerbrain
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl.WanderCenter
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.MAX_LOOK_DOWN_DISTANCE
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.entity.ai.CobblemonRandomSurfacePos
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorUtils
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.ai.util.HoverRandomPos
import net.minecraft.world.entity.ai.util.LandRandomPos
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathType
/**
* A type of wandering behaviour for an entity. I'm not sure if we keep this.
*
* @author Hiroku
* @since December 29th, 2025
*/
enum class WanderType(
val isValid: (PathfinderMob) -> Boolean,
val choosePosition: (PathfinderMob, minimumHeight: Int, maximumHeight: Int, horizontalRange: Int, verticalRange: Int) -> BlockPos?,
val createWalkTarget: (PathfinderMob, position: BlockPos, avoidsTargetingAir: Boolean, speedMultiplier: Float) -> WalkTarget?,
) {
WATER(
isValid = { it.isInWater },
choosePosition = choosePosition@{ entity, _, _, horizontalRange, verticalRange ->
var pos = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange, verticalRange)?.let(BlockPos::containing)
?: return@choosePosition null
// Move target pos down so that we don't move out of water too much
val requiredDepth = ceil(0.5 + entity.eyeHeight)
// Get the current depth of the block
var depth = 1
var testPos = pos.above()
var blockState = entity.level().getBlockState(testPos)
while (blockState.fluidState.type == Fluids.WATER) {
depth++
testPos = testPos!!.above()
blockState = entity.level().getBlockState(testPos)
}
if (depth < requiredDepth) {
// Iterate down until we reach the desired min depth or we reach a non water block
pos = pos.below()
blockState = entity.level().getBlockState(pos)
while (blockState.fluidState.type == Fluids.WATER && depth < requiredDepth && pos.y > entity.level().minBuildHeight) {
depth++
pos = pos.below()
blockState = entity.level().getBlockState(pos)
}
}
return@choosePosition if (depth < requiredDepth) null else pos
},
createWalkTarget = { _, pos, _, speedMultiplier ->
CobblemonWalkTarget(
pos = pos,
nodeTypeFilter = { it == PathType.WATER || it == PathType.WATER_BORDER },
speedModifier = speedMultiplier,
completionRange = 0
)
}
),
LAND(
isValid = { entity -> !entity.isInWater && !entity.isInLava },
choosePosition = choosePosition@{ entity, minimumHeight, maximumHeight, horizontalRange, verticalRange ->
val targetVec = when {
maximumHeight != -1 -> {
HoverRandomPos.getPos(
entity,
horizontalRange,
verticalRange,
entity.random.nextFloat() - 0.5,
entity.random.nextFloat() - 0.5,
Math.PI.toFloat(),
maximumHeight,
minimumHeight
)
}
entity.isUnderWater && entity.canBreatheUnderwater() && !((entity as OmniPathingEntity).canSwimInWater()) -> {
BehaviorUtils.getRandomSwimmablePos(
entity,
horizontalRange,
verticalRange
)
}
entity is OmniPathingEntity && (entity.canWalkOnWater() || entity.canWalkOnLava()) -> {
CobblemonRandomSurfacePos.getPos(
entity,
horizontalRange,
verticalRange
)
}
else -> {
LandRandomPos.getPos(
entity,
horizontalRange,
verticalRange
)
}
}
val pos = targetVec?.let(BlockPos::containing) ?: return@choosePosition null
LAND.applyHeightConstraints(pos, minimumHeight, maximumHeight, entity.level() as ServerLevel)
},
createWalkTarget = { entity, pos, avoidsTargetingAir, speedMultiplier ->
CobblemonWalkTarget(
pos = pos,
speedModifier = speedMultiplier,
completionRange = 0,
nodeTypeFilter = { nodeType ->
entity.canBreatheUnderwater() || nodeType !in listOf(
PathType.WATER,
PathType.WATER_BORDER
)
},
destinationNodeTypeFilter = { nodeType ->
!avoidsTargetingAir || nodeType !in listOf(
PathType.OPEN
)
}
)
}
)
;
fun generateWalkTarget(
entity: PathfinderMob,
maxAttempts: Int,
center: WanderCenter?,
minimumHeight: Int,
maximumHeight: Int,
horizontalRange: Int,
verticalRange: Int,
avoidTargetingAir: Boolean,
speedMultiplier: Float
): WalkTarget? {
var attempts = 0
var pos: BlockPos? = null
while (attempts++ < maxAttempts && pos == null) {
pos = choosePosition(entity, minimumHeight, maximumHeight, horizontalRange, verticalRange)
?: return null
if (!isSuitable(center, pos)) {
pos = null
}
}
if (pos == null) {
return null
}
return createWalkTarget(entity, pos, avoidTargetingAir, speedMultiplier)
}
private fun isSuitable(center: WanderCenter?, position: BlockPos): Boolean {
val center = center ?: return true
val centerPos = BlockPos(center.x.toInt(), center.y.toInt(), center.z.toInt())
val distance = sqrt(centerPos.distSqr(position))
val suitable = distance >= center.minRange && distance <= center.maxRange
return suitable
}
private fun applyHeightConstraints(
pos: BlockPos,
minimumHeight: Int,
maximumHeight: Int,
world: ServerLevel
): BlockPos {
if (minimumHeight <= 0 && maximumHeight == -1) {
return pos // It ain't a hoverer
}
val block = world.getBlockState(pos)
val altitude = if (!block.isAir) {
0
} else {
(1..MAX_LOOK_DOWN_DISTANCE).firstOrNull {
val newPos = pos.below(it)
if (!world.getBlockState(newPos).isAir) {
return@firstOrNull true
} else if (newPos.y <= world.minBuildHeight) {
// Don't adjust downward into the void (Mostly if we're in the end)
// This doesn't stop fliers from wandering off end islands,
// but it does stop them from diving directly into the void.
return pos
}
return@firstOrNull false
} ?: MAX_LOOK_DOWN_DISTANCE
}
if (altitude > maximumHeight && maximumHeight >= 0) {
val excess = altitude - maximumHeight
val excessFromMinimum = altitude - minimumHeight
val correction = world.random.nextIntBetweenInclusive(excess, excessFromMinimum)
return pos.atY(pos.y - correction)
} else if (altitude < minimumHeight && minimumHeight >= 0) {
val deficit = minimumHeight - altitude
val deficitFromMaximum = maximumHeight - altitude
val correction = world.random.nextIntBetweenInclusive(deficit, deficitFromMaximum)
return pos.atY(pos.y + correction)
} else {
return pos
}
}
}
@@ -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.ai.biggerbrain.distraction
import net.minecraft.world.damagesource.DamageSource
/**
* Something that may distract an entity from its current fixation. The majority of fixations
* will rely on the types of distraction to determine if they should be distracted.
*
* @author Hiroku
* @since December 29th, 2025
*/
open class Distraction(val types: Set<DistractionType>) {
constructor(vararg types: DistractionType) : this(types.toSet())
val isIdleBoredom = DistractionType.IDLE_BOREDOM in types
val isForce = DistractionType.FORCE in types
val isComfort = DistractionType.COMFORT in types
val isDamage = DistractionType.DAMAGE in types
fun hasAnyOf(vararg distractionTypes: DistractionType): Boolean {
return distractionTypes.any { it in types }
}
companion object {
val FORCE = Distraction(DistractionType.FORCE)
}
}
/** Reusable basic distraction for a COMFORT type of distraction. */
val comfortDistraction = Distraction(DistractionType.COMFORT)
/** Reusable basic distraction for an IDLE_BOREDOM type of distraction. */
val idleBoredomDistraction = Distraction(DistractionType.IDLE_BOREDOM)
open class HurtDistraction(val damageSource: DamageSource) : Distraction(DistractionType.DAMAGE)
@@ -0,0 +1,28 @@
/*
* 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.ai.biggerbrain.distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
/**
* A categorisation of distractions that can be attempted on an [EntityFixation].
*
* @author Hiroku
* @since December 29th, 2025
*/
enum class DistractionType {
/** Represents a distraction that the entity is not particularly motivated by but would consider doing out of boredom. */
IDLE_BOREDOM,
/** Represents a distraction that is for the sake of the entity's comfort because it isn't comfortable where it is. */
COMFORT,
/** Represents a distraction motivated by being hurt. */
DAMAGE,
/** A forceful distraction. Just about everything should respect this but it's conceivable that something not. */
FORCE,
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import net.minecraft.world.entity.LivingEntity
/**
* A fixation that rejects all non-force distractions and does nothing, lasting until
* the provided [condition] is no longer true.
*
* @author Hiroku
* @since January 16th, 2026
*/
open class ConditionalLockFixation(
val description: String,
val condition: () -> Boolean
): EntityFixation, LockFixation {
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
override fun getDescription(entity: LivingEntity) = description
override fun tick(entity: LivingEntity, brain: BiggerBrain) = condition()
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import net.minecraft.world.entity.LivingEntity
/**
* A fixation that rejects all non-force distractions and does nothing for some
* number of ticks.
*
* @author Hiroku
* @since January 16th, 2026
*/
open class DurationLockFixation(
val description: String,
var durationTicks: Int
) : EntityFixation, LockFixation {
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
override fun getDescription(entity: LivingEntity) = description.replace("{ticks}", durationTicks.toString())
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
return --durationTicks > 0
}
}
@@ -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.ai.biggerbrain.fixation
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import net.minecraft.world.entity.LivingEntity
/**
* An entity's fixation, representing a task that takes a central role in the entity's behaviour
* and excludes all other possible fixations.
*
* The role of the fixation is to do an action which is the primary focus of the entity at that time,
* and which may be distracted from under some circumstances. An entity may be fixated on moving from
* A to B, and then be distracted upon being hurt. Or maybe it's an attack fixation, in which case
* damage doesn't distract it at all.
*
* The difference between a fixation and an objective is that objectives are capable of running in parallel,
* and a fixation may even be in the pursuit of an objective, but the fixation is central.
*
* @author Hiroku
* @since December 28th, 2025
*/
interface EntityFixation {
/** Returns true if the given distraction can stop this fixation. */
fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean
/** Notifies the fixation that it has been distracted by the given distraction so it can do clean up. */
fun onDistract(distraction: Distraction, entity: LivingEntity) {}
/** Continues doing whatever the fixation does. Returns true if the fixation should continue. */
fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean
/** Gets a current description of the fixation for the debug screen. */
fun getDescription(entity: LivingEntity): String
}
@@ -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.ai.biggerbrain.fixation
/**
* Represents a fixation that blocks battling, capture, etc. for Pokémon. This is
* used to control the isBusy property on Pokémon.
*
* @author Hiroku
* @since January 16th, 2026
*/
interface LockFixation
@@ -0,0 +1,47 @@
/*
* 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.ai.biggerbrain.fixation
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
import net.minecraft.world.entity.LivingEntity
/**
* A simple fixation that uses both a looking tracker and a movement tracker.
*
* @author Hiroku
* @since December 29th, 2025
*/
open class LookAndMoveFixation(
/** Distraction types that will be respected. */
val distractionTypes: Set<DistractionType>,
/** How to describe the fixation in the debug overlay. */
val description: String,
override val lookingTracker: LookingTracker,
override val movementTracker: MovementTracker
): EntityFixation, LookingTrackerFixation, MovementTrackerFixation {
override fun getDescription(entity: LivingEntity) = description
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.types.any(distractionTypes::contains)
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
lookingTracker.tick()
return tickMovementFixation() == MovementStatus.PENDING
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
/**
* Applied to fixations which may have looking associated with them. This is useful
* in the case that an objective needs to know if the current fixation intends on (or is)
* controlling their looking direction.
*
* Example: An objective that makes Pokémon bob their heads up and down while music is playing.
* Said objective would not warrant a full fixation, but would still like to ensure that the
* current fixation does not intend on using a looking tracker.
*
* @author Hiroku
* @since December 29th, 2025
*/
interface LookingTrackerFixation {
val lookingTracker: LookingTracker?
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
/**
* Applied to fixations which may have movement associated with them. This is useful
* in the case that an objective needs to know if the current fixation intends on (or is)
* controlling their movement.
*
* @author Hiroku
* @since December 29th, 2025
*/
interface MovementTrackerFixation {
/** The movement tracker at the moment. */
val movementTracker: MovementTracker?
/** Ticks the movement tracker in a way that skips finalized movement trackers. */
fun tickMovementFixation(): MovementStatus? {
val movementTracker = this.movementTracker ?: return null
return if (movementTracker.lastStatus != MovementStatus.PENDING) {
movementTracker.lastStatus
} else {
movementTracker.tick()
}
}
}
@@ -0,0 +1,34 @@
/*
* 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.ai.biggerbrain.fixation
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import net.minecraft.world.entity.LivingEntity
class SimpleMoveFixation(
override val movementTracker: MovementTracker,
private val description: String
) : EntityFixation, MovementTrackerFixation {
override fun getDescription(entity: LivingEntity) = description
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.isForce || distraction.isDamage || movementTracker.lastStatus == MovementStatus.COMPLETED
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
return tickMovementFixation() == MovementStatus.PENDING
}
}
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.player.Player
class AttackEggThiefTask : Objective {
private var remainingCooldownTicks = 0
private var lastThiefId: java.util.UUID? = null
override fun getDescription(entity: LivingEntity) = "Attack egg thief"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
val eggThief = brain.getMemory(CobblemonMemories.EGG_THIEF) as? Player ?: run {
lastThiefId = null
return ObjectiveStatus.INACTIVE
}
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
if (remainingCooldownTicks > 0 && lastThiefId == eggThief.uuid) {
remainingCooldownTicks--
return ObjectiveStatus.ACTIVE
}
brain.sendDebugMessage("[AttackEggThief] Attacking egg thief ${eggThief.name.string}.")
entity.cry()
(entity.level() as? ServerLevel)?.sendParticles(
net.minecraft.core.particles.ParticleTypes.ANGRY_VILLAGER,
entity.getRandomX(1.0),
entity.getRandomY() + 0.5,
entity.getRandomZ(1.0),
4 + kotlin.random.Random.nextInt(2),
0.2 * entity.random.nextGaussian(),
0.1 * entity.random.nextGaussian(),
0.2 * entity.random.nextGaussian(),
0.02
)
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
brain.setMemory(MemoryModuleType.ANGRY_AT, eggThief.uuid)
brain.setMemory(MemoryModuleType.ATTACK_TARGET, eggThief)
lastThiefId = eggThief.uuid
remainingCooldownTicks = 40
return ObjectiveStatus.ACTIVE
}
}
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective.Companion.OBJECTIVE_TAG_WANDERING
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
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.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
class AttackTarget(
private val speedMultiplier: Expression = "1.0".asExpression(),
private val attackRange: Expression = "2.0".asExpression(),
private val attackCooldownTicks: Int = 20
) : Objective {
private var movementTask: EntityFixation? = null
private var remainingCooldownTicks = 0
override val observedMemories = listOf(MemoryModuleType.ATTACK_TARGET)
override fun getDescription(entity: LivingEntity) = "Attack target"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
if (entity !is PathfinderMob) return ObjectiveStatus.INVALID
val target = brain.getMemory(MemoryModuleType.ATTACK_TARGET) as? LivingEntity
?: return ObjectiveStatus.INACTIVE
activeObjectiveTags.add(OBJECTIVE_TAG_WANDERING)
if (target.isRemoved || target.isDeadOrDying) {
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
return ObjectiveStatus.INACTIVE
}
if (target is Player && (target.isCreative || target.isSpectator)) {
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
return ObjectiveStatus.INACTIVE
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
}
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val range = runtime.resolveFloat(attackRange)
val speed = runtime.resolveFloat(speedMultiplier)
if (entity.distanceToSqr(target) <= range * range && entity.hasLineOfSight(target)) {
if (remainingCooldownTicks <= 0) {
if (entity.doHurtTarget(target)) {
entity.swing(InteractionHand.MAIN_HAND)
}
remainingCooldownTicks = attackCooldownTicks
}
return ObjectiveStatus.ACTIVE
}
if (brain.isFixation(movementTask)) {
return ObjectiveStatus.FIXATED
}
if (!brain.distract(Distraction.FORCE)) return ObjectiveStatus.BLOCKED
val walkTarget = WalkTarget(EntityTracker(target, true), speed, 1)
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget) ?: return ObjectiveStatus.ACTIVE
movementTask = brain.fixate(SimpleMoveFixation(movementTracker, "Attack target"))
return ObjectiveStatus.ACTIVE
}
override fun <T> onMemoryChanged(
entity: LivingEntity,
brain: BiggerBrain,
memory: MemoryModuleType<T>,
value: T?
) {
if (memory == MemoryModuleType.ATTACK_TARGET && value == null) {
movementTask = null
}
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("1.0".asExpression())
val attackRange: ExpressionOrEntityVariable = Either.left("2.0".asExpression())
val attackCooldownTicks: ExpressionOrEntityVariable = Either.left("20".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(speedMultiplier, attackRange, attackCooldownTicks).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return AttackTarget(
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
attackRange = attackRange.asSimplifiedExpression(entity),
attackCooldownTicks = behaviourConfigurationContext.runtime.resolveInt(
attackCooldownTicks.asSimplifiedExpression(entity)
)
)
}
}
}
@@ -0,0 +1,183 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.api.nest.WildNestType
import com.cobblemon.mod.common.api.nest.WildNestTypes
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.WildNestBlock
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class BreedAtNestTask : Objective {
override fun getDescription(entity: LivingEntity) = "Breed at nest"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
val paternalNestLoc = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
if (entity.pokemon.isWild() && paternalNestLoc != null) {
brain.sendDebugMessage("[BreedAtNest] Wild Pokémon already has paternal nest; skipping.")
return ObjectiveStatus.INVALID
}
val nestLocation = brain.getMemory(CobblemonMemories.NEST_LOCATION) ?: return ObjectiveStatus.INVALID
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INVALID
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
val nestEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity ?: return ObjectiveStatus.INVALID
val allowedTypes = allowedNestTypes(entity, mate)
val nestType = (nestEntity.blockState.block as? WildNestBlock)?.type ?: return ObjectiveStatus.INVALID
if (!isNestTypeAllowed(nestType, entity, allowedTypes)) {
brain.sendDebugMessage("[BreedAtNest] Nest type $nestType not allowed. Allowed=$allowedTypes.")
return ObjectiveStatus.INVALID
}
val isNestWaterlogged = nestEntity.blockState.getOptionalValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED).orElse(false)
if (!isWaterGroupCompatible(entity, mate, isNestWaterlogged)) {
brain.sendDebugMessage("[BreedAtNest] Water-group mismatch for nest at $nestLocation (waterlogged=$isNestWaterlogged).")
return ObjectiveStatus.INVALID
}
val targetVec = Vec3.atCenterOf(nestLocation)
if (entity.distanceToSqr(targetVec) > 2.0 && mate.distanceToSqr(targetVec) > 2.0) {
brain.sendDebugMessage("[BreedAtNest] Parents not in range of nest $nestLocation.")
return ObjectiveStatus.INACTIVE
}
if (nestEntity.getItem(0) != ItemStack.EMPTY) {
brain.sendDebugMessage("[BreedAtNest] Nest at $nestLocation already has an egg.")
return ObjectiveStatus.INVALID
}
val parentOne = entity.pokemon
val parentTwo = mate.pokemon
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
val form = BreedingLogicManager.calculateForm(parentOne, parentTwo)
val egg = breedingResult.pokemon?.let { result ->
Egg(
species = result.species.resourceIdentifier,
parentOne = parentOne,
parentTwo = parentTwo,
form = form.name.lowercase(),
timeToHatch = 6000F
)
} ?: return ObjectiveStatus.INVALID
nestEntity.setItem(egg.createEggItemStack())
brain.sendDebugMessage("[BreedAtNest] Egg placed at $nestLocation.")
if (Cobblemon.config.canDestinyKnotBreak) {
if (entity.pokemon.heldItem.item == CobblemonItems.DESTINY_KNOT.asItem())
entity.pokemon.heldItemNoCopy().damageValue += 1
if (mate.pokemon.heldItem.item == CobblemonItems.DESTINY_KNOT.asItem())
mate.pokemon.heldItemNoCopy().damageValue += 1
}
if (nestEntity.ownerUUID == null) {
nestEntity.parents = Pair(entity.uuid, mate.uuid)
nestEntity.nestClaimedBy = Pair(entity.uuid, mate.uuid)
}
parentOne.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentOne)
parentTwo.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentTwo)
world.sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
entity.getRandomX(1.0),
entity.getRandomY() + 0.5,
entity.getRandomZ(1.0),
4 + kotlin.random.Random.nextInt(2),
0.2 * entity.random.nextGaussian(),
0.1 * entity.random.nextGaussian(),
0.2 * entity.random.nextGaussian(),
0.02
)
world.sendParticles(
net.minecraft.core.particles.ParticleTypes.HEART,
mate.getRandomX(1.0),
mate.getRandomY() + 0.5,
mate.getRandomZ(1.0),
4 + kotlin.random.Random.nextInt(2),
0.2 * mate.random.nextGaussian(),
0.1 * mate.random.nextGaussian(),
0.2 * mate.random.nextGaussian(),
0.02
)
brain.setMemory(CobblemonMemories.PATERNAL_NEST_LOCATION, nestLocation)
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
mate.biggerBrain.setMemory(CobblemonMemories.PATERNAL_NEST_LOCATION, nestLocation)
mate.biggerBrain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
world.players().forEach { player ->
player.displayClientMessage("An egg has been laid in the nest!".green(), true)
}
return ObjectiveStatus.ACTIVE
}
private fun allowedNestTypes(entity: PokemonEntity, mate: PokemonEntity): Set<WildNestType> {
val types = WildNestTypes.combinedTypes(entity, mate)
val mutable = types.toMutableSet()
if (!entity.pokemon.isWild() && !mate.pokemon.isWild()) {
mutable.add(WildNestType.PLAYER)
}
return mutable
}
private fun isNestTypeAllowed(
nestType: WildNestType,
entity: PokemonEntity,
allowedTypes: Set<WildNestType>
): Boolean {
if (nestType == WildNestType.PLAYER) {
return !entity.pokemon.isWild() && allowedTypes.contains(WildNestType.PLAYER)
}
return allowedTypes.contains(nestType)
}
private fun isWaterGroupCompatible(
entity: PokemonEntity,
mate: PokemonEntity,
isNestWaterlogged: Boolean
): Boolean {
val waterEggGroups = setOf("water_1", "water_2", "water_3")
val entityEggGroups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
val mateEggGroups = mate.pokemon.species.eggGroups.map { it.name.lowercase() }
val entityHasWater = entityEggGroups.any { it in waterEggGroups }
val mateHasWater = mateEggGroups.any { it in waterEggGroups }
val entityOnlyWater = entityEggGroups.all { it in waterEggGroups }
val mateOnlyWater = mateEggGroups.all { it in waterEggGroups }
return if (isNestWaterlogged) {
entityHasWater && mateHasWater
} else {
!(entityOnlyWater || mateOnlyWater)
}
}
}
@@ -0,0 +1,98 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.api.nest.WildNestTypes
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.WildNestBlock
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.phys.Vec3
import kotlin.jvm.optionals.getOrNull
class BuildNestTask : Objective {
override fun getDescription(entity: LivingEntity) = "Build nest"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
if (brain.getMemory(CobblemonMemories.NEST_LOCATION) != null) {
brain.sendDebugMessage("[BuildNest] Nest already assigned; skipping.")
return ObjectiveStatus.INVALID
}
if (brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) != null) {
brain.sendDebugMessage("[BuildNest] Paternal nest already set; skipping.")
return ObjectiveStatus.INVALID
}
if (brain.hasMemoryValue(net.minecraft.world.entity.ai.memory.MemoryModuleType.ATTACK_TARGET)) {
brain.sendDebugMessage("[BuildNest] In combat; skipping.")
return ObjectiveStatus.INVALID
}
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INVALID
val combinedTypes = WildNestTypes.combinedTypes(entity, mate)
if (combinedTypes.isEmpty()) {
brain.sendDebugMessage("[BuildNest] No combined nest types; skipping.")
return ObjectiveStatus.INVALID
}
val idealNestPos: BlockPos = brain.getMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
?: return ObjectiveStatus.INVALID
val nestCenter = Vec3.atCenterOf(idealNestPos)
if (brain.getMemory(CobblemonMemories.READY_TO_BUILD_NEST) != true) {
brain.sendDebugMessage("[BuildNest] Not ready to build; skipping.")
return ObjectiveStatus.INVALID
}
if (entity.distanceToSqr(nestCenter) > 2.0 || mate.distanceToSqr(nestCenter) > 2.0) {
brain.sendDebugMessage("[BuildNest] Parents not in range of ideal nest $idealNestPos.")
return ObjectiveStatus.INACTIVE
}
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
val placement = WildNestTypes.resolvePlacement(
combinedTypes,
world.getBlockState(idealNestPos.below()),
world.getFluidState(idealNestPos)
) ?: return ObjectiveStatus.INVALID
val nestBlock = WildNestTypes.blockFor(placement.type)
val nestState = nestBlock.defaultBlockState()
.setValue(WildNestBlock.VARIANT, placement.variant)
.setValue(BlockStateProperties.WATERLOGGED, placement.waterlogged)
world.setBlock(idealNestPos, nestState, 3)
(world.getBlockEntity(idealNestPos) as NestBlockEntity).nestClaimedBy = Pair(entity.uuid, mate.uuid)
brain.sendDebugMessage("[BuildNest] Built nest at $idealNestPos with ${placement.type}/${placement.variant}.")
brain.eraseMemory(CobblemonMemories.READY_TO_BUILD_NEST)
brain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
mate.biggerBrain.eraseMemory(CobblemonMemories.READY_TO_BUILD_NEST)
mate.biggerBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
world.players().forEach { player ->
player.displayClientMessage("A Pokémon has built a nest!".green(), true)
}
return ObjectiveStatus.ACTIVE
}
}
@@ -0,0 +1,146 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.resolveFloat
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.level.GameRules
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.predicate.BlockStatePredicate
/**
* The baa's eat the graa's
*
* @author Hiroku
* @since January 9th, 2025
*/
class EatGrass(
val cooldownTicks: Int,
val eatGrassChance: Float
) : Objective {
var fixation: EntityFixation? = null
val grassPredicate = BlockStatePredicate.forBlock(Blocks.GRASS_BLOCK)
var remainingCooldownTicks = cooldownTicks
override fun getDescription(entity: LivingEntity) = "Eat grass"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (brain.isFixation(fixation)) {
return ObjectiveStatus.FIXATED
} else if (brain.fixation != null) {
return ObjectiveStatus.BLOCKED
} else if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.ACTIVE
}
val world = entity.level() as ServerLevel
entity.pokemon.getFeature<FlagSpeciesFeature>(DataKeys.HAS_BEEN_SHEARED)?.enabled?.takeIf { it } ?: run {
return ObjectiveStatus.INACTIVE
}
if (world.random.nextFloat() > eatGrassChance) {
return ObjectiveStatus.ACTIVE
} else {
val blockPos = entity.blockPosition()
// Are we not even on a grass block? Alright don't bother
if (!grassPredicate.test(world.getBlockState(blockPos)) && !world.getBlockState(blockPos.below()).`is`(Blocks.GRASS_BLOCK)) {
return ObjectiveStatus.ACTIVE
}
fixation = brain.fixate(EatGrassFixation(this))
entity.playAnimation("eat")
world.broadcastEntityEvent(entity, 10.toByte())
return ObjectiveStatus.FIXATED
}
}
class EatGrassFixation(val objective: EatGrass) : EntityFixation {
var timer = 40
override fun getDescription(entity: LivingEntity) = "Eating grass"
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isComfort || distraction.isDamage || distraction.isForce
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
timer--
if (timer <= 0) {
entity as PokemonEntity
val world = entity.level() as ServerLevel
val blockPos = entity.blockPosition()
if (objective.grassPredicate.test(world.getBlockState(blockPos)) ) {
if (world.gameRules.getBoolean(GameRules.RULE_MOBGRIEFING)) {
world.destroyBlock(blockPos, false)
}
entity.ate()
objective.remainingCooldownTicks = objective.cooldownTicks
} else if (world.getBlockState(blockPos.below()).`is`(Blocks.GRASS_BLOCK)) {
val below = blockPos.below()
if (world.gameRules.getBoolean(GameRules.RULE_MOBGRIEFING)) {
world.levelEvent(2001, below, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()))
world.setBlock(below, Blocks.DIRT.defaultBlockState(), 2)
}
entity.ate()
objective.remainingCooldownTicks = objective.cooldownTicks
}
}
return timer > 0
}
}
class Config : ObjectiveConfig {
var cooldownTicks: ExpressionOrEntityVariable = Either.left("400".asExpression())
var eatGrassChance: ExpressionOrEntityVariable = Either.left("1/400".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
cooldownTicks,
eatGrassChance
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective? {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
val eatGrassChance = eatGrassChance.resolveFloat(behaviourConfigurationContext.runtime)
return if (eatGrassChance == 0F) {
null
} else {
EatGrass(
cooldownTicks = cooldownTicks,
eatGrassChance = eatGrassChance
)
}
}
}
}
@@ -0,0 +1,232 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.HurtDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LockFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* An objective that ensures the entity, whether it's a PokemonEntity or an NPCEntity, will pay attention to its
* opponents in battle. They will not move, and will look at its opponents. For Pokémon, they will focus on the
* targeted opponent if one exists; otherwise, they will randomly look at one of their opponent Pokémon. For NPCs,
* they will randomly look at one of their opponent Pokémon.
*
* If the entity gets hurt and [stopBattleIfHurt] is true, this objective stops the battle.
*
* @author Hiroku
* @since December 29th, 2025
*/
class FocusDuringBattle(
var stopBattleIfHurt: Boolean = true,
var minDurationTicks: Int = 40,
var maxDurationTicks: Int = 80
) : Objective {
var fixation: EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Look at battle opponents"
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
if (entity !is NPCEntity && entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
}
if (stopBattleIfHurt && brain.hasMemoryValue(MemoryModuleType.HURT_BY)) {
if (brain.isFixation(fixation)) {
if (entity is PokemonEntity) {
entity.battle?.stop()
} else if (entity is NPCEntity) {
entity.battleIds.mapNotNull { BattleRegistry.getBattle(it) }.forEach { it.stop() }
}
brain.distract(Distraction.FORCE)
}
return ObjectiveStatus.INACTIVE
}
val isInBattle = isInBattle(entity)
if (fixation == brain.fixation) {
if (isInBattle) {
// Ok this is fine
return ObjectiveStatus.FIXATED
} else {
// Alright my work here is done, going back to inactive.
fixation = null
brain.fixation = null
return ObjectiveStatus.INACTIVE
}
} else if (!isInBattle) {
// Ok we don't need to do anything
fixation = null
return ObjectiveStatus.INACTIVE
} else {
// We need to start focusing
if (brain.distract(Distraction.FORCE)) {
fixation = if (entity is NPCEntity) NPCFocusFixation(this) else PokemonFocusFixation(this)
brain.fixation = fixation
return ObjectiveStatus.FIXATED
} else {
return ObjectiveStatus.BLOCKED
}
}
}
override fun <T> onMemoryChanged(entity: LivingEntity, brain: BiggerBrain, memory: MemoryModuleType<T>, value: T?) {
if (brain.isFixation(fixation) && memory == MemoryModuleType.HURT_BY && value != null && stopBattleIfHurt) {
brain.distract(HurtDistraction(value as DamageSource))
}
}
fun isInBattle(entity: LivingEntity): Boolean {
return when (entity) {
is PokemonEntity -> entity.isBattling
is NPCEntity -> entity.isInBattle()
else -> false
}
}
class NPCFocusFixation(val objective: FocusDuringBattle) : EntityFixation, LookingTrackerFixation {
override var lookingTracker: LookingTracker? = null
var ticksUntilReacquireTarget: Int = 0
override fun getDescription(entity: LivingEntity): String {
return if (lookingTracker == null) {
"Looking for an opponent Pokémon to look at"
} else {
"Looking at targeted or opponent Pokémon"
}
}
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
fun acquireLookTarget(entity: NPCEntity, brain: BiggerBrain) {
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return
val randomBattlingPokemon = battlingPokemon.randomOrNull()?.let { (entity.level() as ServerLevel).getEntity(it) }
if (randomBattlingPokemon != null) {
lookingTracker = LookingTracker(entity, EntityTracker(randomBattlingPokemon, true))
}
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (entity !is NPCEntity) {
return false
}
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return false
val lookingTracker = lookingTracker
if (lookingTracker != null && lookingTracker.entity.uuid !in battlingPokemon) {
this.lookingTracker = null
ticksUntilReacquireTarget = 0
}
if (ticksUntilReacquireTarget <= 0) {
acquireLookTarget(entity, brain)
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
} else {
ticksUntilReacquireTarget--
}
lookingTracker?.tick()
return true
}
}
class PokemonFocusFixation(val objective: FocusDuringBattle) : EntityFixation, LookingTrackerFixation, LockFixation {
var ticksUntilReacquireTarget: Int = 0
override var lookingTracker: LookingTracker? = null
override fun getDescription(entity: LivingEntity): String {
return if (lookingTracker == null) {
"Looking for an opponent Pokémon to look at"
} else {
"Looking at targeted or opponent Pokémon"
}
}
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (entity !is PokemonEntity) {
return false
}
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return false
val targetedBattlePokemon = brain.getMemory(CobblemonMemories.TARGETED_BATTLE_POKEMON)
val lookingTracker = lookingTracker
// If there is a targetedBattlePokemon then that is the highest priority, and we should not look away
// Otherwise, look at one of the entities for a while and occasionally reacquire the target.
if (targetedBattlePokemon != null && (lookingTracker == null || lookingTracker.entity.uuid != targetedBattlePokemon)) {
// We should be targeting the targetedBattlePokemon
val targetedPokemon = (entity.level() as ServerLevel).getEntity(targetedBattlePokemon) ?: return false
this.lookingTracker = LookingTracker(entity, EntityTracker(targetedPokemon, true))
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
} else if (targetedBattlePokemon == null && ticksUntilReacquireTarget <= 0) {
// Choose someone to look at
val randomBattlingPokemon = battlingPokemon.randomOrNull()?.let { (entity.level() as ServerLevel).getEntity(it) }
if (randomBattlingPokemon != null) {
this.lookingTracker = LookingTracker(entity, EntityTracker(randomBattlingPokemon, true))
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
}
}
ticksUntilReacquireTarget--
this.lookingTracker?.tick()
return true
}
}
class Config : ObjectiveConfig {
var stopBattleIfHurt: ExpressionOrEntityVariable = Either.left("true".asExpression())
var minDurationTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
var maxDurationTicks: ExpressionOrEntityVariable = Either.left("80".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
stopBattleIfHurt,
minDurationTicks,
maxDurationTicks
).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
val stopBattleIfHurt = stopBattleIfHurt.resolveBoolean(behaviourConfigurationContext.runtime)
val minDurationTicks = minDurationTicks.resolveInt(behaviourConfigurationContext.runtime)
val maxDurationTicks = maxDurationTicks.resolveInt(behaviourConfigurationContext.runtime)
return FocusDuringBattle(
stopBattleIfHurt = stopBattleIfHurt,
minDurationTicks = minDurationTicks,
maxDurationTicks = maxDurationTicks
)
}
}
}
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveBoolean
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* Randomly looks at nearby entities out of curiosity. Very easily distracted once doing so.
*
* @author Hiroku
* @since December 29th, 2025
*/
class GoToSleep(
val onlyFromStatus: Expression = "false".asExpression(),
) : Objective {
var fixation: EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Sleeping"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (brain.isFixation(fixation)) {
return ObjectiveStatus.FIXATED
} else if (brain.fixation != null) {
fixation = null
return ObjectiveStatus.BLOCKED
}
// Don't sleep if we're not tired
brain.getMemory(CobblemonMemories.POKEMON_DROWSY)
?: return ObjectiveStatus.INACTIVE
val hasSleepStatus = entity.pokemon.status?.status === Statuses.SLEEP
if (entity.behaviour.resting.canSleep && entity.canSleepAt(entity.blockPosition().below()) || hasSleepStatus) {
if (!hasSleepStatus) {
if (mainThreadRuntime.resolveBoolean(onlyFromStatus)) {
return ObjectiveStatus.BLOCKED
}
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
}
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
entity.navigation.stop()
entity.brain.setActiveActivityToFirstValid(listOf(CobblemonActivities.POKEMON_SLEEPING_ACTIVITY))
entity.brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
// Become fixated
fixation = brain.fixate(
SleepFixation(
this,
null
)
)
return ObjectiveStatus.FIXATED
}
return ObjectiveStatus.BLOCKED
}
class SleepFixation(
val objective: GoToSleep,
override val movementTracker: MovementTracker?,
) : EntityFixation, MovementTrackerFixation {
override fun getDescription(entity: LivingEntity): String {
return "Sleeping"
}
override fun tickMovementFixation(): MovementStatus? {
return MovementStatus.PENDING
}
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
if (distraction.isIdleBoredom) {
return false
}
wakeUp(entity)
return true
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (!entity.brain.getMemorySafely(CobblemonMemories.POKEMON_DROWSY).orElse(false)) {
wakeUp(entity)
return false
}
return true
}
fun wakeUp(entity: LivingEntity) {
if (entity !is PokemonEntity) return
val hasSleepStatus = entity.pokemon.status?.status == Statuses.SLEEP
if (hasSleepStatus && entity.pokemon.storeCoordinates.get()?.store is PartyStore) {
return // You can't wake up if you're in the party with the sleep status.
}
if (hasSleepStatus) {
entity.pokemon.status = null
}
entity.brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
entity.brain.useDefaultActivity()
}
}
class Config : ObjectiveConfig {
val onlyFromStatus: ExpressionOrEntityVariable = Either.left("false".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
onlyFromStatus
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return GoToSleep(
onlyFromStatus = onlyFromStatus.asSimplifiedExpression(entity)
)
}
}
}
@@ -0,0 +1,199 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
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.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
class GuardNestTask : Objective {
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
private var remainingCooldownTicks = 0
private var patrolTarget: Vec3? = null
private var warnCooldownTicks = 0
private var attackCooldownTicks = 0
private var lastWarnedPlayerId: java.util.UUID? = null
private var lastAttackedPlayerId: java.util.UUID? = null
override fun getDescription(entity: LivingEntity) = "Guard nest"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
if (entity !is PathfinderMob) return ObjectiveStatus.INVALID
if (warnCooldownTicks > 0) warnCooldownTicks--
if (attackCooldownTicks > 0) attackCooldownTicks--
if (brain.isFixation(movementTask)) {
brain.sendDebugMessage("[GuardNest] Fixation active.")
return ObjectiveStatus.FIXATED
}
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
if (eggInNest != null && eggInNest == false) {
return ObjectiveStatus.INVALID
}
val nestLocation = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return ObjectiveStatus.INVALID
val nestCenter = Vec3.atCenterOf(nestLocation)
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
val visibleEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) as? NearestVisibleLivingEntities
val playerCandidates = if (visibleEntities != null) {
visibleEntities
.findAll { it is Player && !it.isSpectator && !(it as Player).isCreative }
.filterIsInstance<Player>()
} else {
val searchBox = AABB(
nestCenter.x,
nestCenter.y,
nestCenter.z,
nestCenter.x,
nestCenter.y,
nestCenter.z
).inflate(16.0)
entity.level().getEntitiesOfClass(Player::class.java, searchBox) { candidate ->
!candidate.isSpectator &&
!candidate.isCreative &&
!candidate.isInvisible &&
entity.hasLineOfSight(candidate)
}
}
val player = playerCandidates.firstOrNull()
val irritatedAtTarget = brain.getMemory(CobblemonMemories.IRRITATED_AT)
if (player != null) {
val playerDistanceSquared = player.position().distanceToSqr(nestCenter)
if (playerDistanceSquared <= ATTACK_RADIUS * ATTACK_RADIUS) {
if (attackCooldownTicks == 0 || lastAttackedPlayerId != player.uuid) {
brain.sendDebugMessage("[GuardNest] Escalation cry at player ${player.name.string}.")
entity.cry()
(entity.level() as? ServerLevel)?.sendParticles(
net.minecraft.core.particles.ParticleTypes.ANGRY_VILLAGER,
entity.getRandomX(1.0),
entity.getRandomY() + 0.5,
entity.getRandomZ(1.0),
4 + kotlin.random.Random.nextInt(2),
0.2 * entity.random.nextGaussian(),
0.1 * entity.random.nextGaussian(),
0.2 * entity.random.nextGaussian(),
0.02
)
attackCooldownTicks = ATTACK_COOLDOWN_TICKS
lastAttackedPlayerId = player.uuid
}
brain.sendDebugMessage("[GuardNest] Setting attack target to ${player.name.string}.")
brain.setMemory(MemoryModuleType.ANGRY_AT, player.uuid)
brain.setMemory(MemoryModuleType.ATTACK_TARGET, player)
return ObjectiveStatus.ACTIVE
}
if (playerDistanceSquared <= WARN_RADIUS * WARN_RADIUS) {
if (irritatedAtTarget == null) {
brain.setMemory(CobblemonMemories.IRRITATED_AT, player.uuid)
}
if (warnCooldownTicks == 0 || lastWarnedPlayerId != player.uuid) {
brain.sendDebugMessage("[GuardNest] Warning cry at player ${player.name.string}.")
entity.cry()
(entity.level() as? ServerLevel)?.sendParticles(
net.minecraft.core.particles.ParticleTypes.ANGRY_VILLAGER,
entity.getRandomX(1.0),
entity.getRandomY() + 0.5,
entity.getRandomZ(1.0),
4 + kotlin.random.Random.nextInt(2),
0.2 * entity.random.nextGaussian(),
0.1 * entity.random.nextGaussian(),
0.2 * entity.random.nextGaussian(),
0.02
)
warnCooldownTicks = WARN_COOLDOWN_TICKS
lastWarnedPlayerId = player.uuid
}
}
}
if (patrolTarget != null && entity.distanceToSqr(patrolTarget) <= 2.0) {
remainingCooldownTicks = 40 + entity.random.nextInt(40)
patrolTarget = null
return ObjectiveStatus.ACTIVE
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.ACTIVE
}
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
val validPosition = findPatrolPosition(world, nestCenter, entity, PATROL_MIN_RADIUS, PATROL_MAX_RADIUS)
?: return ObjectiveStatus.ACTIVE
patrolTarget = validPosition
val walkTarget = WalkTarget(validPosition, 1.0F, 1)
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget) ?: return ObjectiveStatus.ACTIVE
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
brain.sendDebugMessage("[GuardNest] Wandering near nest to $validPosition.")
movementTask = brain.fixate(SimpleMoveFixation(movementTracker, "Guard nest"))
return ObjectiveStatus.ACTIVE
}
private fun findPatrolPosition(
world: ServerLevel,
center: Vec3,
entity: PathfinderMob,
minRadius: Int,
maxRadius: Int
): Vec3? {
val random = world.random
repeat(20) {
val angle = random.nextDouble() * Math.PI * 2
val radius = minRadius + random.nextInt(maxRadius - minRadius + 1)
val offsetX = kotlin.math.cos(angle) * radius
val offsetZ = kotlin.math.sin(angle) * radius
val offsetY = random.nextDouble() * 2 - 1
val candidate = center.add(offsetX, offsetY, offsetZ)
val blockPos = BlockPos(candidate.x.toInt(), candidate.y.toInt(), candidate.z.toInt())
if (!world.getBlockState(blockPos).isSolidRender(world, blockPos) &&
entity.navigation.isStableDestination(blockPos)
) {
return Vec3.atCenterOf(blockPos)
}
}
return null
}
companion object {
private const val WARN_RADIUS = 12.0
private const val ATTACK_RADIUS = 8.0
private const val WARN_COOLDOWN_TICKS = 80
private const val ATTACK_COOLDOWN_TICKS = 40
private const val PATROL_MIN_RADIUS = 4
private const val PATROL_MAX_RADIUS = 8
}
}
@@ -0,0 +1,204 @@
/*
* 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.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.distanceTo
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.GameRules
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.CaveVines
import net.minecraft.world.level.block.SweetBerryBushBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
/**
* Paths to ripened sweet berry bushes AND glowberry vines with berries.
* Upon reaching the target harvests the berries and stashes one in its
* held item slot if possible.
* Requires NEARBY_SWEET_BERRY_BUSH memory
*
* @author JazzMcNade
* @since February 8th, 2026
*/
class HarvestSweetBerryBush(
val ticksBetweenAttempts: Int = 60,
val maxDistance: Expression = "14.0".asExpression(),
val speedMultiplier: Expression = "0.4".asExpression()
) : Objective {
var harvestTask: EntityFixation? = null
var remainingCooldownTicks = ticksBetweenAttempts
override fun getDescription(entity: LivingEntity) = "Harvesting sweet/glow berry"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (brain.isFixation(harvestTask)) {
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
}
val berryPosition = entity.biggerBrain.getMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH) ?: return ObjectiveStatus.INACTIVE
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.ACTIVE
}
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val maxDistance = mainThreadRuntime.resolveFloat(maxDistance)
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
val distance = entity.distanceTo(berryPosition)
if (distance < maxDistance) {
if (remainingCooldownTicks <= 0 && brain.distract(idleBoredomDistraction)) {
remainingCooldownTicks = ticksBetweenAttempts
val movementTracker = RegularMovementTracker.attempt(
entity = entity,
walkTarget = WalkTarget(
berryPosition,
speedMultiplier,
1
)
)
if (movementTracker != null) {
harvestTask = brain.fixate(
fixation = HarvestSweetBerryBushFixation(
entity = entity,
movementTracker = movementTracker
)
)
}
}
}
return ObjectiveStatus.ACTIVE
}
class HarvestSweetBerryBushFixation(
entity: PathfinderMob,
override val movementTracker: MovementTracker,
): EntityFixation, LookingTrackerFixation, MovementTrackerFixation {
override val lookingTracker: LookingTracker = LookingTracker(entity = entity, lookTarget = movementTracker.walkTarget.target)
override fun getDescription(entity: LivingEntity) = "Harvesting sweet/glow berry"
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.isDamage || distraction.isForce || (movementTracker.lastStatus == MovementStatus.COMPLETED && entity.brain.getMemorySafely(
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH).orElse(null) != null)
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
lookingTracker.tick()
movementTracker.tick()
if (movementTracker.lastStatus == MovementStatus.COMPLETED) {
val world = entity.level()
if (entity !is PokemonEntity) return false
if (!world.gameRules.getRule(GameRules.RULE_MOBGRIEFING).get()) {
// Not allowed to alter the world
return false
}
val blockPos = entity.brain.getMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH).orElse(null) ?: return false
val blockState = world.getBlockState(blockPos)
if (CaveVines.hasGlowBerries(blockState)) { // Glow Berries Case
CaveVines.use(entity, blockState, world, blockPos)
} else if (blockState.`is`(Blocks.SWEET_BERRY_BUSH)) { // Sweet Berries Case
blockState.setValue(SweetBerryBushBlock.AGE, 1)
var j: Int = 1 + entity.level().random.nextInt(2) + if (blockState.getValue<Int>(SweetBerryBushBlock.AGE) == 3) 1 else 0
val itemStack: ItemStack = entity.pokemon.heldItem
if (itemStack.isEmpty) {
entity.pokemon.swapHeldItem(ItemStack(Items.SWEET_BERRIES))
--j
}
if (j > 0) {
Block.popResource(entity.level(), blockPos, ItemStack(Items.SWEET_BERRIES, j))
}
entity.playSound(SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, 1.0f, 1.0f)
entity.level().setBlock(blockPos, blockState.setValue(SweetBerryBushBlock.AGE, 1) as BlockState, 2)
entity.level().gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(entity))
}
entity.brain.eraseMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH)
return false
} else if (movementTracker.lastStatus == MovementStatus.FAILED) {
// Cannot reach, give up for a awhile
entity.brain.setMemoryWithExpiry(CobblemonMemories.DISABLE_WALK_TO_BERRY_BUSH, true, 200)
entity.brain.eraseMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH)
return false
}
return true
}
}
class Config : ObjectiveConfig {
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
ticksBetweenAttempts,
maxDistance,
speedMultiplier
).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
return HarvestSweetBerryBush(
ticksBetweenAttempts = ticksBetweenAttempts,
maxDistance = maxDistance.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
)
}
}
}
@@ -0,0 +1,409 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.resolveFloat
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.pokemon.ai.ToleratedHerdLeader
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.toBlockPos
import com.mojang.datafixers.util.Either
import java.util.UUID
import kotlin.math.cos
import kotlin.math.sin
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class Herd(
var checkTicks: Int = 60,
var tooFar: Float = 8F,
var closeEnough: Float = 4F,
/** Chance per tick of following the herd leader. */
val chance: Float = 1 / 60F,
val pathingCooldownTicks: Int = 80
) : Objective {
var wanderCenter: CobblemonWanderControl.WanderCenter? = null
var fixation: EntityFixation? = null
var leader: PokemonEntity? = null
var ticksUntilMaintainLeader: Int = checkTicks
var remainingPathingCooldownTicks = 0
override fun getDescription(entity: LivingEntity) = "Herd"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
entity as PokemonEntity
val moveControl = entity.moveControl as? PokemonMoveControl ?: return ObjectiveStatus.INVALID
if (remainingPathingCooldownTicks > 0) {
remainingPathingCooldownTicks--
}
val level = entity.level() as ServerLevel
if (leader == null && brain.hasMemoryValue(CobblemonMemories.HERD_LEADER)) {
leader = level.getEntity(brain.getMemory(CobblemonMemories.HERD_LEADER).let(UUID::fromString)) as? PokemonEntity
if (leader == null) {
brain.eraseMemory(CobblemonMemories.HERD_LEADER)
}
}
if (ticksUntilMaintainLeader > 0) {
ticksUntilMaintainLeader--
} else if (ticksUntilMaintainLeader == 0) {
if (brain.fixation == null || brain.isFixation(fixation)) {
// Don't maintain the herd leader if we're busy with something else
maintainHerdLeader(entity, brain)
}
ticksUntilMaintainLeader = checkTicks
}
if (leader?.level() != level) {
this.leader = null
}
val leader = leader
if (leader == null && brain.isFixation(fixation)) {
brain.distract(Distraction.FORCE)
}
val wanderControl = brain.getMemory(CobblemonMemories.WANDER_CONTROL)
?: CobblemonWanderControl()
if (this.wanderCenter != null && leader == null) {
if (wanderControl.center == this.wanderCenter) {
wanderControl.center = null
wanderControl.wanderChanceMultiplier = null
brain.setMemory(CobblemonMemories.WANDER_CONTROL, wanderControl)
}
this.wanderCenter = null
} else if (leader != null) {
wanderControl.center = CobblemonWanderControl.WanderCenter(
x = leader.x,
y = leader.y,
z = leader.z,
minRange = 1F,
maxRange = tooFar
)
this.wanderCenter = wanderControl.center
wanderControl.wanderChanceMultiplier = 0.25F
brain.setMemory(CobblemonMemories.WANDER_CONTROL, wanderControl)
}
leader ?: return ObjectiveStatus.INACTIVE
if (brain.isFixation(fixation) && fixation is MovementTrackerFixation) {
return ObjectiveStatus.FIXATED
}
val leaderFixation = leader.biggerBrain.fixation
val canMove = brain.canDistract(comfortDistraction) || brain.isFixation(fixation)
val isMovingCloser = brain.isFixation(fixation) && fixation is LookAndMoveFixation
val leaderMoveControl = leader.moveControl as? PokemonMoveControl ?: return ObjectiveStatus.INVALID
if (leader.distanceTo(entity) > tooFar) {
return when {
isMovingCloser -> ObjectiveStatus.FIXATED // Already have the fixation to move closer
remainingPathingCooldownTicks > 0 -> manageFixation(brain) // Still cooling down from last pathing attempt
!canMove -> ObjectiveStatus.BLOCKED // I'm not able to distract the brain and move closer
else -> tryMovingCloser(entity, brain, level, leader) // let's give it a fair shake then
}
} else if (leader.isFlying() && !entity.isFlying() && !isMovingCloser) {
if (!entity.canFly()) {
entity.brain.eraseMemory(CobblemonMemories.HERD_LEADER) // We can't go where this guy's going
this.leader = null
return ObjectiveStatus.INACTIVE
} else if (!canMove) {
return ObjectiveStatus.BLOCKED
}
return tryMovingCloser(entity, brain, level, leader)
} else if (leaderMoveControl.banking && !isMovingCloser) {
if (!canMove) {
return ObjectiveStatus.BLOCKED
}
if (leader.isInLiquid && !entity.isInLiquid) {
if (remainingPathingCooldownTicks > 0) {
return manageFixation(brain) // Still cooling down from last pathing attempt
}
// We should get into water asap so we can mimic the banking
return tryMovingCloser(entity, brain, level, leader, completionRange = 1)
} else {
removeSleep(entity, brain)
entity.yRot = leader.yRot
moveControl.startBanking(
forwardBlocksPerTick = leaderMoveControl.bankForwardBlocksPerTick,
upwardsBlocksPerTick = leaderMoveControl.bankUpwardsBlocksPerTick,
durationTicks = 2,
rightDegreesPerTick = 0F // Relying on the above yRot = leader.yRot because we dunno when we first started mimicking the banking
)
return ObjectiveStatus.FIXATED
}
} else if (leaderFixation is MovementTrackerFixation && leaderFixation.movementTracker?.lastStatus == MovementStatus.PENDING) {
if (entity.random.nextFloat() > chance) {
return manageFixation(brain) // Not this tick
} else if (!canMove) {
return ObjectiveStatus.BLOCKED
} else if (isMovingCloser) {
return ObjectiveStatus.FIXATED // Already moving
}
val leaderWalkTarget = leaderFixation.movementTracker?.walkTarget ?: return manageFixation(brain)
// We're already closer to the walk target than the leader is, so no need to move. If anything we should get out of the way
if (entity.distanceToSqr(leaderWalkTarget.target.currentPosition()) + closeEnough < leader.distanceToSqr(leaderWalkTarget.target.currentPosition())) {
return manageFixation(brain)
}
// Go to the leader's walk target, roughly.
val walkTarget = CobblemonWalkTarget(
pos = leaderWalkTarget.target.currentPosition().add(getRandomOffset(entity, level)).toBlockPos(),
speedModifier = leaderWalkTarget.speedModifier,
nodeTypeFilter = (leaderWalkTarget as? CobblemonWalkTarget)?.nodeTypeFilter ?: { true },
completionRange = closeEnough.toInt()
)
return tryMovingCloser(entity, brain, level, leader, walkTarget = walkTarget)
} else if (leader.getCurrentPoseType() == PoseType.SLEEP) {
// Leader is sleeping, if I'm drowsy and can sleep here then I will sleep too.
if (entity.canSleepAt(entity.blockPosition().offset(0, -1, 0)) && brain.getMemory(CobblemonMemories.POKEMON_DROWSY) == true) {
if (entity.random.nextFloat() < 1 / 40F) {
applySleep(entity, brain)
}
}
} else if (brain.getMemory(CobblemonMemories.POKEMON_SLEEPING) == true) {
// Leader is awake, so we should be awake too. Maybe.
if (entity.random.nextFloat() < 1 / 15F) {
removeSleep(entity, brain)
}
}
return manageFixation(brain)
}
fun manageFixation(brain: BiggerBrain): ObjectiveStatus {
if (brain.isFixation(fixation)) {
return ObjectiveStatus.FIXATED
} else if (leader != null) {
return ObjectiveStatus.ACTIVE
} else {
return ObjectiveStatus.INACTIVE
}
}
fun tryMovingCloser(
entity: PokemonEntity,
brain: BiggerBrain,
level: ServerLevel,
leader: PokemonEntity,
walkTarget: WalkTarget? = null,
completionRange: Int = closeEnough.toInt()
): ObjectiveStatus {
val walkTarget = walkTarget ?: CobblemonWalkTarget(
pos = leader.position().add(getRandomOffset(entity, level)).toBlockPos(),
speedModifier = 0.4F,
completionRange = completionRange
)
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
remainingPathingCooldownTicks = pathingCooldownTicks
if (movementTracker == null) {
// Couldn't find a path, wait a bit before trying again
return manageFixation(brain)
} else if (!(brain.isFixation(fixation) && brain.distract(Distraction.FORCE)) && !brain.distract(comfortDistraction)) {
return manageFixation(brain) // Not likely based on checks run on callers to this function.
}
brain.sendDebugMessage("Moving closer, to ${movementTracker.walkTarget.target.currentBlockPosition()}")
removeSleep(entity, brain)
fixation = brain.fixate(
LookAndMoveFixation(
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.COMFORT, DistractionType.FORCE),
lookingTracker = LookingTracker.forTargetEntity(entity, leader, trackEyeHeight = true),
movementTracker = movementTracker,
description = "Moving towards herd leader"
)
)
return ObjectiveStatus.FIXATED
}
fun maintainHerdLeader(entity: PokemonEntity, brain: BiggerBrain) {
val world = entity.level() as ServerLevel
val leader = brain.getMemory(CobblemonMemories.HERD_LEADER)?.let(UUID::fromString).let(world::getEntity) as? PokemonEntity
val currentLeaderDefinition = leader?.let { leader ->
entity.behaviour.herd.toleratedLeaders.find { it.pokemon.matches(leader) }
}
if (leader == null || currentLeaderDefinition == null) {
entity.brain.eraseMemory(CobblemonMemories.HERD_LEADER)
this.leader = null
}
val bestLeader = getBestHerdLeader(
currentLeader = if (leader == null || currentLeaderDefinition == null) null else (leader to currentLeaderDefinition),
nearbyEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return,
entity = entity
)
if (bestLeader != null && bestLeader != leader) {
bestLeader.brain.eraseMemory(CobblemonMemories.HERD_LEADER)
entity.brain.setMemory(CobblemonMemories.HERD_LEADER, bestLeader.uuid.toString())
bestLeader.adjustHerdSize(1)
leader?.adjustHerdSize(-1)
this.leader = bestLeader
}
}
fun getRandomOffset(entity: PokemonEntity, level: ServerLevel): Vec3 {
val leader = leader ?: return Vec3.ZERO
val minRange = closeEnough.toDouble()
val maxRange = (tooFar - closeEnough / 2).toDouble()
val randomDistance = Mth.nextDouble(level.random, minRange, maxRange)
val randomAngle = Mth.nextDouble(level.random, 0.0, 2 * Math.PI)
var offset = Vec3(cos(randomAngle) * randomDistance, 0.0, sin(randomAngle) * randomDistance)
if ((entity.isInLiquid && leader.isInLiquid) || (entity.isFlying() && leader.isFlying())) {
val verticalRange = randomDistance * 0.5
val randomY = Mth.nextDouble(level.random, -verticalRange, verticalRange)
offset = offset.add(0.0, randomY, 0.0)
}
return offset
}
fun applySleep(entity: PokemonEntity, brain: BiggerBrain) {
brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
}
fun removeSleep(entity: PokemonEntity, brain: BiggerBrain) {
if (brain.hasMemoryValue(CobblemonMemories.POKEMON_SLEEPING)) {
brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
val status = entity.pokemon.status
if (status != null && status.status == Statuses.SLEEP) {
entity.pokemon.status = null
}
}
}
/**
* Logic used to choose the best herd leader from nearby visible entities.
*
* - Higher tier leaders are always preferred.
* - Same-tier leaders that are closer are preferred except for when they're the same tier as the current leader.
* - We don't want to exceed the target's herd size limit.
* - We don't want to choose a leader that is themselves following a leader.
* - Some leader definitions allow lower-level Pokémon to lead them.
* - Leaders just have to not be in a party - pastured and wild Pokémon can mingle.
*/
fun getBestHerdLeader(
currentLeader: Pair<PokemonEntity, ToleratedHerdLeader>?,
nearbyEntities: NearestVisibleLivingEntities,
entity: PokemonEntity,
): PokemonEntity? {
var bestLeader: PokemonEntity? = null
val (currentLeaderEntity, currentLeaderDefinition) = currentLeader ?: Pair(null, null)
var bestTier = currentLeaderDefinition?.tier?.let { it + 1 } ?: 0 // Only upgrade to a higher tier leader
var bestDistance = Float.MAX_VALUE
nearbyEntities.findAll {
it is PokemonEntity &&
it != currentLeader &&
it.pokemon.storeCoordinates.get()?.store !is PartyStore &&
it.getHerdSize() < it.behaviour.herd.maxSize &&
!it.brain.hasMemoryValue(CobblemonMemories.POKEMON_DROWSY)
}.forEach { possibleLeader ->
possibleLeader as PokemonEntity
val bestMatchingHerdLeader = entity.behaviour.herd.bestMatchLeader(entity, possibleLeader) ?: return@forEach
val possibleLeaderIsAlreadyALeader = possibleLeader.brain.hasMemoryValue(CobblemonMemories.HERD_SIZE)
val currentLeaderHerdTier = currentLeaderEntity?.getHerdTier() ?: entity.getHerdTier()
if (currentLeaderHerdTier >= bestMatchingHerdLeader.tier) {
return@forEach // This leader is following a leader of a higher tier, we can't replace them
}
val distance = entity.distanceTo(possibleLeader)
if (bestMatchingHerdLeader.tier < bestTier) {
return@forEach // No need to check further if this leader is worse than the best found so far
} else if ((distance > bestDistance && !possibleLeaderIsAlreadyALeader) && bestMatchingHerdLeader.tier == bestTier) {
return@forEach // No need to check further if this leader is farther than the best found so far, unless they are already a leader - I'd prefer to join a pack
} else {
bestLeader = possibleLeader
bestDistance = distance
bestTier = bestMatchingHerdLeader.tier
}
}
return bestLeader ?: currentLeaderEntity
}
class Config : ObjectiveConfig {
// How frequently to check for whether it should herd. Probably isn't that expensive but might use this for chance.
val checkTicks: ExpressionOrEntityVariable = Either.left("60".asExpression())
val tooFar: ExpressionOrEntityVariable = Either.left("8".asExpression())
val closeEnough: ExpressionOrEntityVariable = Either.left("4".asExpression())
val chance: ExpressionOrEntityVariable = Either.left("1 / 60".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(checkTicks, tooFar, closeEnough, chance).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
behaviourConfigurationContext.addMemories(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
behaviourConfigurationContext.addMemories(CobblemonMemories.HERD_LEADER)
behaviourConfigurationContext.addMemories(CobblemonMemories.HERD_SIZE)
return Herd(
checkTicks = checkTicks.resolveInt(behaviourConfigurationContext.runtime),
tooFar = tooFar.resolveFloat(behaviourConfigurationContext.runtime),
closeEnough = closeEnough.resolveFloat(behaviourConfigurationContext.runtime),
chance = chance.resolveFloat(behaviourConfigurationContext.runtime)
)
}
}
}
@@ -0,0 +1,136 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.resolveDouble
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import kotlin.math.pow
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* Randomly looks at nearby entities out of curiosity. Very easily distracted once doing so.
*
* @author Hiroku
* @since December 29th, 2025
*/
class LookAtNearbyEntities(
val maxDistance: Double = 10.0,
val minDuration: Int = 20,
val maxDuration: Int = 80
) : Objective {
var fixation: EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Look at nearby entities"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (brain.isFixation(fixation)) {
return ObjectiveStatus.FIXATED
} else if (brain.fixation != null) {
fixation = null
return ObjectiveStatus.BLOCKED
}
val nearestVisibleLivingEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
?: return ObjectiveStatus.INVALID
val closest = nearestVisibleLivingEntities.findClosest(
{ otherEntity -> otherEntity.distanceToSqr(entity) <= maxDistance.pow(2) && !entity.hasPassenger(otherEntity) }
).orElse(null)
if (closest == null) {
return ObjectiveStatus.INACTIVE
} else {
// Become fixated
fixation = brain.fixate(
IdleLookFixation(
this,
closest,
(minDuration..maxDuration).random()
)
)
return ObjectiveStatus.FIXATED
}
}
class IdleLookFixation(
val objective: LookAtNearbyEntities,
val targetEntity: LivingEntity,
var remainingTicks: Int
) : EntityFixation, LookingTrackerFixation {
override var lookingTracker: LookingTracker? = null
override fun getDescription(entity: LivingEntity): String {
return "Looking at a nearby entity ($remainingTicks ticks left)"
}
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
// Bro literally anything will distract me
return true
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (targetEntity.isRemoved || entity !is PathfinderMob) {
return false
}
if (lookingTracker == null) {
lookingTracker = LookingTracker(entity, EntityTracker(targetEntity, true))
}
val lookingTracker = lookingTracker!!
remainingTicks--
lookingTracker.tick()
return remainingTicks > 0
}
}
class Config : ObjectiveConfig {
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val minDuration: ExpressionOrEntityVariable = Either.left("20".asExpression())
val maxDuration: ExpressionOrEntityVariable = Either.left("80".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
maxDistance,
minDuration,
maxDuration
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
val maxDistance = maxDistance.resolveDouble(behaviourConfigurationContext.runtime)
val minDuration = minDuration.resolveInt(behaviourConfigurationContext.runtime)
val maxDuration = maxDuration.resolveInt(behaviourConfigurationContext.runtime)
return LookAtNearbyEntities(
maxDistance = maxDistance,
minDuration = minDuration,
maxDuration = maxDuration
)
}
}
}
@@ -0,0 +1,65 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.OmniPathingEntity
import net.minecraft.world.entity.LivingEntity
class MaintainFluidY : Objective {
override fun getDescription(entity: LivingEntity) = "Maintain Y in fluids"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is OmniPathingEntity) {
return ObjectiveStatus.INVALID
}
val fixation = brain.fixation
if (fixation is MovementTrackerFixation) {
return ObjectiveStatus.BLOCKED
}
if (entity.isFlying()) {
return ObjectiveStatus.INACTIVE
}
if (entity.canSwimInWater() && entity.isUnderWater) {
entity.isNoGravity = true
} else if (entity.canSwimInLava() && entity.isInLava) {
entity.isNoGravity = true
} else {
entity.isNoGravity = false
}
return if (entity.isNoGravity) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
}
class Config : ObjectiveConfig {
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return emptyList()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return MaintainFluidY()
}
}
}
@@ -0,0 +1,251 @@
/*
* 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.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.*
import com.cobblemon.mod.common.api.ai.biggerbrain.*
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.TamableAnimal
import net.minecraft.world.entity.ai.behavior.BehaviorUtils
import net.minecraft.world.entity.ai.util.RandomPos
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
/**
* Tries to path into water if the entity usually avoids land.
* Will pathfind through land even if the entity usually filters out land nodes.
* If water cannot be found in the entity's wander range it'll wander randomly in a spiral-ish pattern
*
* @author JazzMcNade
* @since January 25th, 2026
*/
class MoveIntoWater(
val ticksBetweenAttempts: Int = 60,
val maxDistance: Expression = "14.0".asExpression(),
val speedMultiplier: Expression = "0.4".asExpression()
) : Objective {
var movementTask: EntityFixation? = null
var remainingCooldownTicks = ticksBetweenAttempts
var wanderDirection: Vec3? = null
var isWanderTurnDirectionClockwise = true
var wanderTurnChance = BASE_TURN_CHANCE
var failCount = 0
override fun getDescription(entity: LivingEntity) = "Move into water if currently out of water"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is TamableAnimal) {
return ObjectiveStatus.INVALID
}
if (entity.isInWater) {
// All good, nothing to do here
return ObjectiveStatus.INACTIVE
}
if (brain.isFixation(movementTask)) {
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.INACTIVE
} else {
remainingCooldownTicks = ticksBetweenAttempts
}
if (brain.distract(comfortDistraction)) {
val maxDistance = mainThreadRuntime.resolveInt(maxDistance)
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
val runtime = mainThreadRuntime
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
val allowLand = wanderControl?.allowLand
?: runtime.resolveBoolean("(q.entity.behaviour.moving.walk.can_walk && q.entity.behaviour.moving.walk.avoids_land) || q.entity.behaviour.moving.fly.can_fly".asExpression())
val allowAir = wanderControl?.allowAir
?: runtime.resolveBoolean("q.entity.behaviour.moving.fly.can_fly && !q.entity.behaviour.moving.walk.can_walk".asExpression())
val types = mutableSetOf<WanderType>()
if ((allowLand || allowAir)) {
types.add(WanderType.WATER)
}
if (types.isEmpty()) {
return ObjectiveStatus.INACTIVE
}
var walkTarget: CobblemonWalkTarget? = null
val targetVec3 = BehaviorUtils.getRandomSwimmablePos(entity, 10, 7)
if (targetVec3 != null) {
walkTarget = CobblemonWalkTarget(
BlockPos.containing(targetVec3.x, targetVec3.y, targetVec3.z),
speedMultiplier,
0,
nodeTypeFilter = { nodeType ->
true
}
)
}
if (walkTarget == null) {
if (failCount >= 5) {
// No out of water target could be found.
// Need to choose a direction and move as far as possible, hoping we randomly find water
// Totally possible that a shoreline is *just* outside the entity's wander range and
// we choose a direction that's opposite of the shoreline
// To counteract this, the pokemon moves in a (very roughly) spiral pattern outward from the origin
var wanderPosition: BlockPos? = null
if (wanderDirection == null) {
val wanderOffset = RandomPos.generateRandomDirection(entity.level().random, maxDistance / 2, 0)
wanderDirection = wanderOffset.toVec3d().normalize()
isWanderTurnDirectionClockwise = entity.random.nextInt(2) > 0
wanderTurnChance = BASE_TURN_CHANCE
wanderPosition = entity.blockPosition().offset(wanderOffset.x, wanderOffset.y, wanderOffset.z)
} else {
if (entity.level().random.nextFloat() < wanderTurnChance) {
// Turn 30 degrees
wanderDirection =
wanderDirection!!.yRot(Math.PI.toFloat() / 3.0f * if (isWanderTurnDirectionClockwise) -1f else 1f)
wanderTurnChance *= 0.7
}
val wanderVec = entity.position().add(wanderDirection!!.scale(maxDistance.toDouble()))
wanderPosition = BlockPos.containing(wanderVec)
}
val wanderTarget = generateWaterSafePosToward(entity, maxDistance, wanderPosition)
if (wanderTarget != null) {
walkTarget = CobblemonWalkTarget(
wanderPosition,
speedMultiplier,
0,
nodeTypeFilter = { nodeType ->
true
}
)
} else {
return ObjectiveStatus.ACTIVE
}
} else {
failCount++
}
}
if (walkTarget != null) {
failCount = 0
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
?: return ObjectiveStatus.ACTIVE
brain.distract(comfortDistraction)
movementTask = brain.fixate(
fixation = MoveIntoWaterFixation(
movementTracker = movementTracker
)
)
}
}
return ObjectiveStatus.ACTIVE
}
fun generateWaterSafePosToward(
mob: PathfinderMob,
range: Int,
targetPos: BlockPos
): BlockPos? {
for (i in 0..4) {
val candidate: BlockPos? = RandomPos.generateRandomPosTowardDirection(
mob,
range,
mob.level().random,
targetPos
)
if (candidate == null) continue
val pathType = mob.navigation.nodeEvaluator.getPathType(mob, candidate)
// Don't actually care if it can be reached, only if it's not hazardous
if (pathType == PathType.WALKABLE || mob.getPathfindingMalus(pathType) <= 0)
return candidate
}
return null
}
companion object {
val BASE_TURN_CHANCE = 0.8
}
class MoveIntoWaterFixation(
override val movementTracker: MovementTracker,
) : EntityFixation, MovementTrackerFixation {
override fun getDescription(entity: LivingEntity) = "Moving into water"
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
val movementStatus = tickMovementFixation()
if (movementStatus == MovementStatus.COMPLETED) return false
if (!entity.isInWater) {
return false
}
return movementStatus != MovementStatus.FAILED
}
}
class Config : ObjectiveConfig {
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
ticksBetweenAttempts,
maxDistance,
speedMultiplier
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
return MoveIntoWater(
ticksBetweenAttempts = ticksBetweenAttempts,
maxDistance = maxDistance.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
)
}
}
}
@@ -0,0 +1,258 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.*
import com.cobblemon.mod.common.api.ai.biggerbrain.*
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.TamableAnimal
import net.minecraft.world.entity.ai.util.LandRandomPos
import net.minecraft.world.entity.ai.util.RandomPos
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
/**
* Tries to path out of water if the entity usually water to avoid water.
* Will pathfind through water even if the entity usually filters out water path nodes.
* If land cannot be found in the entity's wander range it'll wander randomly in a spiral-ish pattern
*
* @author JazzMcNade
* @since January 17th, 2026
*/
class MoveOutOfWater(
val ticksBetweenAttempts: Int = 60,
val maxDistance: Expression = "14.0".asExpression(),
val speedMultiplier: Expression = "0.4".asExpression()
) : Objective {
var movementTask: EntityFixation? = null
var remainingCooldownTicks = ticksBetweenAttempts
var wanderDirection: Vec3? = null
var isWanderTurnDirectionClockwise = true
var wanderTurnChance = BASE_TURN_CHANCE
var failCount = 0
override fun getDescription(entity: LivingEntity) = "Move out of water"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is TamableAnimal) {
return ObjectiveStatus.INVALID
}
if (brain.isFixation(movementTask)) {
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
}
if (!entity.isInWater || entity.isUnderWater) {
// Focus on not drowning first
// TODO: 100% boned if in an underwater cave
return ObjectiveStatus.INACTIVE
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.INACTIVE
} else {
remainingCooldownTicks = ticksBetweenAttempts
}
val maxDistance = mainThreadRuntime.resolveInt(maxDistance)
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
val runtime = mainThreadRuntime
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
val allowLand = wanderControl?.allowLand
?: runtime.resolveBoolean("(q.entity.behaviour.moving.walk.can_walk && !q.entity.behaviour.moving.walk.avoids_land) || q.entity.behaviour.moving.fly.can_fly".asExpression())
val allowAir = wanderControl?.allowAir
?: runtime.resolveBoolean("q.entity.behaviour.moving.fly.can_fly && !q.entity.behaviour.moving.walk.can_walk".asExpression())
val types = mutableSetOf<WanderType>()
if ((allowLand || allowAir)) {
types.add(WanderType.LAND)
}
if (types.isEmpty()) {
return ObjectiveStatus.INACTIVE
}
if (brain.distract(comfortDistraction)) {
var walkTarget: CobblemonWalkTarget? = null
val targetVec3 = LandRandomPos.getPos(entity, maxDistance, maxDistance)
if (targetVec3 != null) {
walkTarget = CobblemonWalkTarget(
BlockPos.containing(targetVec3.x, targetVec3.y, targetVec3.z),
speedMultiplier,
0,
nodeTypeFilter = { nodeType ->
true
}
)
}
if (walkTarget == null) {
if (failCount >= 5) {
// No out of water target black with a malus of 0 could be found.
// Need to choose a direction and move as far as possible, hoping we randomly find land
// Totally possible that a shoreline is *just* outside of the entity's wander range and
// we choose a direction that's opposite of the shoreline
// To counteract this, the pokemon moves in a (very roughly) spiral pattern outward from the origin
var wanderPosition: BlockPos? = null
if (wanderDirection == null) {
val wanderOffset = RandomPos.generateRandomDirection(entity.level().random, maxDistance / 2, 0)
wanderDirection = wanderOffset.toVec3d().normalize()
isWanderTurnDirectionClockwise = entity.random.nextInt(2) > 0
wanderTurnChance = BASE_TURN_CHANCE
wanderPosition = entity.blockPosition().offset(wanderOffset.x, wanderOffset.y, wanderOffset.z)
} else {
if (entity.level().random.nextFloat() < wanderTurnChance) {
// Turn 30 degrees
wanderDirection =
wanderDirection!!.yRot(Math.PI.toFloat() / 3.0f * if (isWanderTurnDirectionClockwise) -1f else 1f)
wanderTurnChance *= 0.7
}
val wanderVec = entity.position().add(wanderDirection!!.scale(maxDistance.toDouble()))
wanderPosition = BlockPos.containing(wanderVec)
}
val wanderTarget = generateWaterSafePosToward(entity, maxDistance, wanderPosition)
if (wanderTarget != null) {
walkTarget = CobblemonWalkTarget(
wanderPosition,
speedMultiplier,
0,
nodeTypeFilter = { nodeType ->
true
}
)
} else {
return ObjectiveStatus.ACTIVE
}
} else {
failCount++
}
}
if (walkTarget != null) {
failCount = 0
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
?: return ObjectiveStatus.ACTIVE
movementTask = brain.fixate(
fixation = StayOutOfWaterFixation(
movementTracker = movementTracker
)
)
}
}
return ObjectiveStatus.ACTIVE
}
fun generateWaterSafePosToward(
mob: PathfinderMob,
range: Int,
targetPos: BlockPos
): BlockPos? {
for (i in 0..4) {
val candidate: BlockPos? = RandomPos.generateRandomPosTowardDirection(
mob,
range,
mob.level().random,
targetPos
)
if (candidate == null) continue
val pathType = mob.navigation.nodeEvaluator.getPathType(mob, candidate)
// Don't actually care if it can be reached, only if it's not hazardous
if (pathType == PathType.WATER || mob.getPathfindingMalus(pathType) <= 0)
return candidate
}
return null
}
companion object {
const val BASE_TURN_CHANCE = 0.8
}
class StayOutOfWaterFixation(
override val movementTracker: MovementTracker,
) : EntityFixation, MovementTrackerFixation {
var ticksOutOfWater = 0
override fun getDescription(entity: LivingEntity) = "Moving out of water"
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
// Easily distracted once we are no longer touching water.
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
val movementStatus = tickMovementFixation()
if (movementStatus == MovementStatus.COMPLETED) return false
if (entity.isInWater) {
ticksOutOfWater = 0
} else {
ticksOutOfWater++
if (ticksOutOfWater > 20) {
// Made it out of water, job done
return false
}
}
return movementStatus != MovementStatus.FAILED
}
}
class Config : ObjectiveConfig {
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
ticksBetweenAttempts,
maxDistance,
speedMultiplier
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
return MoveOutOfWater(
ticksBetweenAttempts = ticksBetweenAttempts,
maxDistance = maxDistance.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
)
}
}
}
@@ -0,0 +1,166 @@
/*
* 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.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.*
import com.cobblemon.mod.common.api.ai.biggerbrain.*
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.AABB
/**
* Looks for a resting place if the Pokemon is drowsy, and if available paths to it.
*
* @author JazzMcNade
* @since February 3rd, 2026
*/
class MoveToRestingPlace(
val ticksBetweenAttempts: Int = 80,
val horizontalSearchDistance: Expression = "14.0".asExpression(),
val verticalSearchDistance: Expression = "6.0".asExpression(),
val onlyFromStatus: Expression = "false".asExpression(),
val speedMultiplier: Expression = "0.4".asExpression()
) : Objective {
var movementTask: EntityFixation? = null
var remainingCooldownTicks = ticksBetweenAttempts
override fun getDescription(entity: LivingEntity) = "Moving to a resting place"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
}
brain.getMemory(CobblemonMemories.POKEMON_DROWSY)
?: return ObjectiveStatus.INACTIVE
if (brain.isFixation(movementTask)) {
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
}
val hasSleepStatus = entity.pokemon.status?.status === Statuses.SLEEP
if (hasSleepStatus) return ObjectiveStatus.INACTIVE // Already sleeping
if (entity.behaviour.resting.canSleep) {
if (mainThreadRuntime.resolveBoolean(onlyFromStatus)) {
return ObjectiveStatus.BLOCKED
}
if (remainingCooldownTicks > 0 || entity.canSleepAt(entity.blockPosition().below())) {
// Can sleep where we're at, no need to go anywhere
return ObjectiveStatus.INACTIVE
}
val hSearchDistance = mainThreadRuntime.resolveDouble(horizontalSearchDistance)
val vSearchDistance = mainThreadRuntime.resolveDouble(verticalSearchDistance)
val positionList = entity.level()
.getBlockPositions(AABB.ofSize(entity.position(), hSearchDistance, vSearchDistance, hSearchDistance))
.filter(entity::canSleepAt)
val position = if (positionList.isEmpty()) null else positionList.random(1).firstOrNull()
if (position != null && brain.distract(comfortDistraction)) {
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
val movementTracker = RegularMovementTracker.attempt(
entity = entity,
walkTarget = WalkTarget(
position,
speedMultiplier,
0
)
)
if (movementTracker != null) {
// Become fixated
movementTask = brain.fixate(
fixation = MoveToRestingPlaceFixation(
movementTracker = movementTracker,
)
)
}
} else {
return ObjectiveStatus.INACTIVE
}
return ObjectiveStatus.ACTIVE
}
return ObjectiveStatus.INACTIVE
}
class MoveToRestingPlaceFixation(
override val movementTracker: MovementTracker,
) : EntityFixation, MovementTrackerFixation {
override fun getDescription(entity: LivingEntity) = "Moving to a resting place"
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
val movementStatus = tickMovementFixation()
if (movementStatus == MovementStatus.COMPLETED) return false
return movementStatus != MovementStatus.FAILED
}
}
class Config : ObjectiveConfig {
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
val horizontalSearchDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val verticalSearchDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
val onlyFromStatus: ExpressionOrEntityVariable = Either.left("false".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
ticksBetweenAttempts,
horizontalSearchDistance,
verticalSearchDistance,
speedMultiplier,
onlyFromStatus
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
return MoveToRestingPlace(
ticksBetweenAttempts = ticksBetweenAttempts,
horizontalSearchDistance = horizontalSearchDistance.asSimplifiedExpression(entity),
verticalSearchDistance = verticalSearchDistance.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
onlyFromStatus = onlyFromStatus.asSimplifiedExpression(entity)
)
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* An ongoing priority for an entity. Objectives are managed by the [BiggerBrain] system. Objectives can act out
* entirely within their own logic, or they can alter memories or create fixations for the entity to take up its
* entire attention.
*
* @author Hiroku
* @since December 29th, 2025
*/
interface Objective {
companion object {
/**
* The idea behind this is that later objectives may like to know or respect that something impacting wandering
* has taken place.
*/
val OBJECTIVE_TAG_WANDERING = "WANDERING"
}
/** The memory types that, upon being changed, should trigger [onMemoryChanged]. */
val observedMemories: List<MemoryModuleType<*>>
get() = emptyList()
/** Gets a current description for this objective. This is purely used for the debug overlay. */
fun getDescription(entity: LivingEntity): String
/** Performs this objective's logic for a tick. */
fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus
/**
* A subscription handler called for when a memory is set or removed (not expired). For a memory to trigger this
* function, it must be in [observedMemories].
*/
fun <T> onMemoryChanged(entity: LivingEntity, brain: BiggerBrain, memory: MemoryModuleType<T>, value: T?) {
// Default implementation does nothing
}
}
@@ -0,0 +1,28 @@
/*
* 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.ai.biggerbrain.objective
/**
* The current status of an [Objective]. Used for the debug overlay.
*
* @author Hiroku
* @since December 29th, 2025
*/
enum class ObjectiveStatus {
/** States that this objective owns the current fixation. */
FIXATED,
/** States that the objective's logic is ongoing and not impeded. */
ACTIVE,
/** States that the objective is not performing any meaningful logic right now but isn't actively blocked. */
INACTIVE,
/** States that there are other objectives, fixations, etc. in play which prevents the objective from being performed. */
BLOCKED,
/** States that the objective is not valid and shouldn't have been registered on the entity in the first place, such as when placed on the wrong entity type. */
INVALID
}
@@ -0,0 +1,98 @@
/*
* 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.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class PathToIdealNestLocationTask(
private val speedMultiplier: Expression
) : Objective {
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Path to ideal nest"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
if (brain.isFixation(movementTask)) {
brain.sendDebugMessage("[PathToIdealNest] Fixation active.")
return ObjectiveStatus.FIXATED
}
if (brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) != null) {
brain.sendDebugMessage("[PathToIdealNest] Already a nest parent; skipping.")
return ObjectiveStatus.INVALID
}
if (entity.pokemon.breedingCooldown > 0) {
brain.sendDebugMessage("[PathToIdealNest] Breeding cooldown active; skipping.")
return ObjectiveStatus.INVALID
}
if (brain.getMemory(CobblemonMemories.READY_TO_BUILD_NEST) != true) {
brain.sendDebugMessage("[PathToIdealNest] Not ready to build.")
return ObjectiveStatus.INVALID
}
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INVALID
if (mate.pokemon.breedingCooldown > 0) {
brain.sendDebugMessage("[PathToIdealNest] Mate breeding cooldown active; skipping.")
return ObjectiveStatus.INVALID
}
val nestPos: BlockPos = brain.getMemory(CobblemonMemories.IDEAL_NEST_LOCATION) ?: return ObjectiveStatus.INVALID
val nestCenter = Vec3.atCenterOf(nestPos)
if (entity.distanceToSqr(nestCenter) <= 2.0) {
brain.sendDebugMessage("[PathToIdealNest] Already at ideal nest location $nestPos.")
return ObjectiveStatus.INACTIVE
}
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val speed = mainThreadRuntime.resolveFloat(speedMultiplier)
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
val movementTracker = RegularMovementTracker.attempt(entity, WalkTarget(nestCenter, speed, 1))
?: return ObjectiveStatus.ACTIVE
brain.sendDebugMessage("[PathToIdealNest] Moving to ideal nest location $nestPos.")
movementTask = brain.fixate(SimpleMoveFixation(movementTracker, "Move to ideal nest"))
moveMateTo(mate, nestCenter, speed)
return ObjectiveStatus.ACTIVE
}
private fun moveMateTo(mate: PokemonEntity, target: Vec3, speed: Float) {
val mateBrain = mate.biggerBrain
if (!mateBrain.distract(comfortDistraction)) return
val tracker = RegularMovementTracker.attempt(mate, WalkTarget(target, speed, 1)) ?: return
mateBrain.fixate(SimpleMoveFixation(tracker, "Move to ideal nest (mate)"))
}
}
@@ -0,0 +1,173 @@
/*
* 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.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.nest.WildNestType
import com.cobblemon.mod.common.api.nest.WildNestTypes
import com.cobblemon.mod.common.block.WildNestBlock
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.withQueryValue
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.memory.WalkTarget
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import kotlin.jvm.optionals.getOrNull
class PathToNestTask(
private val speedMultiplier: Expression
) : Objective {
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Path to nest"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
if (brain.isFixation(movementTask)) {
brain.sendDebugMessage("[PathToNest] Fixation active.")
return ObjectiveStatus.FIXATED
}
val isHoldingEgg = brain.getMemory(CobblemonMemories.HOLDING_EGG) ?: false
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST) ?: false
val paternalNestLoc = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val speed = mainThreadRuntime.resolveFloat(speedMultiplier)
if (isHoldingEgg && !eggInNest && paternalNestLoc != null) {
val paternalNestVec = Vec3.atCenterOf(paternalNestLoc)
if (entity.distanceToSqr(paternalNestVec) <= 2.0) {
brain.sendDebugMessage("[PathToNest] Already at paternal nest.")
return ObjectiveStatus.INACTIVE
}
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(paternalNestVec, speed, 1)) ?: return ObjectiveStatus.ACTIVE
brain.sendDebugMessage("[PathToNest] Moving to paternal nest at $paternalNestLoc.")
movementTask = brain.fixate(SimpleMoveFixation(tracker, "Path to paternal nest"))
return ObjectiveStatus.ACTIVE
}
if (entity.pokemon.breedingCooldown != 0) return ObjectiveStatus.INVALID
val nestLocation = brain.getMemory(CobblemonMemories.NEST_LOCATION) ?: return ObjectiveStatus.INVALID
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INVALID
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
val nestEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity ?: return ObjectiveStatus.INVALID
val allowedTypes = allowedNestTypes(entity, mate)
val nestType = (nestEntity.blockState.block as? WildNestBlock)?.type ?: return ObjectiveStatus.INVALID
if (!isNestTypeAllowed(nestType, entity, allowedTypes)) {
brain.sendDebugMessage("[PathToNest] Nest type $nestType not allowed. Allowed=$allowedTypes.")
return ObjectiveStatus.INVALID
}
val isNestWaterlogged = nestEntity.blockState.getOptionalValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED).orElse(false)
if (!isWaterGroupCompatible(entity, mate, isNestWaterlogged)) {
brain.sendDebugMessage("[PathToNest] Water-group mismatch for nest at $nestLocation (waterlogged=$isNestWaterlogged).")
return ObjectiveStatus.INVALID
}
val claimerEntityOne = nestEntity.nestClaimedBy.first?.let { world.getEntity(it) }
val claimerEntityTwo = nestEntity.nestClaimedBy.second?.let { world.getEntity(it) }
if ((claimerEntityOne != null && nestEntity.nestClaimedBy.first != entity.uuid) &&
(claimerEntityTwo != null && nestEntity.nestClaimedBy.second != entity.uuid)
) {
brain.sendDebugMessage("[PathToNest] Nest already claimed by others at $nestLocation.")
return ObjectiveStatus.INVALID
}
nestEntity.nestClaimedBy = Pair(entity.uuid, mate.uuid)
val targetVec = Vec3.atCenterOf(nestLocation)
if (entity.distanceToSqr(targetVec) <= 2.0 && mate.distanceToSqr(targetVec) <= 2.0) {
brain.sendDebugMessage("[PathToNest] Both parents at nest $nestLocation.")
return ObjectiveStatus.INACTIVE
}
if (nestEntity.getItem(0) != ItemStack.EMPTY) {
brain.sendDebugMessage("[PathToNest] Nest at $nestLocation already has an egg.")
return ObjectiveStatus.INVALID
}
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
val movementTracker = RegularMovementTracker.attempt(entity, WalkTarget(targetVec, speed, 1)) ?: return ObjectiveStatus.ACTIVE
brain.sendDebugMessage("[PathToNest] Moving to nest at $nestLocation.")
movementTask = brain.fixate(SimpleMoveFixation(movementTracker, "Path to nest"))
moveMateTo(mate, targetVec, speed)
return ObjectiveStatus.ACTIVE
}
private fun moveMateTo(mate: PokemonEntity, target: Vec3, speed: Float) {
val mateBrain = mate.biggerBrain
if (!mateBrain.distract(comfortDistraction)) return
val tracker = RegularMovementTracker.attempt(mate, WalkTarget(target, speed, 1)) ?: return
mateBrain.fixate(SimpleMoveFixation(tracker, "Path to nest (mate)"))
}
private fun allowedNestTypes(entity: PokemonEntity, mate: PokemonEntity): Set<WildNestType> {
val types = WildNestTypes.combinedTypes(entity, mate)
val mutable = types.toMutableSet()
if (!entity.pokemon.isWild() && !mate.pokemon.isWild()) {
mutable.add(WildNestType.PLAYER)
}
return mutable
}
private fun isNestTypeAllowed(
nestType: WildNestType,
entity: PokemonEntity,
allowedTypes: Set<WildNestType>
): Boolean {
if (nestType == WildNestType.PLAYER) {
return !entity.pokemon.isWild() && allowedTypes.contains(WildNestType.PLAYER)
}
return allowedTypes.contains(nestType)
}
private fun isWaterGroupCompatible(
entity: PokemonEntity,
mate: PokemonEntity,
isNestWaterlogged: Boolean
): Boolean {
val waterEggGroups = setOf("water_1", "water_2", "water_3")
val entityEggGroups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
val mateEggGroups = mate.pokemon.species.eggGroups.map { it.name.lowercase() }
val entityHasWater = entityEggGroups.any { it in waterEggGroups }
val mateHasWater = mateEggGroups.any { it in waterEggGroups }
val entityOnlyWater = entityEggGroups.all { it in waterEggGroups }
val mateOnlyWater = mateEggGroups.all { it in waterEggGroups }
return if (isNestWaterlogged) {
entityHasWater && mateHasWater
} else {
!(entityOnlyWater || mateOnlyWater)
}
}
}
@@ -0,0 +1,76 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.phys.Vec3
class PickUpEggTask : Objective {
override fun getDescription(entity: LivingEntity) = "Pick up egg"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
if (brain.getMemory(CobblemonMemories.READY_TO_GRAB_EGG) != true) return ObjectiveStatus.INVALID
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
if (eggInNest != null && eggInNest == true) {
brain.sendDebugMessage("[PickUpEgg] Egg already in nest.")
return ObjectiveStatus.INVALID
}
val eggPos: BlockPos = brain.getMemory(CobblemonMemories.EGG_LOCATION) ?: return ObjectiveStatus.INVALID
val eggCenter = Vec3.atCenterOf(eggPos)
if (entity.distanceToSqr(eggCenter) > 2.0) {
brain.sendDebugMessage("[PickUpEgg] Too far from egg at $eggPos; clearing ready flag.")
brain.eraseMemory(CobblemonMemories.READY_TO_GRAB_EGG)
return ObjectiveStatus.INACTIVE
}
val nearbyEggs = entity.level().getEntitiesOfClass(ItemEntity::class.java, entity.boundingBox.inflate(1.5)) { itemEntity ->
itemEntity.blockPosition() == eggPos && itemEntity.item.item == CobblemonItems.POKEMON_EGG
}
val eggItem = nearbyEggs.firstOrNull() ?: return ObjectiveStatus.INVALID
val eggComponent = eggItem.item.get(CobblemonItemComponents.EGG_DATA) ?: return ObjectiveStatus.INVALID
val isParent = eggComponent.egg?.parentOne?.uuid == entity.pokemon.uuid ||
eggComponent.egg?.parentTwo?.uuid == entity.pokemon.uuid
if (!isParent) {
brain.sendDebugMessage("[PickUpEgg] Egg at $eggPos does not belong to this Pokémon.")
return ObjectiveStatus.INVALID
}
entity.setItemInHand(InteractionHand.MAIN_HAND, eggItem.item.copy())
eggItem.remove(Entity.RemovalReason.DISCARDED)
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
brain.setMemory(CobblemonMemories.HOLDING_EGG, true)
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
brain.eraseMemory(CobblemonMemories.READY_TO_GRAB_EGG)
brain.sendDebugMessage("[PickUpEgg] Picked up egg from $eggPos.")
return ObjectiveStatus.ACTIVE
}
}
@@ -0,0 +1,78 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PlaceEggInNestTask : Objective {
override fun getDescription(entity: LivingEntity) = "Place egg in nest"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
if (brain.getMemory(CobblemonMemories.HOLDING_EGG) != true) return ObjectiveStatus.INVALID
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
if (eggInNest != null && eggInNest == true) {
brain.sendDebugMessage("[PlaceEgg] Egg already in nest.")
return ObjectiveStatus.INVALID
}
val nestLocation: BlockPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return ObjectiveStatus.INVALID
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
val nestEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity ?: return ObjectiveStatus.INVALID
val targetVec = Vec3.atCenterOf(nestLocation)
if (entity.distanceToSqr(targetVec) > 2.0) {
brain.sendDebugMessage("[PlaceEgg] Too far from nest $nestLocation.")
return ObjectiveStatus.INACTIVE
}
val heldItem = entity.getItemInHand(InteractionHand.MAIN_HAND)
if (heldItem.isEmpty || heldItem.item.asItem() != CobblemonItems.POKEMON_EGG.asItem()) {
brain.sendDebugMessage("[PlaceEgg] No egg in hand.")
return ObjectiveStatus.INVALID
}
if (nestEntity.getItem(0) != ItemStack.EMPTY) {
brain.sendDebugMessage("[PlaceEgg] Nest at $nestLocation already has an egg.")
return ObjectiveStatus.INVALID
}
nestEntity.setItem(heldItem.copy())
entity.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY)
brain.eraseMemory(CobblemonMemories.HOLDING_EGG)
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
brain.sendDebugMessage("[PlaceEgg] Egg placed in nest at $nestLocation.")
world.players().forEach { player ->
player.displayClientMessage("The Pokémon placed an egg in its nest!".green(), true)
}
return ObjectiveStatus.ACTIVE
}
}
@@ -0,0 +1,67 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class RetrieveEggTask : Objective {
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Retrieve egg"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
if (brain.isFixation(movementTask)) {
brain.sendDebugMessage("[RetrieveEgg] Fixation active.")
return ObjectiveStatus.FIXATED
}
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
if (eggInNest != null && eggInNest == true) {
brain.sendDebugMessage("[RetrieveEgg] Egg already in nest.")
return ObjectiveStatus.INVALID
}
val eggPos: BlockPos = brain.getMemory(CobblemonMemories.EGG_LOCATION) ?: return ObjectiveStatus.INVALID
val eggCenter = Vec3.atCenterOf(eggPos)
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
if (entity.distanceToSqr(eggCenter) <= 2.0) {
brain.sendDebugMessage("[RetrieveEgg] At egg location $eggPos; ready to grab.")
brain.setMemory(CobblemonMemories.READY_TO_GRAB_EGG, true)
return ObjectiveStatus.INACTIVE
}
brain.eraseMemory(CobblemonMemories.READY_TO_GRAB_EGG)
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(eggCenter, 1.0F, 1)) ?: return ObjectiveStatus.ACTIVE
brain.sendDebugMessage("[RetrieveEgg] Moving to egg at $eggPos.")
movementTask = brain.fixate(SimpleMoveFixation(tracker, "Retrieve egg"))
return ObjectiveStatus.ACTIVE
}
}
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
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.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
import kotlin.random.Random
class SearchForEggTask : Objective {
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
private var remainingCooldownTicks = 20
override fun getDescription(entity: LivingEntity) = "Search for egg"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
if (brain.isFixation(movementTask)) {
brain.sendDebugMessage("[SearchForEgg] Fixation active.")
return ObjectiveStatus.FIXATED
}
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
if (eggInNest != null && eggInNest == true) {
brain.sendDebugMessage("[SearchForEgg] Egg already in nest.")
return ObjectiveStatus.INVALID
}
if (brain.getMemory(CobblemonMemories.HOLDING_EGG) == true) {
brain.sendDebugMessage("[SearchForEgg] Already holding egg.")
return ObjectiveStatus.INVALID
}
if (brain.getMemory(CobblemonMemories.EGG_LOCATION) != null) {
brain.sendDebugMessage("[SearchForEgg] Egg location already known.")
return ObjectiveStatus.INVALID
}
if (brain.getMemory(CobblemonMemories.EGG_THIEF) != null) {
brain.sendDebugMessage("[SearchForEgg] Egg thief already detected.")
return ObjectiveStatus.INVALID
}
val nestPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return ObjectiveStatus.INVALID
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
val nestBlockEntity = world.getBlockEntity(nestPos) as? NestBlockEntity
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
val searchCountdownRemaining = entity.brain.getTimeUntilExpiry(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
val searchCountdownActive = brain.getMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
if (searchCountdownRemaining <= 20 && searchCountdownActive != false && searchCountdownActive != null) {
brain.sendDebugMessage("[SearchForEgg] Search countdown expired; clearing nest memories.")
(world.getBlockEntity(nestPos) as? NestBlockEntity)?.parents = Pair(null, null)
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
brain.eraseMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
brain.eraseMemory(CobblemonMemories.EGG_IN_NEST)
brain.eraseMemory(CobblemonMemories.POTENTIAL_MATE)
if (nestBlockEntity != null) {
nestBlockEntity.parents = Pair(null, null)
nestBlockEntity.nestClaimedBy = Pair(null, null)
}
return ObjectiveStatus.INACTIVE
}
val nestCenter = Vec3.atCenterOf(nestPos)
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.ACTIVE
}
remainingCooldownTicks = 20
val disturbancePos = brain.getMemory(MemoryModuleType.DISTURBANCE_LOCATION)
val targetVec = if (disturbancePos != null) {
val disturbanceVec = Vec3.atCenterOf(disturbancePos)
if (entity.position().distanceTo(disturbanceVec) > DISTURBANCE_MIN_DISTANCE) {
disturbanceVec
} else {
null
}
} else {
null
}
val finalTarget = targetVec ?: getRandomPositionAround(nestCenter, entity) ?: return ObjectiveStatus.ACTIVE
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(finalTarget, 1.0F, 1)) ?: return ObjectiveStatus.ACTIVE
brain.sendDebugMessage("[SearchForEgg] Searching near ${finalTarget}.")
movementTask = brain.fixate(SimpleMoveFixation(tracker, "Search for egg"))
return ObjectiveStatus.ACTIVE
}
private fun getRandomPositionAround(center: Vec3, entity: PathfinderMob): Vec3? {
repeat(10) {
val offsetX = Random.nextDouble(-SEARCH_RADIUS, SEARCH_RADIUS)
val offsetZ = Random.nextDouble(-SEARCH_RADIUS, SEARCH_RADIUS)
val targetVec = center.add(offsetX, 0.0, offsetZ)
val targetPos = BlockPos(targetVec.x.toInt(), targetVec.y.toInt(), targetVec.z.toInt())
if (entity.navigation.createPath(targetPos, 0) != null) {
return Vec3.atCenterOf(targetPos)
}
}
return null
}
companion object {
private const val SEARCH_RADIUS = 32.0
private const val DISTURBANCE_MIN_DISTANCE = 10.0
}
}
@@ -0,0 +1,79 @@
/*
* 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.ai.biggerbrain.objective
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.booleanVariable
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.config.task.SharedEntityVariables
import com.cobblemon.mod.common.api.ai.numberVariable
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.resolveFloat
import net.minecraft.tags.FluidTags
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
class StayAfloat(
val chance: Float = 0.8F
) : Objective {
override fun getDescription(entity: LivingEntity) = "Stay Afloat"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PathfinderMob) {
return ObjectiveStatus.INVALID
}
val fixation = brain.fixation
if (fixation is MovementTrackerFixation) {
// Ok if movement is in progress then we don't try staying afloat
return ObjectiveStatus.BLOCKED
}
if (entity.isInWater && (entity.getFluidHeight(FluidTags.WATER) > entity.fluidJumpThreshold) ||
(entity.isInLava && !entity.fireImmune())) {
// Cool do something
if (entity.random.nextFloat() < this.chance) {
entity.jumpControl.jump()
}
return ObjectiveStatus.ACTIVE
} else {
return ObjectiveStatus.INACTIVE
}
}
class Config : ObjectiveConfig {
val condition = booleanVariable(SharedEntityVariables.MOVEMENT_CATEGORY, "can_float", true).asExpressible()
val chance = numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, "float_chance", 0.8F).asExpressible()
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
condition,
chance
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): StayAfloat? {
return when {
condition.resolveBoolean(behaviourConfigurationContext.runtime) -> StayAfloat(chance.resolveFloat(behaviourConfigurationContext.runtime))
else -> null
}
}
}
}
@@ -0,0 +1,240 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import kotlin.math.abs
import kotlin.math.ceil
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.TamableAnimal
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator
/**
* Keeps track of the owner entity, and if they are in the same world but are too far away, fixates on getting closer.
* If they are extremely far away, will attempt to teleport to them. When walking to the owner, it pauses for a short
* duration after reaching them to look at them. It's cute.
*
* @author Hiroku
* @since December 29th, 2025
*/
class StayNearOwner(
val ticksBetweenAttempts: Int = 60,
val completionRange: Expression = "4.0".asExpression(),
val maxDistance: Expression = "14.0".asExpression(),
val teleportDistance: Expression = "24.0".asExpression(),
val speedMultiplier: Expression = "0.4".asExpression()
) : Objective {
var followTask: EntityFixation? = null
var remainingCooldownTicks = ticksBetweenAttempts
override fun getDescription(entity: LivingEntity) = "Stay near owner"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is TamableAnimal) {
return ObjectiveStatus.INVALID
} else if (brain.isFixation(followTask)) {
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
}
val owner = entity.owner ?: return ObjectiveStatus.INVALID
if (owner.level() != entity.level()) {
return ObjectiveStatus.INVALID
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
}
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val teleportDistance = mainThreadRuntime.resolveFloat(teleportDistance)
val maxDistance = mainThreadRuntime.resolveFloat(maxDistance)
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
val completionRange = mainThreadRuntime.resolveInt(completionRange)
val horizontalHitboxRadius = maxOf(entity.boundingBox.xsize, entity.boundingBox.zsize) / 2
val distance = entity.distanceTo(owner) - horizontalHitboxRadius
if (distance > teleportDistance) {
if (remainingCooldownTicks <= 0 && brain.distract(Distraction.FORCE)) {
remainingCooldownTicks = ticksBetweenAttempts
tryTeleport(entity, owner)
return ObjectiveStatus.ACTIVE
}
return if (remainingCooldownTicks <= 0) ObjectiveStatus.BLOCKED else ObjectiveStatus.ACTIVE
} else if (distance > maxDistance) {
if (remainingCooldownTicks <= 0 && brain.distract(comfortDistraction)) {
remainingCooldownTicks = ticksBetweenAttempts
val movementTracker = RegularMovementTracker.attempt(
entity = entity,
walkTarget = WalkTarget(
owner,
speedMultiplier,
maxOf(completionRange, ceil(horizontalHitboxRadius).toInt()) - 1
)
)
if (movementTracker != null) {
followTask = brain.fixate(
fixation = FollowOwnerFixation(
entity = entity,
owner = owner,
teleportDistance = teleportDistance,
movementTracker = movementTracker
)
)
}
}
}
return ObjectiveStatus.ACTIVE
}
private fun tryTeleport(entity: TamableAnimal, owner: Entity): Boolean {
val blockPos = owner.blockPosition()
for (i in 0..9) {
val j = this.getRandomInt(entity.random, -3, 3)
val k = this.getRandomInt(entity.random, -1, 1)
val l = this.getRandomInt(entity.random, -3, 3)
val succeeded = this.tryTeleportTo(entity, owner, blockPos.x + j, blockPos.y + k, blockPos.z + l)
if (succeeded) {
return true
}
}
return false
}
private fun tryTeleportTo(entity: TamableAnimal, owner: Entity, x: Int, y: Int, z: Int): Boolean {
if (abs(x.toDouble() - owner.x) < 2.0 && abs(z - owner.z) < 2.0) {
return false
} else if (!this.canTeleportTo(entity, BlockPos(x, y, z))) {
return false
} else {
entity.moveTo(
x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5,
entity.yRot,
entity.xRot
)
entity.navigation.stop()
return true
}
}
private fun canTeleportTo(entity: TamableAnimal, pos: BlockPos): Boolean {
val pathNodeType = WalkNodeEvaluator.getPathTypeStatic(entity, pos.mutable())
if (pathNodeType != PathType.WALKABLE) {
return false // Could be more complex to support fliers and swimmers
} else {
val blockPos = pos.subtract(entity.blockPosition())
return entity.level().noCollision(entity, entity.boundingBox.move(blockPos))
}
}
private fun getRandomInt(random: RandomSource, min: Int, max: Int) = random.nextInt(max - min + 1) + min
class FollowOwnerFixation(
entity: PathfinderMob,
val owner: LivingEntity,
val teleportDistance: Float,
override val movementTracker: MovementTracker,
): EntityFixation, LookingTrackerFixation, MovementTrackerFixation {
// Look at the owner for some amount of time after reaching them. For cuteness!
var lingeringStareTicks = (20..40).random()
override val lookingTracker: LookingTracker = LookingTracker(entity = entity, lookTarget = EntityTracker(owner, true))
override fun getDescription(entity: LivingEntity) = "Moving to owner"
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
// Easily distracted once we've reached the player.
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
lookingTracker.tick()
if (entity.distanceTo(owner) > teleportDistance) {
// Too far away, give up, the teleport will handle it
return false
}
if (movementTracker.lastStatus == MovementStatus.COMPLETED) {
lingeringStareTicks--
return lingeringStareTicks > 0
}
return tickMovementFixation() != MovementStatus.FAILED
}
}
class Config : ObjectiveConfig {
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
val completionRange: ExpressionOrEntityVariable = Either.left("4.0".asExpression())
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val teleportDistance: ExpressionOrEntityVariable = Either.left("24.0".asExpression())
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
ticksBetweenAttempts,
completionRange,
maxDistance,
teleportDistance,
speedMultiplier
).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
return StayNearOwner(
ticksBetweenAttempts = ticksBetweenAttempts,
completionRange = completionRange.asSimplifiedExpression(entity),
maxDistance = maxDistance.asSimplifiedExpression(entity),
teleportDistance = teleportDistance.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
)
}
}
}
@@ -0,0 +1,210 @@
/*
* 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.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.WanderType
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
/*
* I've scuffed the hell out of this thing. The logic for what types of wandering to do is unclear, and I haven't
* really decided whether it should be one mega function for finding a wander target and figuring out the type of
* goal in the process or if it should be cleanly separated for each type of wander target. I've done a bit of both
* and it's not pretty.
*/
class WanderAround(
val condition: Expression = "true".asExpression(),
val cooldownTicks: Int = 80,
val allowLand: Expression = "(q.entity.behaviour.moving.walk.can_walk && !q.entity.behaviour.moving.walk.avoids_land) || q.entity.behaviour.moving.fly.can_fly".asExpression(),
val allowWater: Expression = "q.entity.behaviour.moving.swim.can_swim_in_water && !q.entity.behaviour.moving.swim.avoids_water".asExpression(),
val allowAir: Expression = "q.entity.behaviour.moving.fly.can_fly && !q.entity.behaviour.moving.walk.can_walk".asExpression(),
val wanderChance: Expression = "1/(20*6)".asExpression(),
val horizontalRange: Expression = "10".asExpression(),
val verticalRange: Expression = "5".asExpression(),
val speedMultiplier: Expression = "0.35".asExpression(),
val avoidTargetingAir: Expression = "true".asExpression(),
val minimumHeight: Expression = "0".asExpression(),
val maximumHeight: Expression = "999".asExpression()
) : Objective {
var fixation: EntityFixation? = null
var remainingCooldownTicks = 10
override fun getDescription(entity: LivingEntity) = "Wander around"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (brain.isFixation(fixation)) {
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
return ObjectiveStatus.FIXATED
} else if (entity !is PathfinderMob) {
return ObjectiveStatus.INVALID
} else if (Objective.OBJECTIVE_TAG_WANDERING in activeObjectiveTags) {
return ObjectiveStatus.BLOCKED
} else if (!brain.canDistract(idleBoredomDistraction)) {
return ObjectiveStatus.BLOCKED
}
fixation = null
val runtime = mainThreadRuntime
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!runtime.resolveBoolean(condition)) {
return ObjectiveStatus.INACTIVE
} else if (Objective.OBJECTIVE_TAG_WANDERING in activeObjectiveTags) {
return ObjectiveStatus.BLOCKED
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.INACTIVE
}
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
val wanderChance = runtime.resolveFloat(this.wanderChance) * (wanderControl?.wanderChanceMultiplier ?: 1.0F)
if (wanderChance <= 1.0F && entity.level().random.nextFloat() > wanderChance) {
return ObjectiveStatus.ACTIVE
}
val allowLand = wanderControl?.allowLand ?: runtime.resolveBoolean(allowLand)
val allowWater = wanderControl?.allowWater ?: runtime.resolveBoolean(allowWater)
val allowAir = wanderControl?.allowAir ?: runtime.resolveBoolean(allowAir)
val types = mutableSetOf<WanderType>()
if ((allowLand || allowAir) && WanderType.LAND.isValid(entity)) {
types.add(WanderType.LAND)
}
if (allowWater && WanderType.WATER.isValid(entity)) {
types.add(WanderType.WATER)
}
if (types.isEmpty()) {
return ObjectiveStatus.INVALID
}
remainingCooldownTicks = cooldownTicks
val walkTarget = types.firstNotNullOfOrNull {
it.generateWalkTarget(
entity = entity,
horizontalRange = runtime.resolveInt(horizontalRange),
verticalRange = runtime.resolveInt(verticalRange),
minimumHeight = runtime.resolveInt(minimumHeight),
maximumHeight = runtime.resolveInt(maximumHeight),
avoidTargetingAir = runtime.resolveBoolean(avoidTargetingAir),
speedMultiplier = runtime.resolveFloat(speedMultiplier),
center = wanderControl?.center,
maxAttempts = wanderControl?.maxAttempts ?: 4
)
} ?: return ObjectiveStatus.ACTIVE
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
?: return ObjectiveStatus.ACTIVE
if (brain.distract(idleBoredomDistraction)) {
movementTracker.tick()
fixation = brain.fixate(
LookAndMoveFixation(
description = "Wandering",
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE),
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, walkTarget),
movementTracker = movementTracker
)
)
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING) // unsure if we need this
return ObjectiveStatus.FIXATED
}
return ObjectiveStatus.BLOCKED
}
class Config : ObjectiveConfig {
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val cooldownTicks: ExpressionOrEntityVariable = Either.left("80".asExpression())
val allowLand: ExpressionOrEntityVariable = Either.left("(q.entity.behaviour.moving.walk.can_walk && !q.entity.behaviour.moving.walk.avoids_land) || q.entity.behaviour.moving.fly.can_fly".asExpression())
val allowWater: ExpressionOrEntityVariable = Either.left("q.entity.behaviour.moving.swim.can_swim_in_water && !q.entity.behaviour.moving.swim.avoids_water".asExpression())
val allowAir: ExpressionOrEntityVariable = Either.left("q.entity.behaviour.moving.fly.can_fly && !q.entity.behaviour.moving.walk.can_walk".asExpression())
val wanderChance: ExpressionOrEntityVariable = Either.left("1/(20*6)".asExpression())
val horizontalRange: ExpressionOrEntityVariable = Either.left("10".asExpression())
val verticalRange: ExpressionOrEntityVariable = Either.left("5".asExpression())
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.35".asExpression())
val avoidTargetingAir: ExpressionOrEntityVariable = Either.left("true".asExpression())
val minimumHeight: ExpressionOrEntityVariable = Either.left("0".asExpression())
val maximumHeight: ExpressionOrEntityVariable = Either.left("999".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(
condition,
cooldownTicks,
allowLand,
allowWater,
allowAir,
wanderChance,
horizontalRange,
verticalRange,
speedMultiplier,
avoidTargetingAir,
minimumHeight,
maximumHeight
).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return WanderAround(
condition = condition.asSimplifiedExpression(entity),
cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime),
allowLand = allowLand.asSimplifiedExpression(entity),
allowWater = allowWater.asSimplifiedExpression(entity),
allowAir = allowAir.asSimplifiedExpression(entity),
wanderChance = wanderChance.asSimplifiedExpression(entity),
horizontalRange = horizontalRange.asSimplifiedExpression(entity),
verticalRange = verticalRange.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
avoidTargetingAir = avoidTargetingAir.asSimplifiedExpression(entity),
minimumHeight = minimumHeight.asSimplifiedExpression(entity),
maximumHeight = maximumHeight.asSimplifiedExpression(entity)
)
}
}
}
@@ -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.ai.biggerbrain.sense
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* Senses whether the entity is battling or not. Maintains the BATTLING_POKEMON memory.
*
* @author Hiroku
* @since December 29th, 2025
*/
class BattlingPokemonSense(
var cooldownTicks: Int = 2
) : Sense {
override var ticksUntilNextExecution: Int = 10
override val memories: Set<MemoryModuleType<*>> = setOf(CobblemonMemories.BATTLING_POKEMON)
override fun merge(other: Sense): Boolean {
if (other is BattlingPokemonSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
} else {
return false
}
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
val battles = when (entity) {
is PokemonEntity -> entity.battleId?.let { BattleRegistry.getBattle(it) }?.let { listOf(it) } ?: emptyList()
is NPCEntity -> entity.battleIds.mapNotNull(BattleRegistry::getBattle)
else -> emptyList()
}
val entityUUIDs = if (entity is PokemonEntity) { // Pokemon can look at opponents, never allies
battles.flatMap { battle ->
val actor = battle.getActor(entity)
if (actor != null) {
actor.getSide().getOppositeSide().activePokemon.mapNotNull { it.battlePokemon?.entity?.uuid }
} else {
emptyList()
}
}
} else if (entity is NPCEntity) { // NPCs can look at any of the pokemon in play
battles.flatMap { it.activePokemon }.mapNotNull { it.battlePokemon?.entity?.uuid }
} else {
emptyList()
}
brain.setMemory(CobblemonMemories.BATTLING_POKEMON, entityUUIDs)
if (entityUUIDs.isEmpty() && entity is PokemonEntity) {
brain.eraseMemoryIfPresent(CobblemonMemories.TARGETED_BATTLE_POKEMON)
}
ticksUntilNextExecution = cooldownTicks
}
class Config : SenseConfig {
var cooldownTicks: ExpressionOrEntityVariable = Either.left("2".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(cooldownTicks).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return BattlingPokemonSense(cooldownTicks = cooldownTicks)
}
}
}
@@ -0,0 +1,91 @@
/*
* 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.ai.biggerbrain.sense
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* Senses when the Pokémon is in a situation that would make it ready to sleep.
* Either by choice (nighttime) or by force (battle/spawning with sleep status)
*
* @author Hiroku
* @since March 23rd, 2024
*/
class DrowsySense(
var cooldownTicks: Int = 20
) : Sense {
override var ticksUntilNextExecution: Int = 20
override val memories = setOf(CobblemonMemories.POKEMON_DROWSY)
override fun merge(other: Sense): Boolean {
if (other is DrowsySense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
} else {
return false
}
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
if (entity !is PokemonEntity) {
return
}
val world = entity.level()
val rest = entity.behaviour.resting
val isDrowsy = brain.getMemory(CobblemonMemories.POKEMON_DROWSY) ?: false
val hurtLately = brain.hasMemoryValue(MemoryModuleType.HURT_BY)
val shouldBeDrowsy = rest.canSleep && !hurtLately && (world.dayTime.toInt() % 24000) in rest.times && !brain.hasMemoryValue(MemoryModuleType.ANGRY_AT)
val forcedAsleep = entity.pokemon.status?.status == Statuses.SLEEP
if (entity.isBattling) {
if (!isDrowsy && forcedAsleep) {
brain.setMemory(CobblemonMemories.POKEMON_DROWSY, true)
} else if (isDrowsy && !forcedAsleep) {
brain.eraseMemory(CobblemonMemories.POKEMON_DROWSY)
}
} else {
if (!isDrowsy && shouldBeDrowsy && entity.random.nextFloat() < rest.drowsyChance) {
brain.setMemory(CobblemonMemories.POKEMON_DROWSY, true)
} else if (isDrowsy && !shouldBeDrowsy) {
brain.eraseMemory(CobblemonMemories.POKEMON_DROWSY)
} else if (isDrowsy && entity.random.nextFloat() < rest.rouseChance) {
brain.eraseMemory(CobblemonMemories.POKEMON_DROWSY)
}
}
ticksUntilNextExecution = cooldownTicks
}
class Config : SenseConfig {
var cooldownTicks: ExpressionOrEntityVariable = Either.left("20".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(cooldownTicks).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return DrowsySense(cooldownTicks = cooldownTicks)
}
}
}
@@ -0,0 +1,144 @@
/*
* 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.ai.biggerbrain.sense
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
class EggSense(
private var cooldownTicks: Int = 100
) : Sense {
override var ticksUntilNextExecution: Int = 100
override val memories = setOf(
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_LOCATION,
CobblemonMemories.EGG_THIEF,
CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK,
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
MemoryModuleType.ATTACK_TARGET,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES
)
override fun merge(other: Sense): Boolean {
if (other is EggSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
if (entity !is PokemonEntity) return
val level = entity.level() as? ServerLevel ?: return
val nestPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return
val blockEntity = level.getBlockEntity(nestPos)
if (blockEntity is NestBlockEntity) {
val nestItem = blockEntity.getItem()
if (!nestItem.isEmpty && nestItem.item.asItem() == CobblemonItems.POKEMON_EGG.asItem()) {
brain.sendDebugMessage("[EggSense] Egg confirmed in nest at $nestPos.")
brain.setMemory(CobblemonMemories.EGG_LOCATION, nestPos)
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
return
}
}
brain.sendDebugMessage("[EggSense] Egg missing from nest at $nestPos; searching.")
brain.setMemory(CobblemonMemories.EGG_IN_NEST, false)
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
val nearbyEggItems = level.getEntitiesOfClass(ItemEntity::class.java, entity.boundingBox.inflate(20.0)) { itemEntity ->
val itemStack = itemEntity.item
if (itemStack.item.asItem() != CobblemonItems.POKEMON_EGG.asItem()) return@getEntitiesOfClass false
val eggComponent = itemStack.get(CobblemonItemComponents.EGG_DATA) ?: return@getEntitiesOfClass false
eggComponent.egg?.let { egg ->
egg.parentOne?.uuid == entity.pokemon.uuid || egg.parentTwo?.uuid == entity.pokemon.uuid
} == true
}
val existingEggThief = brain.getMemory(CobblemonMemories.EGG_THIEF) as? Player
val lastSeenTick = brain.getMemory(CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK)
val currentTick = level.gameTime
if (existingEggThief != null) {
if (isEntityTargetable(entity, existingEggThief)) {
brain.setMemory(CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK, currentTick)
brain.sendDebugMessage("[EggSense] Egg thief still in sight: ${existingEggThief.name.string}.")
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
return
}
val lastSeen = lastSeenTick ?: currentTick
if (currentTick - lastSeen > THIEF_LOST_SIGHT_TICKS) {
brain.sendDebugMessage("[EggSense] Lost sight of egg thief for too long; clearing.")
brain.eraseMemory(CobblemonMemories.EGG_THIEF)
brain.eraseMemory(CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK)
} else {
brain.sendDebugMessage("[EggSense] Egg thief out of sight; holding target.")
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
return
}
}
if (nearbyEggItems.isNotEmpty()) {
brain.sendDebugMessage("[EggSense] Egg item found on ground near ${nearbyEggItems[0].blockPosition()}.")
level.players().forEach { player ->
player.displayClientMessage("An egg has been found on the ground!".green(), true)
}
if (brain.hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
if (brain.hasMemoryValue(MemoryModuleType.ANGRY_AT)) brain.eraseMemory(MemoryModuleType.ANGRY_AT)
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
brain.setMemory(CobblemonMemories.EGG_LOCATION, nearbyEggItems[0].blockPosition())
brain.eraseMemory(CobblemonMemories.EGG_THIEF)
} else {
brain.sendDebugMessage("[EggSense] No egg or thief found; starting search timer.")
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
brain.eraseMemory(CobblemonMemories.EGG_THIEF)
brain.eraseMemory(CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK)
if (!brain.hasMemoryValue(CobblemonMemories.EGG_SEARCH_COUNTDOWN)) {
brain.setMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN, true, EGG_SEARCH_TIME)
}
}
}
private fun isEntityTargetable(entity: LivingEntity, player: Player): Boolean {
return entity.hasLineOfSight(player) && !player.isInvisible
}
class Config : SenseConfig {
override fun create(entity: LivingEntity, behaviourConfigurationContext: com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext): Sense? {
return EggSense()
}
}
companion object {
private const val EGG_SEARCH_TIME = 2400L
private const val THIEF_LOST_SIGHT_TICKS = 1200L
}
}
@@ -0,0 +1,92 @@
/*
* 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.ai.biggerbrain.sense
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.HurtDistraction
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* Maintains memory of the last entity that hurt this entity.
*
* @author Hiroku
* @since December 29th, 2025
*/
class HurtBySense(
var cooldownTicks: Int = 2
) : Sense {
var lastDamageSource: DamageSource? = null
override var ticksUntilNextExecution: Int = 1
override val memories = setOf(MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY)
override fun merge(other: Sense): Boolean {
if (other is HurtBySense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
} else {
return false
}
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
val damageSource = entity.lastDamageSource
if (damageSource === lastDamageSource) {
ticksUntilNextExecution = cooldownTicks
return
}
if (damageSource != null) {
brain.setMemory(MemoryModuleType.HURT_BY, entity.lastDamageSource)
val attacker = damageSource.entity
if (attacker is LivingEntity) {
brain.setMemory(MemoryModuleType.HURT_BY_ENTITY, attacker)
}
} else {
brain.eraseMemoryIfPresent(MemoryModuleType.HURT_BY)
}
// so apparently it remembers the entity that hurt them even if it was forever ago? fair enough I guess. that's vanilla for you.
brain.getMemory(MemoryModuleType.HURT_BY_ENTITY)?.let { livingEntity ->
if (!livingEntity.isAlive || livingEntity.level() != entity.level()) {
brain.eraseMemory(MemoryModuleType.HURT_BY_ENTITY)
}
}
if (damageSource != null && lastDamageSource !== damageSource) {
lastDamageSource = damageSource
brain.distract(HurtDistraction(damageSource))
}
ticksUntilNextExecution = cooldownTicks
}
class Config : SenseConfig {
var cooldownTicks: ExpressionOrEntityVariable = Either.left("2".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(cooldownTicks).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return HurtBySense(cooldownTicks = cooldownTicks)
}
}
}
@@ -0,0 +1,187 @@
/*
* 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.ai.biggerbrain.sense
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.nest.WildNestType
import com.cobblemon.mod.common.api.nest.WildNestTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
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.memory.MemoryModuleType
import net.minecraft.world.level.block.Blocks
import kotlin.jvm.optionals.getOrNull
class IdealNestPlacementSense(
private var cooldownTicks: Int = 100
) : Sense {
override var ticksUntilNextExecution: Int = 100
override val memories = setOf(
CobblemonMemories.IDEAL_NEST_LOCATION,
CobblemonMemories.READY_TO_BUILD_NEST,
CobblemonMemories.POTENTIAL_MATE
)
override fun merge(other: Sense): Boolean {
if (other is IdealNestPlacementSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
if (entity !is PokemonEntity) return
val world = entity.level() as? ServerLevel ?: return
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity
val combinedTypes = mate?.let { WildNestTypes.combinedTypes(entity, it) } ?: emptyList()
if (mate == null || combinedTypes.isEmpty()) {
debug(brain, "No mate or no combined nest types; clearing ideal nest memories.")
brain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
brain.eraseMemory(CobblemonMemories.READY_TO_BUILD_NEST)
mate?.biggerBrain?.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
mate?.biggerBrain?.eraseMemory(CobblemonMemories.READY_TO_BUILD_NEST)
return
}
if (brain.getMemory(CobblemonMemories.READY_TO_BUILD_NEST) != true) {
debug(brain, "Ready to build set true for both parents.")
brain.setMemory(CobblemonMemories.READY_TO_BUILD_NEST, true)
mate.biggerBrain.setMemory(CobblemonMemories.READY_TO_BUILD_NEST, true)
}
val storedNestPos = brain.getMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
if (storedNestPos != null) {
if (isValidNestPlacement(world, entity, mate, storedNestPos, combinedTypes)) {
debug(brain, "Stored ideal nest still valid at $storedNestPos.")
mate.biggerBrain.setMemory(CobblemonMemories.IDEAL_NEST_LOCATION, storedNestPos)
return
} else {
debug(brain, "Stored ideal nest invalid at $storedNestPos; clearing.")
brain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
mate.biggerBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
}
}
val entityPos = entity.blockPosition()
val validNestPosition = findValidNestPlacement(world, entity, mate, entityPos, combinedTypes)
if (validNestPosition != null) {
debug(brain, "Found ideal nest position at $validNestPosition.")
brain.setMemory(CobblemonMemories.IDEAL_NEST_LOCATION, validNestPosition)
mate.biggerBrain.setMemory(CobblemonMemories.IDEAL_NEST_LOCATION, validNestPosition)
} else {
debug(brain, "No valid ideal nest position found.")
brain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
mate.biggerBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
}
}
private fun findValidNestPlacement(world: ServerLevel, entity: PokemonEntity, mate: PokemonEntity?, entityPos: BlockPos, sharedTypes: List<WildNestType>): BlockPos? {
return BlockPos.betweenClosed(
entityPos.offset(-DETECTION_RADIUS, -5, -DETECTION_RADIUS),
entityPos.offset(DETECTION_RADIUS, 5, DETECTION_RADIUS)
).firstOrNull { pos ->
val blockBelowState = world.getBlockState(pos)
val nestPos = pos.above()
val blockAbove = world.getBlockState(nestPos).block
val blockAbove2 = world.getBlockState(nestPos.above()).block
val placement = WildNestTypes.resolvePlacement(sharedTypes, blockBelowState, world.getFluidState(nestPos)) ?: return@firstOrNull false
if (!isWaterGroupCompatible(entity, mate, placement.waterlogged)) return@firstOrNull false
if (placement.waterlogged) {
if (!(blockAbove == Blocks.WATER && blockAbove2 == Blocks.WATER)) return@firstOrNull false
} else {
if (!(blockAbove == Blocks.AIR && blockAbove2 == Blocks.AIR)) return@firstOrNull false
}
if (isNearExistingNest(world, nestPos)) return@firstOrNull false
if (!canPathfindToTarget(entity, nestPos)) return@firstOrNull false
return@firstOrNull true
}?.above()
}
private fun isValidNestPlacement(world: ServerLevel, entity: PokemonEntity, mate: PokemonEntity?, pos: BlockPos, sharedTypes: List<WildNestType>): Boolean {
val blockBelowState = world.getBlockState(pos.below())
val blockAbove = world.getBlockState(pos.above()).block
val blockAbove2 = world.getBlockState(pos.above(2)).block
val placement = WildNestTypes.resolvePlacement(sharedTypes, blockBelowState, world.getFluidState(pos)) ?: return false
if (!isWaterGroupCompatible(entity, mate, placement.waterlogged)) return false
if (placement.waterlogged) {
if (!(blockAbove == Blocks.WATER && blockAbove2 == Blocks.WATER)) return false
} else {
if (!(blockAbove == Blocks.AIR && blockAbove2 == Blocks.AIR)) return false
}
if (isNearExistingNest(world, pos)) return false
if (!canPathfindToTarget(entity, pos)) return false
return true
}
private fun canPathfindToTarget(entity: PathfinderMob, targetPos: BlockPos): Boolean {
val path = entity.navigation.createPath(targetPos, 0)
return path != null && path.distToTarget < 1.75f
}
private fun isNearExistingNest(world: ServerLevel, pos: BlockPos): Boolean {
return BlockPos.betweenClosed(
pos.offset(-MIN_NEST_DISTANCE, -MIN_NEST_DISTANCE, -MIN_NEST_DISTANCE),
pos.offset(MIN_NEST_DISTANCE, MIN_NEST_DISTANCE, MIN_NEST_DISTANCE)
).any { checkPos ->
world.getBlockEntity(checkPos) is com.cobblemon.mod.common.block.entity.NestBlockEntity
}
}
private fun isWaterGroupCompatible(
entity: PokemonEntity,
mate: PokemonEntity?,
isNestWaterlogged: Boolean
): Boolean {
val waterEggGroups = setOf("water_1", "water_2", "water_3")
val entityEggGroups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
val entityHasWater = entityEggGroups.any { it in waterEggGroups }
val entityOnlyWater = entityEggGroups.all { it in waterEggGroups }
val mateEggGroups = mate?.pokemon?.species?.eggGroups?.map { it.name.lowercase() } ?: emptyList()
val mateHasWater = mateEggGroups.any { it in waterEggGroups }
val mateOnlyWater = mateEggGroups.isNotEmpty() && mateEggGroups.all { it in waterEggGroups }
return if (isNestWaterlogged) {
entityHasWater && mateHasWater
} else {
!(entityOnlyWater || mateOnlyWater)
}
}
private fun debug(brain: BiggerBrain, message: String) {
brain.sendDebugMessage("[IdealNestPlacementSense] $message")
}
class Config : SenseConfig {
override fun create(entity: LivingEntity, behaviourConfigurationContext: com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext): Sense? {
return IdealNestPlacementSense()
}
}
companion object {
private const val DETECTION_RADIUS = 10
private const val MIN_NEST_DISTANCE = 10
}
}
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.breeding.BreedingLogicManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.phys.AABB
class MateSense(
private var cooldownTicks: Int = 1200
) : Sense {
override var ticksUntilNextExecution: Int = 1200
override val memories = setOf(
CobblemonMemories.POTENTIAL_MATE,
CobblemonMemories.MATE_SCAN_COOLDOWN,
CobblemonMemories.PATERNAL_NEST_LOCATION
)
override fun merge(other: Sense): Boolean {
if (other is MateSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
if (entity !is PokemonEntity) return
if (brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) != null) {
return
}
if (brain.getMemory(CobblemonMemories.MATE_SCAN_COOLDOWN) == true) {
return
}
brain.eraseMemory(CobblemonMemories.POTENTIAL_MATE)
if (entity.pokemon.breedingCooldown != 0) return
val world = entity.level() as? ServerLevel ?: return
val potentialMate = world.getEntitiesOfClass(
PokemonEntity::class.java,
AABB(entity.blockPosition()).inflate(DETECTION_RADIUS)
) { nearbyEntity ->
nearbyEntity != entity && isValidMate(entity, nearbyEntity)
}.firstOrNull()
brain.setMemory(CobblemonMemories.MATE_SCAN_COOLDOWN, true, MATE_SCAN_COOLDOWN)
if (potentialMate != null) {
brain.setMemory(CobblemonMemories.POTENTIAL_MATE, potentialMate)
potentialMate.biggerBrain.setMemory(CobblemonMemories.POTENTIAL_MATE, entity)
potentialMate.biggerBrain.setMemory(CobblemonMemories.MATE_SCAN_COOLDOWN, true, MATE_SCAN_COOLDOWN)
} else {
brain.eraseMemory(CobblemonMemories.POTENTIAL_MATE)
}
}
private fun isValidMate(entity: PokemonEntity, mate: PokemonEntity): Boolean {
if (entity.isBattling || entity.isBattleClone() || (!entity.pokemon.isWild() && entity.tethering?.tetheringId == null)) return false
if (mate.pokemon.breedingCooldown != 0) return false
val canBreed = BreedingLogicManager.canBreed(entity.pokemon, mate.pokemon)
if (!canBreed) return false
val isCompatible = BreedingLogicManager.checkCompatibility(entity.pokemon, mate.pokemon)
val bothWild = (entity.pokemon.isWild() && mate.pokemon.isWild())
val bothOwned = (!entity.pokemon.isWild() && !mate.pokemon.isWild())
val mateHasMate = mate.biggerBrain.getMemory(CobblemonMemories.POTENTIAL_MATE) != null
return !mateHasMate && isCompatible && (bothWild || bothOwned)
}
class Config : SenseConfig {
var cooldownTicks: ExpressionOrEntityVariable = Either.left("1200".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(cooldownTicks).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return MateSense(cooldownTicks = cooldownTicks)
}
}
companion object {
private const val DETECTION_RADIUS = 10.0
private const val MATE_SCAN_COOLDOWN = 1200L
}
}
@@ -0,0 +1,72 @@
/*
* 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.ai.biggerbrain.sense
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
/**
* A sense that checks if an NPC is currently in a battle. Maintains the NPC_BATTLING memory.
*
* @author Hiroku
* @since December 29th, 2025
*/
class NPCBattlingSense(
var cooldownTicks: Int = 10
) : Sense {
override var ticksUntilNextExecution: Int = 20
override val memories = setOf(CobblemonMemories.NPC_BATTLING)
override fun merge(other: Sense): Boolean {
if (other is NPCBattlingSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
} else {
return false
}
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
if (entity !is NPCEntity) {
return // Surely they didn't put this on a non-NPC
}
val isBattling = entity.isInBattle()
if (isBattling) {
brain.setMemoryIfChanged(CobblemonMemories.NPC_BATTLING, value = true)
} else {
brain.eraseMemoryIfPresent(CobblemonMemories.NPC_BATTLING)
}
ticksUntilNextExecution = cooldownTicks
}
class Config : SenseConfig {
var cooldownTicks: ExpressionOrEntityVariable = Either.left("2".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(cooldownTicks).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return NPCBattlingSense(cooldownTicks = cooldownTicks)
}
}
}
@@ -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.ai.biggerbrain.sense
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveDouble
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.mojang.datafixers.util.Either
import java.util.function.Predicate
import java.util.function.ToDoubleFunction
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
/**
* Senses nearby entities. Maintains the NEAREST_LIVING_ENTITIES and NEAREST_VISIBLE_LIVING_ENTITIES memories.
*
* @author Hiroku
* @since December 29th, 2025
*/
class NearbyEntitiesSense(
var condition: Expression,
var radiusXZ: Double = 16.0,
var radiusY: Double = 16.0,
var cooldownTicks: Int = 20 * 5
) : Sense {
override var ticksUntilNextExecution: Int = 50
override val memories = setOf(MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
override fun merge(other: Sense): Boolean {
if (other is NearbyEntitiesSense) {
this.condition = other.condition
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
this.radiusXZ = other.radiusXZ
this.radiusY = other.radiusY
return true
} else {
return false
}
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
if (!brain.runtime.resolveBoolean(condition)) {
return
}
val level = entity.level()
val aABB = entity.boundingBox.inflate(radiusXZ, radiusY, radiusXZ)
val list = level.getEntitiesOfClass(LivingEntity::class.java, aABB, Predicate { it !== entity && it!!.isAlive })
list.sortWith(Comparator.comparingDouble(ToDoubleFunction { it.distanceToSqr(entity) }))
brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, list)
brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, NearestVisibleLivingEntities(entity, list))
}
class Config : SenseConfig {
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
var radiusXZ: ExpressionOrEntityVariable = Either.left("16.0".asExpression())
var radiusY: ExpressionOrEntityVariable = Either.left("16.0".asExpression())
var cooldownTicks: ExpressionOrEntityVariable = Either.left("60".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
condition,
radiusXZ,
radiusY,
cooldownTicks
).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
return NearbyEntitiesSense(
condition = condition.asSimplifiedExpression(entity),
radiusXZ = radiusXZ.resolveDouble(behaviourConfigurationContext.runtime),
radiusY = radiusY.resolveDouble(behaviourConfigurationContext.runtime),
cooldownTicks = cooldownTicks.resolveDouble(behaviourConfigurationContext.runtime).toInt()
)
}
}
}
@@ -0,0 +1,204 @@
/*
* 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.ai.biggerbrain.sense
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.nest.WildNestType
import com.cobblemon.mod.common.api.nest.WildNestTypes
import com.cobblemon.mod.common.block.WildNestBlock
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
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.memory.MemoryModuleType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import kotlin.jvm.optionals.getOrNull
class NestSense(
private var cooldownTicks: Int = 200
) : Sense {
override var ticksUntilNextExecution: Int = 200
override val memories = setOf(
CobblemonMemories.NEST_LOCATION,
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_LOCATION,
CobblemonMemories.NEST_DESPAWN_IMMUNE_UNTIL_TICK
)
override fun merge(other: Sense): Boolean {
if (other is NestSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
if (entity !is PokemonEntity) return
val world = entity.level() as? ServerLevel ?: return
val paternalNestLocation = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
if (paternalNestLocation != null) {
val blockAtLocation = world.getBlockEntity(paternalNestLocation)
if (blockAtLocation !is NestBlockEntity) {
debug(brain, "Paternal nest missing at $paternalNestLocation; clearing memories.")
brain.eraseMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
brain.eraseMemory(CobblemonMemories.EGG_IN_NEST)
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
} else if (blockAtLocation.parents.first == entity.uuid || blockAtLocation.parents.second == entity.uuid) {
debug(brain, "Paternal nest confirmed at $paternalNestLocation.")
brain.setMemory(CobblemonMemories.PATERNAL_NEST_LOCATION, paternalNestLocation)
brain.setMemory(CobblemonMemories.NEST_LOCATION, paternalNestLocation)
if (blockAtLocation.getItem().item == ItemStack.EMPTY.item) {
brain.setMemory(CobblemonMemories.EGG_IN_NEST, false)
} else {
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
}
return
} else {
debug(brain, "Paternal nest no longer owned by this Pokémon; clearing memories.")
brain.eraseMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
brain.setMemory(CobblemonMemories.EGG_IN_NEST, false)
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
}
}
val nearbyNest = findNearbyNest(entity, brain, world, entity.blockPosition())
if (nearbyNest != null) {
debug(brain, "Nearby nest selected at $nearbyNest.")
brain.setMemory(CobblemonMemories.NEST_LOCATION, nearbyNest)
} else {
debug(brain, "No valid nearby nest found.")
brain.eraseMemory(CobblemonMemories.NEST_LOCATION)
}
}
private fun findNearbyNest(pokemonEntity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, entityPos: BlockPos): BlockPos? {
return BlockPos.betweenClosed(
entityPos.offset(-20, -5, -20),
entityPos.offset(20, 5, 20)
).firstOrNull { pos ->
val blockEntity: BlockEntity? = world.getBlockEntity(pos)
if (blockEntity !is NestBlockEntity) return@firstOrNull false
if (pokemonEntity.isBattling || pokemonEntity.isBattleClone() || (!pokemonEntity.pokemon.isWild() && pokemonEntity.tethering?.tetheringId == null)) return@firstOrNull false
val nestBlockEntity = blockEntity
val allowedTypes = allowedNestTypes(pokemonEntity, brain)
val nestType = (nestBlockEntity.blockState.block as? WildNestBlock)?.type
if (nestType == null) {
debug(brain, "Rejecting nest at $pos: missing WildNestBlock type.")
return@firstOrNull false
}
if (!isNestTypeAllowed(nestType, pokemonEntity, allowedTypes)) {
debug(brain, "Rejecting nest at $pos: type=$nestType not in $allowedTypes.")
return@firstOrNull false
}
if (!canPathfindToTarget(pokemonEntity, nestBlockEntity.blockPos)) return@firstOrNull false
val isWildNest = nestBlockEntity.ownerUUID == null
val isOwnedNest = nestBlockEntity.ownerUUID != null
val isClaimed = nestBlockEntity.nestClaimedBy.first != null || nestBlockEntity.nestClaimedBy.second != null
val isEggless = nestBlockEntity.getItem(0) == ItemStack.EMPTY
val isNestWaterlogged = nestBlockEntity.blockState.getOptionalValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED).orElse(false)
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity
val nestClaimerOne = nestBlockEntity.nestClaimedBy.first?.let { world.getEntity(it) }
val nestClaimerTwo = nestBlockEntity.nestClaimedBy.second?.let { world.getEntity(it) }
val isClaimerOne = pokemonEntity.uuid == nestClaimerOne?.uuid
val isClaimerTwo = pokemonEntity.uuid == nestClaimerTwo?.uuid
val nestParentOneActive = nestBlockEntity.parents.first?.let { world.getEntity(it) }
val nestParentTwoActive = nestBlockEntity.parents.second?.let { world.getEntity(it) }
if (isWildNest && isEggless && ((nestParentOneActive != null || nestParentTwoActive != null) || (isClaimed && (nestClaimerOne != null && !isClaimerOne && nestClaimerTwo != null && !isClaimerTwo)))) {
debug(brain, "Rejecting nest at $pos: claimed or active parents present.")
return@firstOrNull false
}
if (!isWaterGroupCompatible(pokemonEntity, mate, isNestWaterlogged)) {
debug(brain, "Rejecting nest at $pos: water-group compatibility failed (waterlogged=$isNestWaterlogged).")
return@firstOrNull false
}
return@firstOrNull when {
pokemonEntity.tethering?.tetheringId != null -> isOwnedNest && isEggless
else -> isWildNest && isEggless
}
}
}
private fun canPathfindToTarget(entity: PathfinderMob, targetPos: BlockPos): Boolean {
val path = entity.navigation.createPath(targetPos, 0)
return path != null && path.distToTarget < 1.75f
}
private fun isWaterGroupCompatible(
entity: PokemonEntity,
mate: PokemonEntity?,
isNestWaterlogged: Boolean
): Boolean {
val waterEggGroups = setOf("water_1", "water_2", "water_3")
val entityEggGroups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
val entityHasWater = entityEggGroups.any { it in waterEggGroups }
val entityOnlyWater = entityEggGroups.all { it in waterEggGroups }
val mateEggGroups = mate?.pokemon?.species?.eggGroups?.map { it.name.lowercase() } ?: emptyList()
val mateHasWater = mateEggGroups.any { it in waterEggGroups }
val mateOnlyWater = mateEggGroups.isNotEmpty() && mateEggGroups.all { it in waterEggGroups }
return if (isNestWaterlogged) {
entityHasWater && mateHasWater
} else {
!(entityOnlyWater || mateOnlyWater)
}
}
private fun allowedNestTypes(entity: PokemonEntity, brain: BiggerBrain): Set<WildNestType> {
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity
val types = if (mate != null) {
WildNestTypes.combinedTypes(entity, mate)
} else {
WildNestTypes.typesFor(entity.pokemon.species)
}
val mutable = types.toMutableSet()
if (!entity.pokemon.isWild() && mate != null && !mate.pokemon.isWild()) {
mutable.add(WildNestType.PLAYER)
}
return mutable
}
private fun isNestTypeAllowed(
nestType: WildNestType,
entity: PokemonEntity,
allowedTypes: Set<WildNestType>
): Boolean {
if (nestType == WildNestType.PLAYER) {
return !entity.pokemon.isWild() && allowedTypes.contains(WildNestType.PLAYER)
}
return allowedTypes.contains(nestType)
}
private fun debug(brain: BiggerBrain, message: String) {
brain.sendDebugMessage("[NestSense] $message")
}
class Config : SenseConfig {
override fun create(entity: LivingEntity, behaviourConfigurationContext: com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext): Sense? {
return NestSense()
}
}
}
@@ -0,0 +1,107 @@
/*
* 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.ai.biggerbrain.sense
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.AgeableMob
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
/**
* Senses when the Pokémon is near an adult of the same species. Maintains the NEAREST_VISIBLE_ADULT memory.
*
* @author broccoli
* @since May 15th, 2024
*/
class PokemonAdultSense(
var condition: Expression,
var cooldownTicks: Int = 100
) : Sense {
override var ticksUntilNextExecution: Int = 100
override val memories = setOf(MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
override fun merge(other: Sense): Boolean {
if (other is PokemonAdultSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
} else {
return false
}
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
if (entity !is PokemonEntity || !brain.runtime.resolveBoolean(condition)) {
return
}
brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)?.let { visibleMobs ->
setNearestPokemonAdult(entity, visibleMobs, brain)
}
}
private fun setNearestPokemonAdult(entity: PokemonEntity, visibleMobs: NearestVisibleLivingEntities, brain: BiggerBrain) {
val nearestAdult = visibleMobs.findClosest { mob ->
targetIsValidForChild(entity, mob)
}.map { mob -> mob as AgeableMob }.orElse(null)
if (nearestAdult == null && brain.hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_ADULT)) {
brain.eraseMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT)
} else if (nearestAdult != null && brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT) != nearestAdult) {
brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT, nearestAdult)
}
}
private fun targetIsValidForChild(child: PokemonEntity, entity: LivingEntity): Boolean {
val potentialParent = entity as? PokemonEntity ?: return false
val childSpecies = child.pokemon.species.resourceIdentifier
var preEvolution = potentialParent.pokemon.preEvolution
while (preEvolution != null) {
if (childSpecies == preEvolution.species.resourceIdentifier) {
return true
}
preEvolution = preEvolution.species.preEvolution
}
return false
}
class Config : SenseConfig {
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
var cooldownTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(cooldownTicks).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return PokemonAdultSense(
condition = condition.asSimplifiedExpression(entity),
cooldownTicks = cooldownTicks
)
}
}
}
@@ -0,0 +1,50 @@
/*
* 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.ai.biggerbrain.sense
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class PokemonDisturbanceSense(
private var cooldownTicks: Int = 20
) : Sense {
override var ticksUntilNextExecution: Int = 20
override val memories = setOf(MemoryModuleType.DISTURBANCE_LOCATION)
override fun merge(other: Sense): Boolean {
if (other is PokemonDisturbanceSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
val world = entity.level() as? ServerLevel ?: return
val nearestPlayer = world.players()
.filter { it.isAlive && entity.distanceToSqr(it) <= 16 * 16 }
.minByOrNull { entity.distanceToSqr(it) }
nearestPlayer?.let {
brain.setMemory(MemoryModuleType.DISTURBANCE_LOCATION, BlockPos(it.blockPosition()))
}
}
class Config : SenseConfig {
override fun create(entity: LivingEntity, behaviourConfigurationContext: com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext): Sense? {
return PokemonDisturbanceSense()
}
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.SweetBerryBushSensor
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* A sense that an entity can use to gather information and store it in its brain's memory. This is an analog to a
* Minecraft SensorType. Unlike SensorTypes, Senses are instantiated per entity and can handle cooldowns in a more
* bespoke way. The remaining cooldown time is something that is set by the sense itself rather than it being forced,
* so the cooldown can differ.
*
* @author Hiroku
* @since December 29th, 2025
*/
interface Sense {
companion object {
val types = mutableMapOf(
cobblemonResource("battling_pokemon") to BattlingPokemonSense::class.java,
cobblemonResource("drowsy") to DrowsySense::class.java,
cobblemonResource("nearby_entities") to NearbyEntitiesSense::class.java,
cobblemonResource("npc_battling") to NPCBattlingSense::class.java,
cobblemonResource("pokemon_adult") to PokemonAdultSense::class.java,
cobblemonResource("hurt_by") to HurtBySense::class.java,
cobblemonResource("mate_sensor") to MateSense::class.java,
cobblemonResource("nest_sensor") to NestSense::class.java,
cobblemonResource("egg_sensor") to EggSense::class.java,
cobblemonResource("ideal_nest_location_sensor") to IdealNestPlacementSense::class.java,
cobblemonResource("pokemon_disturbance") to PokemonDisturbanceSense::class.java,
cobblemonResource("hurt_by") to HurtBySense::class.java,
cobblemonResource("nearby_sweet_berry_bush") to SweetBerryBushSense::class.java
)
}
/** The ticks until it will next be ticked. This is also displayed in the debug overlay. */
var ticksUntilNextExecution: Int
/** The set of memories that this sense may write to or read from. This ensures they are registered in the brain. */
val memories: Set<MemoryModuleType<*>>
/** Performs the sensing logic. Alter [ticksUntilNextExecution] to apply a cooldown if necessary. */
fun tick(entity: LivingEntity, brain: BiggerBrain)
/** Attempts to merge the [other] Sense into this one. Returns true if it was merged and therefore should not be added to the brain separately. */
fun merge(other: Sense): Boolean
}
@@ -0,0 +1,109 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getMemorySafely
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.CaveVines
import net.minecraft.world.level.block.SweetBerryBushBlock
import net.minecraft.world.level.block.state.BlockState
import java.util.Optional
class SweetBerryBushSense(
private var cooldownTicks: Int = SCAN_INTERVAL
) : Sense {
override var ticksUntilNextExecution: Int = SCAN_INTERVAL
override val memories = setOf(
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH,
CobblemonMemories.DISABLE_WALK_TO_BERRY_BUSH,
)
override fun merge(other: Sense): Boolean {
if (other is SweetBerryBushSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
if (entity !is PokemonEntity) return
val world = entity.level() as? ServerLevel ?: return
entity.brain.getMemorySafely(CobblemonMemories.DISABLE_WALK_TO_BERRY_BUSH).orElse(false) ?: return
val nearestBush = findNearestBush(world, entity.blockPosition())
if (!nearestBush.isEmpty) {
entity.brain.setMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH, nearestBush)
} else {
entity.brain.eraseMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH)
}
}
private fun findNearestBush(level: ServerLevel, origin: BlockPos): Optional<BlockPos> {
var closest: BlockPos? = null
var closestDistSq = Double.MAX_VALUE
for (pos in BlockPos.betweenClosed(origin.offset(-SCAN_RADIUS_HORIZONTAL, 1 - SCAN_RADIUS_VERTICAL, -SCAN_RADIUS_HORIZONTAL),
origin.offset(SCAN_RADIUS_HORIZONTAL, SCAN_RADIUS_VERTICAL - 1, SCAN_RADIUS_HORIZONTAL))) {
val state: BlockState = level.getBlockState(pos)
if ((state.`is`(Blocks.SWEET_BERRY_BUSH) && state.getValue(SweetBerryBushBlock.AGE) >= 2)
|| (CaveVines.hasGlowBerries(state))) {
val distSq = pos.distSqr(origin)
if (distSq < closestDistSq) {
closest = pos.immutable()
closestDistSq = distSq
}
}
}
return Optional.ofNullable(closest)
}
class Config : SenseConfig {
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
var cooldownTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(cooldownTicks).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return SweetBerryBushSense(
// condition = condition.asSimplifiedExpression(entity),
cooldownTicks = cooldownTicks
)
}
}
companion object {
const val SCAN_RADIUS_HORIZONTAL = 10
const val SCAN_RADIUS_VERTICAL = 1
const val SCAN_INTERVAL = 40
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
class AddObjectivesConfig : BehaviourConfig {
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val objectivesByPriority: Map<Int, List<ObjectiveConfig>> = mapOf()
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(condition).asVariables() + objectivesByPriority
.values
.flatten()
.flatMap { it.getVariables(entity, behaviourConfigurationContext) }
}
override fun configure(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext){
if (!checkCondition(behaviourConfigurationContext, condition)) return
for ((priority, objectiveConfigs) in objectivesByPriority) {
val objectives = objectiveConfigs.mapNotNull { it.create(entity, behaviourConfigurationContext) }
val priorityObjectives = behaviourConfigurationContext.objectives.getOrPut(priority) { mutableListOf() }
priorityObjectives.addAll(objectives)
}
}
}
@@ -0,0 +1,40 @@
/*
* 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.ai.config
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
class AddSensesConfig : BehaviourConfig {
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val senses: List<SenseConfig> = listOf()
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(condition).asVariables() + senses.flatMap { it.getVariables(entity, behaviourConfigurationContext) }
}
override fun configure(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) {
if (!checkCondition(behaviourConfigurationContext, condition)) {
return
}
behaviourConfigurationContext.addSenses(senses.mapNotNull { it.create(entity, behaviourConfigurationContext) })
}
}
@@ -35,9 +35,11 @@ import net.minecraft.world.entity.LivingEntity
*/
interface BehaviourConfig {
companion object {
val types = mutableMapOf<ResourceLocation, Class<out BehaviourConfig>>(
val types = mutableMapOf(
cobblemonResource("script") to ScriptBehaviourConfig::class.java,
cobblemonResource("add_tasks_to_activity") to AddTasksToActivity::class.java,
cobblemonResource("add_objectives") to AddObjectivesConfig::class.java,
cobblemonResource("add_senses") to AddSensesConfig::class.java,
cobblemonResource("apply_behaviours") to ApplyBehaviours::class.java,
cobblemonResource("set_default_activity") to SetDefaultActivity::class.java,
cobblemonResource("set_core_activities") to SetCoreActivities::class.java,
@@ -0,0 +1,75 @@
/*
* 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.ai.config.objective
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.AttackTarget
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.EatGrass
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.FocusDuringBattle
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.GoToSleep
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.HarvestSweetBerryBush
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Herd
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.LookAtNearbyEntities
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.MaintainFluidY
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.MoveIntoWater
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.StayAfloat
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.StayNearOwner
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.MoveOutOfWater
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.MoveToRestingPlace
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.WanderAround
import com.cobblemon.mod.common.api.ai.config.task.AttackEggThiefTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.BreedAtNestTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.BuildNestTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.GuardNestTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.PathToIdealNestLocationTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.PathToNestTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.PickUpEggTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.PlaceEggInNestTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.RetrieveEggTaskConfig
import com.cobblemon.mod.common.api.ai.config.task.SearchForEggTaskConfig
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
interface ObjectiveConfig {
companion object {
val types: MutableMap<ResourceLocation, Class<out ObjectiveConfig>> = mutableMapOf(
cobblemonResource("focus_during_battle") to FocusDuringBattle.Config::class.java,
cobblemonResource("look_at_nearby_entities") to LookAtNearbyEntities.Config::class.java,
cobblemonResource("stay_near_owner") to StayNearOwner.Config::class.java,
cobblemonResource("wander_around") to WanderAround.Config::class.java,
cobblemonResource("eat_grass") to EatGrass.Config::class.java,
cobblemonResource("herd") to Herd.Config::class.java,
cobblemonResource("stay_afloat") to StayAfloat.Config::class.java,
cobblemonResource("maintain_fluid_y") to MaintainFluidY.Config::class.java,
cobblemonResource("move_out_of_water") to MoveOutOfWater.Config::class.java,
cobblemonResource("move_into_water") to MoveIntoWater.Config::class.java,
cobblemonResource("move_to_resting_place") to MoveToRestingPlace.Config::class.java,
cobblemonResource("go_to_sleep") to GoToSleep.Config::class.java,
cobblemonResource("attack_target") to AttackTarget.Config::class.java,
cobblemonResource("path_to_nest") to PathToNestTaskConfig::class.java,
cobblemonResource("breed_at_nest") to BreedAtNestTaskConfig::class.java,
cobblemonResource("guard_nest") to GuardNestTaskConfig::class.java,
cobblemonResource("search_for_egg") to SearchForEggTaskConfig::class.java,
cobblemonResource("retrieve_egg") to RetrieveEggTaskConfig::class.java,
cobblemonResource("pick_up_egg") to PickUpEggTaskConfig::class.java,
cobblemonResource("place_egg_in_nest") to PlaceEggInNestTaskConfig::class.java,
cobblemonResource("attack_egg_thief") to AttackEggThiefTaskConfig::class.java,
cobblemonResource("path_to_ideal_nest_location") to PathToIdealNestLocationTaskConfig::class.java,
cobblemonResource("build_nest") to BuildNestTaskConfig::class.java,
cobblemonResource("harvest_sweet_berry_bush") to HarvestSweetBerryBush.Config::class.java,
)
}
/** The variables that this objective config uses. These are used to declare variables on the entity cleanly. */
fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable>
fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective?
}
@@ -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.api.ai.config.sense
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.BattlingPokemonSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.DrowsySense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.EggSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.HurtBySense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.IdealNestPlacementSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.MateSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.NPCBattlingSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.NestSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.NearbyEntitiesSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.PokemonDisturbanceSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.PokemonAdultSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.SweetBerryBushSense
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.LivingEntity
/**
* Creates a [Sense] for an entity.
*
* @author Hiroku
* @since December 29th, 2025
*/
interface SenseConfig {
companion object {
val types = mutableMapOf(
cobblemonResource("nearby_entities") to NearbyEntitiesSense.Config::class.java,
cobblemonResource("drowsy") to DrowsySense.Config::class.java,
cobblemonResource("hurt_by") to HurtBySense.Config::class.java,
cobblemonResource("battling_pokemon") to BattlingPokemonSense.Config::class.java,
cobblemonResource("npc_battling") to NPCBattlingSense.Config::class.java,
cobblemonResource("pokemon_adult") to PokemonAdultSense.Config::class.java,
cobblemonResource("mate_sensor") to MateSense.Config::class.java,
cobblemonResource("nest_sensor") to NestSense.Config::class.java,
cobblemonResource("egg_sensor") to EggSense.Config::class.java,
cobblemonResource("ideal_nest_location_sensor") to IdealNestPlacementSense.Config::class.java,
cobblemonResource("pokemon_disturbance") to PokemonDisturbanceSense.Config::class.java,
cobblemonResource("pokemon_adult") to PokemonAdultSense.Config::class.java,
cobblemonResource("nearby_sweet_berry_bush") to SweetBerryBushSense.Config::class.java
)
}
/** The variables that this sense config uses. These are used to declare variables on the entity cleanly. */
fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense?
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.AttackEggThiefTask
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class AttackEggThiefTaskConfig : ObjectiveConfig {
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
behaviourConfigurationContext.addMemories(
CobblemonMemories.EGG_THIEF,
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.ATTACK_TARGET
)
return AttackEggThiefTask()
}
}
@@ -0,0 +1,45 @@
/*
* 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.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.BreedAtNestTask
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
class BreedAtNestTaskConfig : ObjectiveConfig {
companion object {
const val BREED = "breed_at_nest"
}
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(condition).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
behaviourConfigurationContext.addMemories(
CobblemonMemories.NEST_LOCATION,
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.POTENTIAL_MATE,
CobblemonMemories.EGG_IN_NEST
)
return BreedAtNestTask()
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.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.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.BuildNestTask
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class BuildNestTaskConfig : ObjectiveConfig {
companion object {
const val BREED = "breed"
}
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(condition).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
behaviourConfigurationContext.addMemories(
CobblemonMemories.NEST_LOCATION,
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.POTENTIAL_MATE,
CobblemonMemories.READY_TO_BUILD_NEST,
CobblemonMemories.IDEAL_NEST_LOCATION,
MemoryModuleType.ATTACK_TARGET
)
return BuildNestTask()
}
}
@@ -49,7 +49,7 @@ class EatGrassTaskConfig : SingleTaskConfig {
return null
}
behaviourConfigurationContext.addMemories(MemoryModuleType.WALK_TARGET, CobblemonMemories.RECENTLY_ATE_GRASS)
// behaviourConfigurationContext.addMemories(MemoryModuleType.WALK_TARGET, CobblemonMemories.RECENTLY_ATE_GRASS)
return WrapperLivingEntityTask(
EatGrassTask(
@@ -8,7 +8,6 @@
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.ExpressionOrEntityVariable
@@ -0,0 +1,50 @@
/*
* 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.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.GuardNestTask
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class GuardNestTaskConfig : ObjectiveConfig {
companion object {
const val BREED = "breed"
}
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(condition).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_LOCATION,
CobblemonMemories.EGG_THIEF,
CobblemonMemories.IRRITATED_AT,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.ATTACK_TARGET,
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES
)
return GuardNestTask()
}
}
@@ -0,0 +1,149 @@
/*
* 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.ai.config.task
/*
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonFlows.runtime
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, behaviourConfigurationContext: BehaviourConfigurationContext) = 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
}
}*/
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
@@ -0,0 +1,50 @@
/*
* 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.ai.config.task
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PathToIdealNestLocationTask
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
class PathToIdealNestLocationTaskConfig : ObjectiveConfig {
companion object {
const val BREED = "breed"
}
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
private val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.6".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(condition, speedMultiplier).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
behaviourConfigurationContext.addMemories(
CobblemonMemories.IDEAL_NEST_LOCATION,
CobblemonMemories.READY_TO_BUILD_NEST,
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.NEST_LOCATION,
CobblemonMemories.POTENTIAL_MATE
)
val speedExpression: Expression = speedMultiplier.asSimplifiedExpression(entity)
return PathToIdealNestLocationTask(speedExpression)
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PathToNestTask
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
class PathToNestTaskConfig : ObjectiveConfig {
companion object {
const val BREED = "breed"
}
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
private val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.6".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(condition, speedMultiplier).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
behaviourConfigurationContext.addMemories(
CobblemonMemories.POTENTIAL_MATE,
CobblemonMemories.NEST_LOCATION,
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_LOCATION,
CobblemonMemories.HOLDING_EGG
)
val speedExpression: Expression = speedMultiplier.asSimplifiedExpression(entity)
return PathToNestTask(speedExpression)
}
}
@@ -0,0 +1,33 @@
/*
* 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.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PickUpEggTask
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import net.minecraft.world.entity.LivingEntity
class PickUpEggTaskConfig : ObjectiveConfig {
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.READY_TO_GRAB_EGG,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_LOCATION,
CobblemonMemories.HOLDING_EGG,
CobblemonMemories.EGG_SEARCH_COUNTDOWN
)
return PickUpEggTask()
}
}
@@ -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.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PlaceEggInNestTask
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import net.minecraft.world.entity.LivingEntity
class PlaceEggInNestTaskConfig : ObjectiveConfig {
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.HOLDING_EGG,
CobblemonMemories.EGG_IN_NEST
)
return PlaceEggInNestTask()
}
}
@@ -43,8 +43,6 @@ class PokemonWanderControlTaskConfig : SingleTaskConfig {
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
?: CobblemonWanderControl()
wanderControl.reset()
wanderControl.allowLand = (entity.behaviour.moving.walk.canWalk && !entity.behaviour.moving.walk.avoidsLand) || entity.behaviour.moving.fly.canFly
wanderControl.allowWater = entity.behaviour.moving.swim.canSwimInWater && !entity.behaviour.moving.swim.avoidsWater
wanderControl.allowAir = entity.behaviour.moving.fly.canFly && !entity.behaviour.moving.walk.canWalk
@@ -0,0 +1,45 @@
/*
* 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.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.RetrieveEggTask
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
class RetrieveEggTaskConfig : ObjectiveConfig {
companion object {
const val BREED = "breed_at_nest"
}
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(condition).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_LOCATION,
CobblemonMemories.READY_TO_GRAB_EGG
)
return RetrieveEggTask()
}
}
@@ -0,0 +1,49 @@
/*
* 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.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.resolveBoolean
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.SearchForEggTask
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class SearchForEggTaskConfig : ObjectiveConfig {
companion object {
const val BREED = "breed_at_nest"
}
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(condition).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_LOCATION,
CobblemonMemories.HOLDING_EGG,
CobblemonMemories.EGG_THIEF,
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
MemoryModuleType.DISTURBANCE_LOCATION
)
return SearchForEggTask()
}
}
@@ -96,6 +96,9 @@ interface TaskConfig {
cobblemonResource("eat_grass") to EatGrassTaskConfig::class.java,
cobblemonResource("find_air") to FindAirTaskConfig::class.java,
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
/*cobblemonResource("hunt_player") to HuntPlayerTaskConfig::class.java,*/
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("path_to_hive") to PathToBeeHiveTaskConfig::class.java,
cobblemonResource("place_honey_in_hive") to PlaceHoneyInHiveTaskConfig::class.java,
cobblemonResource("place_honey_in_sacc_leaves") to PlaceHoneyInSaccLeavesTaskConfig::class.java,
@@ -108,7 +111,7 @@ interface TaskConfig {
cobblemonResource("defend_owner") to DefendOwnerTaskConfig::class.java,
cobblemonResource("move_to_sweet_berry_bush") to MoveToSweetBerryBushTaskConfig::class.java,
cobblemonResource("stop_moving_to_sweet_berry_bush") to StopTryingToReachSweetBerryBushTaskConfig::class.java,
cobblemonResource("harvest_sweet_berry_bush") to HarvestSweetBerryBushTaskConfig::class.java,
// cobblemonResource("harvest_sweet_berry_bush") to HarvestSweetBerryBushTaskConfig::class.java,
cobblemonResource("eat_held_item") to EatHeldItemTaskConfig::class.java,
cobblemonResource("move_to_item") to MoveToItemTaskConfig::class.java,
cobblemonResource("stop_moving_to_item") to StopTryingToReachWantedItemTaskConfig::class.java,
@@ -175,4 +178,4 @@ interface TaskConfig {
fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable>
/** Given the entity in construction, returns a list of tasks. */
fun createTasks(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<BehaviorControl<in LivingEntity>>
}
}
@@ -143,13 +143,13 @@ class WanderTaskConfig : SingleTaskConfig {
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) {
return@Trigger false
}
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
pathCooldown.setWithExpiry(true, 40L)
val minimumHeight = minimumHeight.resolveInt(mainThreadRuntime)
val maximumHeight = maximumHeight.resolveInt(mainThreadRuntime)
var attempts = 0
var pos: BlockPos? = null
while (attempts++ < wanderControl.maxAttempts && pos == null) {
while (attempts++ < (wanderControl.maxAttempts ?: 4) && pos == null) {
// val targetVec = getLandTarget(entity) ?: return@Trigger true
val targetVec = if (maximumHeight != -1) {
HoverRandomPos.getPos(
@@ -189,7 +189,7 @@ class WanderTaskConfig : SingleTaskConfig {
minimumHeight = minimumHeight,
maximumHeight = maximumHeight,
world = world
).takeIf(wanderControl::isSuitable)
)//.takeIf(wanderControl::isSuitable)
}
if (pos == null) {
@@ -201,17 +201,8 @@ class WanderTaskConfig : SingleTaskConfig {
pos = pos,
speedModifier = speedMultiplier.resolveFloat(mainThreadRuntime),
completionRange = 0,
nodeTypeFilter = { nodeType ->
entity.canBreatheUnderwater() || nodeType !in listOf(
PathType.WATER,
PathType.WATER_BORDER
)
},
destinationNodeTypeFilter = { nodeType ->
!avoidsTargetingAir || nodeType !in listOf(
PathType.OPEN
)
}
nodeTypeFilter = { nodeType -> entity.canBreatheUnderwater() || nodeType !in listOf(PathType.WATER, PathType.WATER_BORDER) },
destinationNodeTypeFilter = { nodeType -> !avoidsTargetingAir || nodeType !in listOf(PathType.OPEN) }
)
)
lookTarget.set(BlockPosTracker(pos.toVec3d().add(0.0, entity.eyeHeight.toDouble(), 0.0)))
@@ -75,7 +75,7 @@ class WaterWanderTaskConfig : SingleTaskConfig {
}
val wanderControl = it.tryGet(wanderControlData).orElse(null) ?: CobblemonWanderControl()
if (!wanderControl.allowWater) {
if (wanderControl.allowWater != true) {
return@Trigger false
}
@@ -85,17 +85,17 @@ class WaterWanderTaskConfig : SingleTaskConfig {
return@Trigger false
}
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
pathCooldown.setWithExpiry(true, 40L)
var pos: BlockPos? = null
var target: Vec3? = null
var attempts = 0
while (attempts < wanderControl.maxAttempts && pos == null) {
while (attempts < wanderControl.maxAttempts ?: 4 && pos == null) {
attempts++
target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(mainThreadRuntime), verticalRange.resolveInt(mainThreadRuntime))
?: continue
pos = BlockPos.containing(target).takeIf(wanderControl::isSuitable)
// pos = BlockPos.containing(target).takeIf(wanderControl::isSuitable) im migrating shit
}
if (pos == null || target == null) {
@@ -192,6 +192,11 @@ open class PokemonBattle(
*/
fun getActor(npc: NPCEntity) = actors.firstOrNull { it is NPCBattleActor && it.npc == npc }
/**
* Gets the first battle actor who has or is the given [PokemonEntity]
*/
fun getActor(pokemonEntity: PokemonEntity) = actors.firstOrNull { it.uuid == pokemonEntity.uuid || it.activePokemon.any { it.battlePokemon?.entity == pokemonEntity } }
/**
* Gets a [BattleActor] and an [ActiveBattlePokemon] from a pnx key, e.g. p2a
*

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