Compare commits

..
Author SHA1 Message Date
Paul 64e0c91e73 Added category to Behaviours backend 2026-04-25 11:21:47 +10:00
Paul fd443785ad Added majority of functionality for premade NPCs 2026-04-20 09:43:00 +10:00
Paul 810ce8b759 Outline work for premade NPCs as a logical replacement for NPC presets 2026-04-18 11:35:40 +10:00
Paul aad115e43a Merge remote-tracking branch 'origin/main' into npc-upgrades
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/ai/OmniPathNavigation.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2026-04-13 13:35:52 +10:00
JazzMcNade 664c0c012c Converted PointToSpawn to BiggerBrains 2026-03-24 01:05:07 -05:00
JazzMcNade ed8c7569f9 Update FocusDuringBattle.kt 2026-03-21 18:55:36 -05:00
JazzMcNade e63a7c474f Migrate kick starting flight for flyers over water 2026-03-21 16:38:40 -05:00
Paul f5580fd83b Merge remote-tracking branch 'origin/bigger-brains' into bigger-brains 2026-03-14 11:40:10 +10:00
Paul a31b533b77 Rhyperior's grooving with better transitioning 2026-03-14 11:39:55 +10:00
JazzMcNade 817ea0bff7 Added "canDance" species configuration option 2026-03-10 23:30:12 -05:00
Paul 35977c0724 Rhyperior's grooving 2026-03-10 21:26:02 +10:00
JazzMcNade 7e08a46f28 Dancing Objective WIP 2026-03-10 01:41:32 -05:00
JazzMcNade cc5d63fd11 Port JumpOutOfWaterTask to BiggerBrains 2026-03-08 01:56:47 -06:00
JazzMcNade 065c5388a0 Fix Combee block jail break, more cleanup 2026-03-02 02:03:04 -06:00
JazzMcNade c20203a8e9 more clean up 2026-03-01 22:57:28 -06:00
JazzMcNade 932ebfa563 Clean up 2026-03-01 22:11:31 -06:00
JazzMcNade 0f57bd35de Comments 2026-03-01 22:05:45 -06:00
JazzMcNade d6974452fb Implemented Bee behavior with biggerbrain tech, allow fliers to wander 2026-03-01 22:02:59 -06:00
JazzMcNade 937393b93d Remove old tasks 2026-02-22 20:37:45 -06:00
JazzMcNade 59fceeefc3 Smarter usage of looktracker 2026-02-22 16:06:26 -06:00
JazzMcNade ec7e106efb Item pickup for items too close to path to 2026-02-22 13:33:14 -06:00
JazzMcNade d95c5687b1 Added eatHeldItem Objective, corrections to PickupItem 2026-02-18 01:35:05 -06:00
JazzMcNade 39ef558b9b Pickup item logic 2026-02-15 23:11:03 -06: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
JazzMcNade 21411d26ac More clean up, added moveToRestingPlace Objective 2026-02-03 00:49:14 -06:00
JazzMcNade 3f6c5ebc96 Sleep blocks movement, Added comfort distractions, Removed null check from StayAfloat 2026-01-29 23:25:12 -06: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
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
8755 changed files with 106876 additions and 621615 deletions
-3
View File
@@ -24,7 +24,6 @@ eclipse
run
runClient
runServer
utilityscripts/implementation/*.txt
# Files from Forge MDK
forge*changelog.txt
@@ -37,11 +36,9 @@ common/src/main/resources/assets/cobblemon/showdown
# Generated files
*/generated/resources/*
/fabric/src/generated/resources/cobblemon-common.accesswidener
/fabric/src/main/generated/*
/.ignored/
common/.fabric
.kotlin/
# MacOS
.DS_Store
/common/config/*
+2 -6
View File
@@ -16,7 +16,6 @@ check_assets_cache:
stage: preprocessing
script:
- echo "Checking if cached hash matches current hash"
- yum install -y findutils
# Calculate the current hash
- find common/src/main/resources/assets common/src/main/resources/data -type f -exec sha256sum {} + | sort -k 2 | sha256sum > current_hash.txt
@@ -72,8 +71,8 @@ setup_packsquash:
- echo "Installing wget & unzip"
- yum install -y wget unzip
- echo "Downloading PackSquash"
- wget https://github.com/ComunidadAylas/PackSquash/releases/download/v0.4.1/packsquash-x86_64-unknown-linux-musl.zip
- unzip packsquash-x86_64-unknown-linux-musl.zip -d ./packsquash_bin
- wget https://github.com/ComunidadAylas/PackSquash/releases/download/v0.4.0/PackSquash.CLI.executable.x86_64-unknown-linux-musl.zip
- unzip PackSquash.CLI.executable.x86_64-unknown-linux-musl.zip -d ./packsquash_bin
- echo "Creating PackSquash options.toml"
- |
cat <<EOF > ./options.toml
@@ -149,7 +148,6 @@ build:
dependencies:
- replace_resources
script:
- yum install -y findutils
- export GRADLE_OPTS="-Xmx4G -Xms4G"
- chmod +x ./gradlew
- ./gradlew build
@@ -165,7 +163,6 @@ publish:
rules:
- if: '$CI_PROJECT_PATH == "cable-mc/cobblemon" && $CI_COMMIT_BRANCH == "main"'
script:
- yum install -y findutils
- export GRADLE_OPTS="-Xmx4G -Xms4G"
- chmod +x ./gradlew
- ./gradlew publish
@@ -179,7 +176,6 @@ deploy_artifacts:
dependencies:
- build
script:
- yum install -y findutils
- echo "Collecting artifacts..."
- find neoforge/build/libs/ -type f -name "*.jar" | grep -vE "dev-shadow|sources|javadoc" | xargs -I {} mv {} ./
- find fabric/build/libs/ -type f -name "*.jar" | grep -vE "dev-shadow|sources|javadoc" | xargs -I {} mv {} ./
+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
+21 -481
View File
@@ -1,518 +1,58 @@
# Changelog
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
## [1.7.4 (MONTH Xth, 2026)](#1-7-4)
### Additions
- Added config options to award experience to fainted Pokémon for opponents they helped defeat (`awardExperienceToFaintedPokemon`) and to still award experience for defeated enemies even after battle loss/forfeit (`awardExperienceOnBattleLoss`)
- Party Overlay Exp Gained Animation will now display the amount of EXP the Pokémon gained
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys??!
- Added a new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
- Added new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
- Added missing crops to Botany Pots integration
- Added a way to obtain Porygon by inserting the Upgrade or Dubious Disc into a data monitor
- Added a new `cobblemon:structure`, which is a feature without any restrictions allowing the Builders to place certain structures in much more specific manner.
- Added Configs for Infinite Use TMs and Unlocking all TMs
- Added Type Gem blocks
- Added Type Gem Clusters
- Added TMs
- Added TM Machine
- Added Deepslate Core Block
- Added /givetm Command
- Added advancement triggers for learning TMs: `cobblemon:has_learn_specific_tm` (per-TM), `cobblemon:has_learn_all_tm` (all TMs learned), and `cobblemon:tms_learned` (count-based), backed by a new `totalTMLearnedCount` on player advancement data.
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` tracking for capturing Alpha Pokémon.
- Added Move Dex to Pokédex
- Added the Habitat Block, a spawner block for servers and adventure maps that controls spawning in an area.
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks
- Added intrinsic scaling to all Pokémon (+-5%). Some rendering cases, such as shoulder mounting, revert to 1.0 scaling to preserve animation accuracy.
- Added scaling to "baby" Pokémon (smaller (-20%) at level 1 → normally sized at level 10)
- Added conditional seats for riding. Seats can be gated by molang such as "is_alpha".
- Added Alpha Pokémon
- Added alpha scaling (alphas are larger than their normal counterparts)
- Alphas will always lead herds and will spawn in herds
- Alphas spawn with two random TM moves from their learnset
- Alpha Pokémon will always defend themselves from players that attack them
- Added alpha level matching and stat boost (based on level)
- Added pitch-down and reverb to alpha cries
- Added alpha Pokémon bait effect
- Added alpha eye rendering including eye overlay, bloom, eye trail, and particles.
- Added cobweb block slowness immunity for spider Pokémon
- Added 49 new habitat structures. 34 as typical Minecraft structures and 15 as structures that get placed as biome features to allow for more natural placement in generation. Structures placed as biome features will not be able to be located with the `/locate structure` command.
- Vanilla village house template pools have been adjusted to account for the new abandoned village structure habitat. Weights have changed within the JSON file for every piece to ensure too many habitats don't generate in a single village.
- Added 10 new ruin structures.
- rooted_arch_ruins & crumbling_arch_ruins have been transferred over to biome features, allowing for better placement in generation.
- All ruin structures that did not generate with a gimmighoul/gilded chest before will now generate with one.
- All ruin structures gilded chest loot tables have been updated to include a guaranteed TM not learned naturally by any Pokémon through their level-up moveset. Each ruin gets its own specific TM, so you can now go hunting ruins to complete your move dex!
- All fossil structures have been transferred over to biome features, allowing for better placement in generation.
- The prehistoric_underwater_fissure & prehistoric_submerged_impact fossil structures now use the biome tag `#cobblemon:is_temperate_ocean`.
- Gimmighoul towers now generate at ground level rather than sinking one block into the ground.
- A new shipwreck cove structure called the "magma_shipwreck_cove" has been added.
- Shipwreck coves now generate slightly more frequently.
- Added equipment loot tables for shipwreck cove enemies.
- Maps from abandoned fishing boats leading to shipwreck coves now will skip explored chunks, allowing the player to find more shipwreck coves easier.
- The lush shipwreck cove's phantom mob spawner has been replaced with breezes.
- The lush shipwreck cove structure itself has been slightly cleaned up to be less noisy with the blocks it is built out of.
- All shipwreck coves' spawners have been adjusted to give compatible mobs enchanted iron or diamond armour and weapons while in its ominous state. The armour given to the mobs will also use the automaton trim. We upped the difficulty because it was too easy now that your Pokémon can fight for you.
- One TM of the elemental hyper beams has been added as guaranteed loot to the main treasure chest in every shipwreck cove. (Magma cove gets blast burn, lush cove gets frenzy plant, and submerged cove gets hydro cannon)
- Added 7 new advancements
- Pressing the inventory key now closes the Pokémon interact wheel GUI
- Pokémon cries now play when clicking Pokémon in the summary screen.
- Pressing the Summary key while either the starter selection or summary screen are open now closes those screens
- Ice type Pokémon are now immune to Packed Snow
- Held item visibility can now be controlled by the held_item_visible command argument.
### New rideable Pokémon
- Crobat
- Skarmory
- Milotic
- Archeops
- Goodra
- Goodra (Hisuian Form)
- Drampa
- Duraludon
- Archaludon
### Pokémon Added
#### Generation 3
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Skitty
- Delcatty
- Seviper
- Zangoose
#### Generation 4
- Cherubi
- Cherrim
- Stunky
- Skuntank
#### Generation 5
- Audino
- Pawniard
- Bisharp
#### Generation 6
- Pancham (+ Cosmetic with Black Glasses)
- Pangoro (+ Cosmetic with Black Glasses)
#### Generation 7
- Rockruff
- Lycanroc
#### Generation 8
- Rolycoly
- Carkol
- Coalossal
- Runerigus
- Snom
- Frosmoth
- Indeedee
- Duraludon
- Applin
- Flapple
- Appletun
#### Generation 9
- Nymble
- Lokix
- Kingambit
- Dipplin
- Archaludon
- Hydrapple
#### Regional Forms
- Sandshrew (Alolan Form)
- Sandslash (Alolan Form)
- Yamask (Galarian Form)
- Avalugg (Hisuian Form)
### Model updates for the following Pokémon
- Sandshrew
- Sandslash
- Nidoking
- Zubat
- Golbat
- Crobat
- Ditto
- Porygon
- Porygon2
- Porygon-Z
- Skarmory
- Smoochum
- Jynx
- Popplio
- Brionne
- Primarina
- Fletchling
- Fletchinder
- Talonflame
- Sableye
- Mawile
- Goomy
- Sliggoo
- Goodra
- Nacli
- Naclstack
- Garganacl
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Tyranitar
- Turtonator
- Golett
- Corsola
- Arbok (New Patterns: Angry, Cartoon, Clown, Comedic, Crow, Grin, Ink, Modern, Rain, Scribble, Silence, Sloth, Suspicious, Will)
- ... and Substitute
#### Model changes to the regional bias resource pack:
- Added Mime Jr. (Galar Bias)
- Added Bergmite (Hisui Bias)
- Changed models for every existing regional bias
### Animation updates for the following Pokémon
- Kakuna
- Grimer
- Nidoran♀ (+ New shoulder mount)
- Nidorina
- Nidoran♂ (+ New shoulder mount)
- Sandshrew
- Diglett
- Dugtrio
- Graveler
- Happiny
- Chansey
- Blissey
- Poliwhirl
- Poliwrath
- Zubat
- Vulpix
- Drowzee
- Hypno
- Noctowl
- Skarmory
- Skitty
- Flygon
- Shellos
- Riolu
- Lucario
- Purrloin
- Liepard
- Joltik
- Galvantula
- Nidoking
- Fletchling
- Fletchinder
- Talonflame
- Archeops
- Drampa
- Sewaddle
- Swadloon
- Leavanny
- Goomy
- Sliggoo
- Goodra
- Shedinja
- Skwovet
- Greedent
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Luvdisc
- Staryu
- Starmie
- Togetic
- Rellor
- Rabsca
- Yungoos
- Gumshoos
- Cottonee
- Whimsicott
- Corsola
#### Added sounds to the following moves
- Absorb
- Aurora Beam
- Double Team
- Draco Meteor
- Fire Punch
- Fire Spin
- Flame Charge
- Flame Wheel
- Giga Drain
- Ice Punch
- Leech Life
- Magical Leaf
- Mega Drain
- Minimize
- Poison Gas
- Rock Throw
- Scary Face
- Seed bomb
- Smokescreen
- String Shot
- Tail Whip
### Changes
- Stat increase and decrease particles have been changed, with the addition of alpha boost particles.
- Dragonite's mailbag cosmetic can now also be obtained with a chest in the cosmetic slot
- Adjusted Croagunk's walkspeed
- Adjusted Aerodactyl's walkspeed
- Sharp Beak can now be crafted
- Metal Alloy can now be crafted
- Ability Capsules can now be brewed
- Using an Ability Capsule will now return an Empty Bottle
- The following items can now be obtained by brushing the following Pokémon:
- Pink Wool - Wigglytuff
- White Wool - Swablu, Altaria
- Shed Shell - Ekans, Arbok, Dratini, Dragonair, Dunsparce, Treecko, Grovyle, Sceptile, Seviper, Scraggy, Scrafty, Helioptile, Heliolisk, Salandit, Salazzle, Sobble, Drizzile, Inteleon, Silicoblra, Sandaconda, Toxel, Toxtricity, Orthworm, Dudunsparce
- Phantom Membrane - Dreepy, Drakloak, Dragapult
- The following items can now be obtained by evolving the following Pokémon into any of their evolutions:
- Turtle Scute - Squirtle, Wartortle, Turtwig, Grotle, Tirtouga, Chewtle
- Shed Shell - Metapod, Kakuna, Ekans, Paras, Venonat, Krabby, Scyther, Kabuto, Dratini, Dragonair, Ledyba, Spinarak, Yanma, Pineco, Dunsparce, GLigar, Pupitar, Treecko, Grovyle, Silcoon, Cascoon, Surskit, Nincada, Trapinch, Vibrava, Corphish, Anorith, Bagon, Shelgon, Kricketot, Burmy, Combee, Skorupi, Swadloon, Whirlipede, Dwebble, Scraggy, Karrablast, Joltik, Larvesta, Spewpa, Clauncher, Helioptile, Charjabug, Crabrawler, Cutiefly, Dewpider, Salandit, Wimpod, Poipole, Sobble, Drizzile, Dottler, Silicobra, Toxel, Sizzlipede, Snom, Tarountula, Nymble, Rellor
- Saccharine Leaves now drop Tart, Sweet, or Syrupy Apples, depending on their honey coating stage, instead of vanilla Apples
- Fixed the height of the Tumblestone Bud items when held in hand, placed on head, viewed in a GUI, and placed in an Item Frame
- Refactored all advancements, removing several deemed unnecessary. Due to this refactor, all previously obtained advancements will be reset besides the root (for picking a starter Pokémon). We are sorry for the inconvience, but it makes the tab cleaner and simplifies future advancements.
- Restructured Botany Pots integration folder structure
- Decrease the default pastured mon wander radius to 32.
- Factor in the weight of Pokémon when calculating pushing forces between hitboxes.
- Decrease default pastured mon wander radius to 32.
- Factor in the weight of pokemon when calculating pushing forces between hitboxes.
- Remade the Starter Selection Screen with new assets
- Refactored berry trees to be less impactful on TPS (more performant)
- Changed the bait effects of the following items: (Vanilla) Sweet & Glow Berries, Golden & Enchanted Golden Apples (Cobblemon) Custap, Eggant, Hopo & Micle Berries, Sweet Hearts
- Wild Pokémon now reliably have at least one offensive move if they can have offensive moves at that level.
- Data Monitor can accept Disks (TMs, Music Disks, Upgrade, Dubious Disk, etc.) to change what's being displayed on the screen
- Rider positioning is now client-driven (Remove poseOffsets and offset per seat/pose) in favour of sending real-time passenger position.
- Cleaned up seat data. Removed all now unused fields from seats in all species files. Also made locator seat names explicit in the species files (however, this doesn't break the legacy format)
- Marks can now provide aspects.
- Added new shiny and Alpha chance configs
- Apricorns now make a new sound when harvested
- Lowered the volume of harvesting berries
- Added alternate versions of the basic PC Wallpapers based on the original design of Cobblemon's PC, before public release
- Improved compatibility with various mods
- Farmer's Delight's cutting board can now be used to strip Apricorn and Saccharine's logs, or salvage their planks from various blocks. It can also separate their boats from chests
- Farmer's Delight's cutting board can be used to get two white dye from Pep-Up Flowers and two magenta dye from Medicinal Leeks
- Farmer's Delight's cutting board can separate Tumblestone Blocks back into Tumblestones
- Farmer's Delight's cutting board can salvage Type Gems from their Gem Blocks
- Farmer's Delight's Onions, Tomatoes, and Cabbages can be used to cook Open-Faced Sandwiches. Nature's Spirit's Olives also can be used
- Farmer's Delight's cutting board can be used on Hearty Grains to obtain rice and straw, similarly to Farmer's Delight's Rice Panicles
- Farmer's Delight's Apple Pies and Apple Ciders can now be made using Applin Apples, alongside the Vanilla counterpart
- Amendments allows for cooking Leek and Potato Stew in a Cauldron
- Any mods that utilise Moonlight's Soft Fluid system can store Leek and Potato Stew, Medicinal Brew, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Ability Capsules, Moomoo Milk, and Berry Juice in liquid form (such as Suplementaries' Jar or Goblet, or Cauldrons while using Amendments)
- Supplementaries' Hourglass can now use Bright Powder, Quick Powder, Metal Powder, Heal Powder, Silver Powder, and Remedy
- Berry Trees are now compatible with the Planter properly
- Saccharine Way Signs (added by Supplementaries automagically) now use a premade texture to prevent odd visual issues
- Supplementaries' Jar can now store various Cobblemon cookies, candies, and similar items
- Some of Supplementaries' items have been added to the `#cobblemon:held/container_held_items` item tag, including sacks, safes, and lunch baskets
- Experience Nuggets can be used in place of Sculk for making the EXP Candies if using Create
- Rage Candy Bar has a Create Mixing recipe
- All Cobblemon ores can now be crushed in Create's Crushing Wheels
- All Gem and Tumblestone Clusters can be crushed for automatic, low return, gems
- Hearty Grains can be milled like Farmer's Delight Rice Panicles if both Farmer's Delight and Create are installed
- Big Roots and Revival Herbs can be milled to their respective powders
- Pep-Up Flowers, Medicinal Leeks, and all 70 berries can be milled in Create's Millstone for additional dye drops
- Auspicious Armour and Upgrades can be Haunted using Create's crafting systems
- Applin Apples can be used to make Honeyed Apples in Create
- Golden Apples can now be crafted using the Applin Apples
- Prismarine Crystals and Amethyst Shards can now be used to craft Pokedexes
- Updated Pokédex Screens item tag to support tagged variants of Redstone and Glowstone Dust
- Sounds for interacting with various Cobblemon blocks now play from the centre of the block, rather than the corner
- The `/pc` command no longer plays PC activation sounds
### Fixes
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly
- Fixed Litleo not blinking
- Fixed Omanyte and Omastar incorrectly using water animations on land.
- Fixed Pokédex layout for genderless variations
- Fixed odd stacking behaviour with the poke_bait item.
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
- Fix occasional crash on retrieving revived fossils
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
- Fixed improper validation of Pokémon when performing a trade.
- Fixed an issue where particle like beam can disappear when you don't see the beginning of the beam, or you look away
- Fixed Bedrock particle materials (alpha/opaque/blend/add) sharing one render batch, which caused incorrect culling on mixed batches; each material now batches and draws with its own blend and cull state, and the alpha material renders as a true cutout
- Fixed an occasional crash on retrieving revived fossils
- Fixed brushing not working on Neoforge
- Fixed braking not working for submarines (whoops).
- Fixed strafing not having the same friction as forward movement (funny whoops).
- Fixed submarine's surface-water animations not playing when supposed to.
- Fixed submarine camera freaking out when looking up or down on the surface.
- Fixed pastured mons despawning and running away.
- Fixed Apricorn trees crashing when other mods bonemeal it
- Fixed the issue that caused Hover Behaviour rides without a ground style to be rendered useless upon touching the ground on a server.
- Removed the "inAir" penalty for block destruction speed when on a hover mount.
- Fixed horizontal and vertical collisions with hover mounts so they no longer accumulate or retain momentum
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within its menu
- Fixed NPCs switching Pokémon very often during battles
- Fixed issue where particle like beam can disappear when you don't see the beginning of the beam or you look away
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
- Fix occasional crash on retrieving revived fossils
- Fix brushing not working on Neoforge
- [Various Submarine Ride Fixes](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2157)
- Fix Pastured mons despawning and running away.
- Fix Apricorn trees crashing when other mods bonemeal it
- [Various UFO Ride Improvements](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2156)
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within it's menu
- Fixed NPCs switching pokemon very often during battles
- Fixed Campfire pot causing crashes under certain circumstances
- Fixed Type Gems not being consumed upon use
- Fixed Ancient Pokéball's to their respective modifiers
- Fixed a Server warning when recalling Pokémon
- Fixed sliding particles incorrectly handling block collision
- Fixed Full Heal not curing status conditions in battle
- Fixed a rare issue with battles locking up when you are forced to switch, with the switch menu appearing instantly.
- Fixed the combined effects of Pledge moves having broken Showdown message handling and localisation
- Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain.
- Resolved the niche issue where Structures would spawn in an incorrect location, creating very funky world gen.
- Fixed Galarian Ponyta missing its cry.
- Fixed various log warnings caused by redundant files and sound events.
- Traded Pokémon will now retain the original friendship level when traded back to its OT
- Clusters now display correctly on maps
- Fixed removing mulch from berry trees with a shovel not working.
- Fixed collecting honey from Saccharine Leaf blocks deleting Honey Bottles when the inventory was full
- Fixed Stripped Saccharine Wood recipe being missing
- Fixed CancelableSoundInstance causing lag by attempting to create sounds every tick when out of range of hearing
- Fixed Stat Reducing Aprijuice not being possible to apply on a max out Pokémon, and the possibility of going into negatives
- Fixed Exploit where you could take out the bait or drop your Pokérod with bait to avoid consuming bait, now the bobber will just disappear if you do so
- Fixed Apricorn and Saccharine boats incorrectly using chest boat models
- Fixed wild and trainer battle AI refusing to select moves with the `adjacentAlly` target type, even when it's their only option
- Fixed gilded chests being more accessible than intended
- Fixed fished up Pokémon not being added as "seen" to the Pokedex
- Fixed surprised Gimmighoul encounters not being added as "seen" to the Pokedex
- Fixed a crash involving berry trees that have been world edited.
- Fixed visual desync in multiplayer for players mounting Pokémon outside other players' loaded chunks
- Fixed Pokémon entities leveled by rare candy not getting stronger as Minecraft entities unless recalled.
- Fixed a freeze during game load that occurred when multiple mods tried to load Cobblemon API classes at the same time.
- Fixed random tick crashes from berry trees
- Lowered the volume of Blitzle and Zebstrika's cry
- Regional Foods will now prioritise being eaten instead of used for healing Pokémon, shift clicking will use it as healing
- Resolved Z-Fighting on Pokédex Models
- Restored Ninjask's ability to do barrel rolls
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
- Fixed a crash caused by adding enchanted items to the Resurrection Machine
- Fixed a crash that could occur when starting a PvN battle with only fainted Pokémon.
- Fixed the Remember Riding Camera config not being respected when dismounting and resolved the incompatibility with the Auto Third Person mod
- Fixed Full Heal block not dropping Full Heals
- Fixed status cure items not returning an empty bottle when used
- Fixed Panpour consuming its held item when evolving with a Water Stone
- Fixed Smoked Tail Curry returning a Wooden Bowl when consumed despite it not being included in the recipe
- Fixed `/giveallpokemon` not respecting the min and max parameters
- Fixed the choose starter toast not disappearing when capturing a Pokémon instead of opening the starter selection screen
- Fixed an issue where certain Sinister Tea seasonings were only applying as instant effects and would not apply over time
- Long config option labels now scroll instead of overflowing
- Fixed Ancient Pokeball's to their respective modifiers
- Fixed a Server warning when recalling Pokemon
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters
- Added `BattleFleeAttemptEvent` fired from `PokemonBattle.checkFleeAttempt`, allowing developers to intercept and control flee attempts
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
- Added a new `cobblemon:height_range` processor type to allow developers to restrict structure-spawning ranges.
- Separated all the functions of MoLangFunctions.kt into their own files.
- Spawn buckets and their base weights are now provided by the spawner, with different base weights for world, fishing, snack, and habitat spawn buckets.
- Removed "SpawnBucket" class in favour of just storing maps of bucket names to weights.
- Fixed nicknames not being applied to Pokémon spawned from `spawn_pool_world` files.
- Added support for `profilePokedexScale`, `profilePokedexTranslation`, `profileSummaryScale`, and `profileSummaryTranslation` in poser files for finer control over profile displays.
- Fixed riding camera offsets defined on Pokémon forms not being applied
### Molang & Datapacks
- Added `moveset_builders` datapack folder for different styles of movesets.
- Added `party_pools` datapack folder for defining groups of Pokémon for complex NPC party definitions.
- Added `party_compositions` datapack folder for arranging parties on NPCs that rely on party pools.
- Added `composed_pool` NPC party provider type which combines a party pool with a party composition to create more complex NPC parties.
- Added basic chatter NPC Behaviour
- Added `q.item.is_enchanted()`
- Added `q.item.has_enchantment(minecraft:sharpness, 3)`
- q.item.is_enchanted()
- q.item.has_enchantment(minecraft:sharpness, 3)
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
- Added `has_tm_move_unlocked`, `unlock_tm_move`, & `lock_tm_move` as available Molang functions for Players
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for Pokémon
- Added Data Component support for species drops, eg `"components": {"minecraft:custom_model_data": 123}`
- Added the new `sortOrder` field to mark entries, (defaults to 0 if not specified) which controls the order in which marks are rendered on the HUD, with higher numbers being rendered on top of lower numbers
- Added optional `order` field to starter categories. It will allow explicitly sorting categories in the starter selection screen
- SpeciesAdditions append vs override now matches wiki documentation
- Added `weighted_choice` as a new feature type. It requires setting weights for each choice and allows developers to simplify the process of assigning random aspect choice based on marble chance
- Added `q.world.spawn_loot_table_items(loot_table_id, x, y, z)` which spawns the items from the loot table at the position. (Use type: "minecraft:chest" for the loot table.)
- Added `q.pokemon.entity` to access the entity of Pokémon.
- Flamethrower and Seismic Toss had their action effect JSON cleaned up
- Ride sounds can now be set as exclusive to passengers
- Ride sounds no longer play when submerged unless specified with a new setting
- NPC Trainers can now call a `command` animation when issuing a command in battle.
- Added a q.failed function to move action effects.
- Herd spawned Pokémon have their levels controlled differently. The `levelRange` property now acts as a clamp instead of a filter, with a new `herdLevelRange` property to do the filter behaviour.
- `q.sound` now accepts three parameters (sound event, volume and pitch) instead of just the sound event and pitch.
### Particles
#### New Move Animations
- Blizzard
- Disarming Voice
- Draco Meteor
- Dragon Breath
- Dragon Rage
- Drain Punch
- Fairy Wind
- Fire Fang
- Gust
- Horn Drill
- Hydro Pump
- Ice Fang
- Leafage
- Leaf Storm
- Light Screen
- Night Shade
- Play Rough
- Pyro Ball
- Reflect
- Rock Slide
- Sonic Boom
- Sucker Punch
- Supersonic
- Thunder Fang
#### Tweaks and Fixes (Moves)
- Confusion
- Dragon Claw
- Eruption
- Fire Punch
- Flame Burst
- Flame Charge
- Flamethrower
- Frost Breath
- Hypnosis
- Ice Punch
- Icy Wind
- Mud Sport
- Poison Fang
- Protect
- Psychic
- Quick Attack
- Rage Powder
- Razor Leaf
- Sand Attack
- Scratch
- Shadow Claw
- Sing
- Spore
- String Shot
- Tackle
- Tail Whip
- Thunder
- Thunder Punch
- Thunder Wave
- Water Gun
- Water Pulse
- Water Sport
#### Miscellaneous Particles
+ Added Wild Might particles for Alpha Pokémon
+ Updated stat increase and decrease particles
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for pokemon
### Localization
- Updated translations for:
- Japanese
- German
- Spanish
- Ecuadorian Spanish
- Portuguese
- Brazilian Portuguese
- Simplified Chinese
- Traditional Chinese
- Korean
-9
View File
@@ -1,9 +0,0 @@
- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator
--- global posers? stacked posers?
--- red texture seems to be what some people wanted.
/- shoulder mounts part of it
- omni boost
- roar when player comes within reach, adjust levels
- Riding settings to control what the min scale is for a pokemon to be ridable.
-4
View File
@@ -72,10 +72,6 @@ tasks.withType<Test> {
}
}
tasks.processResources {
exclude("data/**/README.md")
}
sourceSets {
main {
blossom {
@@ -13,6 +13,5 @@ public interface ChannelDuck {
void cobblemon$inverseAttenuation(float rolloffFactor);
void cobblemon$clearFilters();
void cobblemon$applyReverb(float decayTime, float gain, float diffusion, float density);
void cobblemon$clearReverb();
// void cobblemon$exponentialAttenuation(float rolloff, float refDistance, float maxDistance);
}
@@ -67,14 +67,14 @@ public abstract class BeeOccupantMixin {
if (state.is(BlockTags.BEEHIVES)) {
// position in front of the entrance, face away from the block
var facing = state.getValue(HorizontalDirectionalBlock.FACING);
newPos = pos.relative(facing).getCenter();
newPos = pos.relative(facing).getBottomCenter();
newYaw = facing.toYRot();
if (brain.checkMemory(CobblemonMemories.HIVE_LOCATION, MemoryStatus.REGISTERED)) {
brain.setMemory(CobblemonMemories.HIVE_LOCATION, pos); // This needs to be set in the case that the hive was picked up and moved.
}
} else {
// Block has likely been destroyed
var center = pos.getCenter();
var center = pos.getBottomCenter();
newPos = new Vec3(
center.x + level.random.nextFloat() * 0.6 - 0.3,
center.y + level.random.nextFloat() * 0.6 - 0.3,
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.ChannelDuck;
import com.mojang.blaze3d.audio.Channel;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.EXTEfx;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Channel.class)
public abstract class ChannelMixin implements ChannelDuck {
@Shadow
private int source;
// Id of filter applied when sound is obstructed
private int cobblemon$lowPassFilterId = 0;
@Override
public void cobblemon$applyLowPassFilter(float gain, float hfGain) {
// Delete old filter if it exists
if (cobblemon$lowPassFilterId != 0) {
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0); // Detach filter from source
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId); // Delete old filter
cobblemon$lowPassFilterId = 0;
}
// Generate a fresh filter
int filterId = EXTEfx.alGenFilters();
if (!EXTEfx.alIsFilter(filterId)) {
System.err.println("Failed to create OpenAL low-pass filter");
return;
}
EXTEfx.alFilteri(filterId, EXTEfx.AL_FILTER_TYPE, EXTEfx.AL_FILTER_LOWPASS);
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAIN, gain);
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAINHF, hfGain);
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, filterId);
// Save the new filter ID
cobblemon$lowPassFilterId = filterId;
}
@Override
public void cobblemon$inverseAttenuation(float rolloffFactor) {
AL10.alSourcei(source, AL10.AL_DISTANCE_MODEL, AL10.AL_INVERSE_DISTANCE);
AL10.alSourcef(source, AL10.AL_ROLLOFF_FACTOR, rolloffFactor);
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, 1.0f);
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, 0.0f);
}
@Override
public void cobblemon$clearFilters() {
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0);
if (EXTEfx.alIsFilter(cobblemon$lowPassFilterId)) {
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId);
cobblemon$lowPassFilterId = 0;
}
}
}
@@ -36,15 +36,15 @@ public class ClientPacketListenerMixin {
for (int passenger : packet.getPassengers()) {
passengers.add(this.level.getEntity(passenger));
}
pokemon.getOccupiedSeats().values().stream()
Arrays
.stream(pokemon.getOccupiedSeats())
.filter(x -> !passengers.contains(x))
.filter(Objects::nonNull)
.forEach(Entity::stopRiding);
// Mount passengers not yet in the map
var currentPassengers = new ArrayList<>(pokemon.getOccupiedSeats().values());
var occupiedSeats = Arrays.asList(pokemon.getOccupiedSeats());
passengers.stream()
.filter(x -> !currentPassengers.contains(x))
.filter(x -> !occupiedSeats.contains(x))
.filter(Objects::nonNull)
.forEach(x -> x.startRiding(pokemon, true));
ci.cancel();
@@ -1,133 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.mixin.accessor.SinglePoolElementAccessor;
import com.cobblemon.mod.common.world.structureprocessors.HeightRangeStructureProcessor;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.pools.*;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
@Mixin(JigsawPlacement.class)
public class JigsawPlacementMixin {
@Unique
private static final Logger cobblemon$LOGGER = LoggerFactory.getLogger("Cobblemon/HeightRangeVeto");
@Inject(
method = "addPieces(Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;Lnet/minecraft/core/Holder;Ljava/util/Optional;ILnet/minecraft/core/BlockPos;ZLjava/util/Optional;ILnet/minecraft/world/level/levelgen/structure/pools/alias/PoolAliasLookup;Lnet/minecraft/world/level/levelgen/structure/pools/DimensionPadding;Lnet/minecraft/world/level/levelgen/structure/templatesystem/LiquidSettings;)Ljava/util/Optional;",
at = @At("RETURN"),
cancellable = true
)
private static void onAddPieces(
Structure.GenerationContext context,
Holder<StructureTemplatePool> startPool,
Optional<ResourceLocation> startJigsawName,
int maxDepth,
BlockPos pos,
boolean useExpansionHack,
Optional<Heightmap.Types> projectStartToHeightmap,
int maxDistanceFromCenter,
PoolAliasLookup aliasLookup,
DimensionPadding dimensionPadding,
LiquidSettings liquidSettings,
CallbackInfoReturnable<Optional<Structure.GenerationStub>> cir
) {
Optional<Structure.GenerationStub> result = cir.getReturnValue();
if (result.isEmpty()) {
return;
}
BlockPos spawnPos = result.get().position();
String poolId = startPool.unwrapKey().map(key -> key.location().toString()).orElse("unknown");
boolean canPlace = cobblemon$canStructurePlace(context, startPool, spawnPos);
cobblemon$LOGGER.info(
"[HeightRangeVeto] addPieces evaluated requestedPos={} spawnPos={} startPool={} -> canPlace={}",
pos, spawnPos, poolId, canPlace
);
if (canPlace) {
return;
}
cobblemon$LOGGER.info(
"[HeightRangeVeto] Vetoing structure start at {} (startPool={})",
spawnPos, poolId
);
cir.setReturnValue(Optional.empty());
}
@Unique
private static boolean cobblemon$canStructurePlace(
Structure.GenerationContext generationContext,
Holder<StructureTemplatePool> startPool,
BlockPos startPos
) {
for (StructurePoolElement element : startPool.value().templates) {
Optional<HeightRangeStructureProcessor> heightRangeProcessor = cobblemon$getHeightRangeProcessor(element);
if (heightRangeProcessor.isEmpty()) {
continue;
}
HeightRangeStructureProcessor processor = heightRangeProcessor.get();
boolean valid = processor.isValidPlacement(
generationContext.chunkGenerator(),
generationContext.heightAccessor(),
generationContext.randomState(),
startPos
);
cobblemon$LOGGER.info(
"[HeightRangeVeto] template={} pos={} valid={}",
element, startPos, valid
);
if (!valid) {
return false;
}
}
return true;
}
@Unique
private static Optional<HeightRangeStructureProcessor> cobblemon$getHeightRangeProcessor(StructurePoolElement element) {
if (!(element instanceof SinglePoolElement)) {
return Optional.empty();
}
StructureProcessorList processorList = ((SinglePoolElementAccessor) element).getProcessors().value();
for (StructureProcessor processor : processorList.list()) {
if (processor instanceof HeightRangeStructureProcessor heightRangeStructureProcessor) {
return Optional.of(heightRangeStructureProcessor);
}
}
return Optional.empty();
}
}
@@ -1,52 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.block.entity.DiscShelfBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.NoteBlock;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(NoteBlock.class)
public abstract class NoteBlockMixin {
@Inject(method = "triggerEvent", at = @At("HEAD"), cancellable = true)
private void cobblemon$triggerTMShelfSequencer(BlockState state, Level level, BlockPos pos, int type, int data, CallbackInfoReturnable<Boolean> cir) {
final BlockPos shelfPos = pos.below();
final BlockEntity blockEntity = level.getBlockEntity(shelfPos);
if (!(blockEntity instanceof DiscShelfBlockEntity discShelfBlockEntity)) {
return;
}
if (!level.isClientSide) {
final BlockPos instrumentPos = shelfPos.below();
final SoundEvent soundEvent;
if (level.getBlockState(instrumentPos).isAir()) {
soundEvent = SoundEvents.NOTE_BLOCK_BIT.value();
} else {
final NoteBlockInstrument instrument = level.getBlockState(instrumentPos).instrument();
final Holder<SoundEvent> holder = instrument.getSoundEvent();
soundEvent = holder.value();
}
discShelfBlockEntity.onNoteBlockPulse(level, pos, soundEvent);
}
// Suppress vanilla note-block playback while it is on top of a TM Shelf.
cir.setReturnValue(false);
}
}
@@ -19,10 +19,8 @@ import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket;
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClientboundSeatAssignmentPacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerLevel;
@@ -39,7 +37,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -56,15 +53,12 @@ public abstract class ServerEntityMixin {
private Matrix3f cobblemon$lastSentOrientation;
@Unique
private boolean cobblemon$lastSentActive;
@Unique
private List<Entity> cobblemon$lastSeatPassengers = List.of();
@Inject(method = "sendChanges", at = @At("TAIL"))
private void cobblemon$sendChanges(CallbackInfo ci) {
cobblemon$sendOrientationChanges();
cobblemon$sendRidingStateChanges();
cobblemon$sendDriverInput();
cobblemon$sendOccupiedSeatsChange();
}
private void cobblemon$sendRidingStateChanges() {
@@ -107,24 +101,6 @@ public abstract class ServerEntityMixin {
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(driverInput, entity.getId()));
}
private void cobblemon$sendOccupiedSeatsChange() {
if (!(this.entity instanceof PokemonEntity pokemon)) return;
List<Entity> currentPassengers = pokemon.getPassengers();
if (currentPassengers.equals(cobblemon$lastSeatPassengers)) return;
cobblemon$lastSeatPassengers = List.copyOf(currentPassengers);
pokemon.getOccupiedSeats().forEach((seat, passenger) -> {
if (seat.getLocator() == null) return;
if (passenger == null) return;
cobblemon$broadcast(new ClientboundSeatAssignmentPacket(
passenger.getId(),
pokemon.getId(),
seat.getLocator()
));
});
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
@@ -139,4 +115,5 @@ public abstract class ServerEntityMixin {
CobblemonNetwork.INSTANCE.sendPacketToPlayer(player, packet);
}
}
}
@@ -23,7 +23,6 @@ import net.minecraft.client.sounds.ChannelAccess;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
@@ -33,7 +32,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
@@ -110,17 +108,6 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
}
}
/**
* TM Shelf can intentionally push note-block pitches above vanilla's 2.0f ceiling for octave-up playback.
* Keep vanilla clamping for all other sounds.
*/
@Inject(method = "calculatePitch", at = @At("HEAD"), cancellable = true)
private void cobblemon$allowExtendedNoteblockPitch(SoundInstance sound, CallbackInfoReturnable<Float> cir) {
if (sound.getLocation().getPath().startsWith("block.note_block.")) {
cir.setReturnValue(Mth.clamp(sound.getPitch(), 0.5F, 4.0F));
}
}
/** Different behavior for resuming SoundInstances while a BattleMusicInstance is being played. We do not want to resume filtered sounds. */
@Inject(method = "resume()V", at = @At("HEAD"), cancellable = true)
public void resume(CallbackInfo cb) {
@@ -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.mixin;
import com.cobblemon.mod.common.world.CobblemonStructureIDs;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
@Mixin(Structure.class)
public abstract class StructureMixin {
// Define a set of structures that should not spawn below Sea Level
@Unique
private static final Set<ResourceLocation> RESTRICTED_STRUCTURES = new HashSet<>(Arrays.asList(
CobblemonStructureIDs.STONJOURNER_HENGE,
CobblemonStructureIDs.LUNA_HENGE,
CobblemonStructureIDs.SOL_HENGE
));
@Inject(method = "generate", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/StructureStart;isValid()Z"),
cancellable = true)
public void cobblemon$isValid(RegistryAccess registryAccess, ChunkGenerator chunkGenerator, BiomeSource biomeSource,
RandomState randomState, StructureTemplateManager structureTemplateManager, long seed,
ChunkPos chunkPos, int references, LevelHeightAccessor heightAccessor,
Predicate<Holder<Biome>> validBiome,
CallbackInfoReturnable<StructureStart> cir,
@Local StructureStart structureStart) {
ResourceLocation structureKey = registryAccess.registryOrThrow(Registries.STRUCTURE).getKey((Structure) (Object) this);
if(!RESTRICTED_STRUCTURES.contains(structureKey)) {
return;
}
for (StructurePiece piece : structureStart.getPieces()) {
if (piece.getBoundingBox().minY() < chunkGenerator.getSeaLevel()) {
cir.setReturnValue(StructureStart.INVALID_START);
return;
}
}
}
}
@@ -1,22 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(SinglePoolElement.class)
public interface SinglePoolElementAccessor {
@Accessor("processors")
Holder<StructureProcessorList> getProcessors();
}
@@ -1,123 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.duck.ChannelDuck;
import com.mojang.blaze3d.audio.Channel;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL11;
import org.lwjgl.openal.EXTEfx;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Channel.class)
public abstract class ChannelMixin implements ChannelDuck {
@Shadow
private int source;
// Ids of effects applied to the channel
private int cobblemon$lowPassFilterId = 0;
private int cobblemon$reverbEffectId = 0;
private int cobblemon$reverbSlotId = 0;
@Override
public void cobblemon$applyLowPassFilter(float gain, float hfGain) {
// Delete old filter if it exists
if (cobblemon$lowPassFilterId != 0) {
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0); // Detach filter from source
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId); // Delete old filter
cobblemon$lowPassFilterId = 0;
}
// Generate a fresh filter
int filterId = EXTEfx.alGenFilters();
if (!EXTEfx.alIsFilter(filterId)) {
System.err.println("Failed to create OpenAL low-pass filter");
return;
}
EXTEfx.alFilteri(filterId, EXTEfx.AL_FILTER_TYPE, EXTEfx.AL_FILTER_LOWPASS);
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAIN, gain);
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAINHF, hfGain);
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, filterId);
// Save the new filter ID
cobblemon$lowPassFilterId = filterId;
}
@Override
public void cobblemon$inverseAttenuation(float rolloffFactor) {
AL10.alSourcei(source, AL10.AL_DISTANCE_MODEL, AL10.AL_INVERSE_DISTANCE);
AL10.alSourcef(source, AL10.AL_ROLLOFF_FACTOR, rolloffFactor);
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, 1.0f);
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, 0.0f);
}
@Override
public void cobblemon$clearFilters() {
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0);
if (EXTEfx.alIsFilter(cobblemon$lowPassFilterId)) {
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId);
cobblemon$lowPassFilterId = 0;
}
}
@Override
public void cobblemon$applyReverb(float decayTime, float gain, float diffusion, float density) {
// Clean up existing reverb first if it exists
cobblemon$clearReverb();
// Create the effect object
int effectId = EXTEfx.alGenEffects();
if (!EXTEfx.alIsEffect(effectId)) {
System.err.println("Failed to create OpenAL reverb effect");
return;
}
EXTEfx.alEffecti(effectId, EXTEfx.AL_EFFECT_TYPE, EXTEfx.AL_EFFECT_REVERB);
EXTEfx.alEffectf(effectId, EXTEfx.AL_REVERB_DECAY_TIME, decayTime);
EXTEfx.alEffectf(effectId, EXTEfx.AL_REVERB_GAIN, gain);
EXTEfx.alEffectf(effectId, EXTEfx.AL_REVERB_DIFFUSION, diffusion);
EXTEfx.alEffectf(effectId, EXTEfx.AL_REVERB_DENSITY, density);
// Create auxiliary effect slot and load effect into it
int slotId = EXTEfx.alGenAuxiliaryEffectSlots();
if (!EXTEfx.alIsAuxiliaryEffectSlot(slotId)) {
System.err.println("Failed to create OpenAL auxiliary effect slot");
EXTEfx.alDeleteEffects(effectId);
return;
}
EXTEfx.alAuxiliaryEffectSloti(slotId, EXTEfx.AL_EFFECTSLOT_EFFECT, effectId);
// Route source through the slot — send 0, no direct filter
AL11.alSource3i(source, EXTEfx.AL_AUXILIARY_SEND_FILTER, slotId, 0, EXTEfx.AL_FILTER_NULL);
cobblemon$reverbEffectId = effectId;
cobblemon$reverbSlotId = slotId;
}
@Override
public void cobblemon$clearReverb() {
if (cobblemon$reverbSlotId != 0) {
// Detach source from slot
AL11.alSource3i(source, EXTEfx.AL_AUXILIARY_SEND_FILTER,
EXTEfx.AL_EFFECTSLOT_NULL, 0, EXTEfx.AL_FILTER_NULL);
EXTEfx.alDeleteAuxiliaryEffectSlots(cobblemon$reverbSlotId);
cobblemon$reverbSlotId = 0;
}
if (cobblemon$reverbEffectId != 0) {
EXTEfx.alDeleteEffects(cobblemon$reverbEffectId);
cobblemon$reverbEffectId = 0;
}
}
}
@@ -9,13 +9,10 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.CobblemonItemComponents;
import com.cobblemon.mod.common.CobblemonItems;
import com.cobblemon.mod.common.ModAPI;
import com.cobblemon.mod.common.item.PokeBallItem;
import com.cobblemon.mod.common.item.PokedexItem;
import com.cobblemon.mod.common.item.WearableItem;
import com.cobblemon.mod.common.item.components.TMMoveComponent;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.ItemModelShaper;
@@ -57,17 +54,11 @@ public abstract class ItemRendererMixin {
cancellable = true
)
private void cobblemon$overrideItemModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource multiBufferSource, int light, int overlay, BakedModel model, CallbackInfo ci) {
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED || stack.getItem() == CobblemonItems.TECHNICAL_MACHINE;
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED;
ResourceLocation resourceLocation = null;
if (shouldBe2d) {
if (stack.getItem() instanceof PokeBallItem pokeBallItem) resourceLocation = pokeBallItem.getPokeBall().getModel2d();
else if (stack.getItem() instanceof PokedexItem pokedexItem) resourceLocation = pokedexItem.getType().getItemSpritePath();
else if (stack.getItem() == CobblemonItems.TECHNICAL_MACHINE) {
TMMoveComponent component = stack.get(CobblemonItemComponents.TM_MOVE);
if (component != null) {
resourceLocation = new ResourceLocation(Cobblemon.MODID, "tm/" + component.getMove().getElementalType().showdownId() + "_tm");
}
}
}
if (renderMode != ItemDisplayContext.HEAD && stack.getItem() instanceof WearableItem wearableItem) resourceLocation = wearableItem.getModel2d();
@@ -23,7 +23,6 @@ import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdatePassengerPositionPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
@@ -149,22 +148,10 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
cobblemon$lastRideYRot = rideYRot;
cobblemon$lastRideEyePos = rideEyePos;
CobblemonNetwork.INSTANCE.sendToServer(
new ServerboundUpdateRiderRotationPacket(rideXRot, rideYRot, rideEyePos)
);
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateRiderRotationPacket(rideXRot, rideYRot, rideEyePos));
}
}
@Inject(method = "rideTick", at = @At("TAIL"))
private void cobblemon$updatePassengerPosition(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
if (pokemonEntity.getSeatForPassenger(this) == null) return;
var offset = this.position().subtract(pokemonEntity.position());
CobblemonNetwork.INSTANCE.sendToServer(
new ServerboundUpdatePassengerPositionPacket(this.getId(), offset)
);
}
@Unique
@@ -230,10 +217,9 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
private void cobblemon$isHandsBusy(CallbackInfoReturnable<Boolean> cir) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
Seat seat = pokemonEntity.getSeatForPassenger(this);;
if (seat != null) {
cir.setReturnValue(seat.getHandsBusy());
}
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
cir.setReturnValue(seat.getHandsBusy());
}
}
@@ -9,7 +9,8 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.CobblemonBakingOverrides;
import net.minecraft.client.Minecraft;
import java.util.List;
import java.util.Map;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.BlockStateModelLoader;
@@ -17,76 +18,29 @@ import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.util.profiling.ProfilerFiller;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Mixin(ModelBakery.class)
public abstract class ModelLoaderMixin {
@Shadow abstract UnbakedModel getModel(ResourceLocation resourceLocation);
@Shadow protected abstract void registerModel(ModelResourceLocation modelId, UnbakedModel unbakedModel);
@Shadow protected abstract void loadItemModelAndDependencies(ResourceLocation resourceLocation);
@Unique
List<String> dynamicFolders = Arrays.asList("egg", "tm");
@Inject(
method = "<init>",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V",
ordinal = 1
)
)
public void loadCustomItemModels(BlockColors blockColors,
ProfilerFiller profiler,
Map<ResourceLocation, BlockModel> jsonUnbakedModels,
Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>> blockStates,
CallbackInfo ci
) {
/*
* The models/item/*.json's only get loaded if there's an item in the registry that actually asks for it. It
* naively looks for things in the root with an item ID and calls upon it, and some of those JSONs reference
* other model JSONs and so it cascades a little bit. Used for block models but more importantly here, sprites.
*
* The thing about the eggs (and TMs) is, we really want the eggs to be addon-compatible in a way that doesn't restrict
* the species that can have eggs, so we can't pre-plan all of them. This mixin looks for the things inside the
* egg subfolder (which won't get loaded automagically) and manually inserts them into the unbaked model list so
* Minecraft loads them for us. We'll then dynamically call these models from the ItemRendererMixin; so it's
* hacky at the init stage and hacky at the render stage. Pretty cool in the gameplay stage though.
*
* - Hiro
*/
dynamicFolders.forEach(folder -> {
Map<ResourceLocation, Resource> map = Minecraft.getInstance().getResourceManager().listResources("models/item/" + folder, (t) -> true);
map.forEach((key, value) -> {
ResourceLocation resourceLocation = new ResourceLocation(key.getNamespace(), key.getPath().replace("models/item/", "").replace(".json", ""));
this.loadItemModelAndDependencies(resourceLocation);
});
});
}
@Inject(
method = "<init>",
at = @At("TAIL")
method = "<init>",
at = @At("TAIL")
)
public void init(BlockColors blockColors,
ProfilerFiller profiler,
Map<ResourceLocation, BlockModel> jsonUnbakedModels,
Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>> blockStates,
CallbackInfo ci) {
ProfilerFiller profiler,
Map<ResourceLocation, BlockModel> jsonUnbakedModels,
Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>> blockStates,
CallbackInfo ci) {
CobblemonBakingOverrides.INSTANCE.getModels().forEach(bakingOverride -> {
var unbakedModel = this.getModel(bakingOverride.getModelLocation());
this.registerModel(bakingOverride.getModelIdentifier(), unbakedModel);
@@ -18,10 +18,10 @@ 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
import com.cobblemon.mod.common.api.npc.premades.PremadeNPCs
import com.cobblemon.mod.common.api.permission.PermissionValidator
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculators
@@ -34,7 +34,6 @@ import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
import com.cobblemon.mod.common.api.pokemon.stats.EvCalculator
import com.cobblemon.mod.common.api.pokemon.stats.Generation8EvCalculator
@@ -64,8 +63,6 @@ import com.cobblemon.mod.common.api.storage.player.adapter.DexDataMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.DexDataNbtBackend
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataJsonBackend
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveNbtBackend
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
@@ -94,11 +91,9 @@ import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacke
import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.aspects.ALPHA_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.CHARACTERISTIC_RAINBOW_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.COSMETIC_SLOT_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.GENDER_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.MARK_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeature
@@ -108,7 +103,6 @@ import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.FreezeFrameProperty
import com.cobblemon.mod.common.pokemon.properties.HeldItemVisibleProperty
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.LabelProperty
import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
@@ -116,7 +110,13 @@ import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifDedicatedServer
import com.cobblemon.mod.common.util.isLaterVersion
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.requestWallpapers
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
@@ -183,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
@@ -245,8 +247,6 @@ object Cobblemon {
DATA_SYNCHRONIZED.subscribe {
storage.onPlayerDataSync(it)
it.tmList().scheduleFullSyncFromStores(it.party(), it.pc())
it.pokedex().scheduleFullSyncFromStores(it.party(), it.pc())
playerDataManager.syncAllToPlayer(it)
starterHandler.handleJoin(it)
it.requestWallpapers()
@@ -259,6 +259,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)
@@ -309,9 +310,6 @@ object Cobblemon {
this.config.maxPokemonLevel,
this.config.maxPokemonFriendship,
this.config.maxDynamaxLevel,
this.config.unlockAllMoveDexMovesByDefault,
this.config.pokemonIntrinsicSizeMin,
this.config.pokemonIntrinsicSizeMax
).sendToPlayer(player)
}
@@ -325,11 +323,8 @@ object Cobblemon {
GENDER_ASPECT.register()
COSMETIC_SLOT_ASPECT.register()
CHARACTERISTIC_RAINBOW_ASPECT.register()
ALPHA_ASPECT.register()
MARK_ASPECT.register()
SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java
SpeciesFeatures.types["weighted_choice"] = WeightedChoiceSpeciesFeatureProvider::class.java
SpeciesFeatures.types["flag"] = FlagSpeciesFeatureProvider::class.java
SpeciesFeatures.types["integer"] = IntSpeciesFeatureProvider::class.java
@@ -351,7 +346,6 @@ object Cobblemon {
CustomPokemonProperty.register(UnaspectPropertyType)
CustomPokemonProperty.register(FreezeFrameProperty)
CustomPokemonProperty.register(NoAIProperty)
CustomPokemonProperty.register(HeldItemVisibleProperty)
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
@@ -371,9 +365,9 @@ object Cobblemon {
}
PlatformEvents.SERVER_STARTING.subscribe { event ->
val server = event.server
PremadeNPCs.onServerLoading(server)
MoLangLoadedFilesCache.initialize(server)
playerDataManager = PlayerInstancedDataStoreManager().also { it.setup(server) }
HabitatPools.onServerLoading(server)
val mongoClient: MongoClient?
@@ -386,12 +380,8 @@ object Cobblemon {
val pokedexNbtFactory = CachedPlayerDataStoreFactory(DexDataNbtBackend())
pokedexNbtFactory.setup(server)
val tmMoveFactory = CachedPlayerDataStoreFactory(TMMoveNbtBackend())
tmMoveFactory.setup(server)
playerDataManager.setFactory(generalJsonFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexNbtFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMoveFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
if (config.storageFormat == "nbt") {
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
@@ -418,14 +408,8 @@ object Cobblemon {
val pokedexMongoFactory = CachedPlayerDataStoreFactory(DexDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PokeDexCollection"))
pokedexMongoFactory.setup(server)
val tmMovesMongoFactory = CachedPlayerDataStoreFactory(
TMMoveMongoBackend(mongoClient, config.mongoDBDatabaseName, "TMMovesCollection")
)
tmMovesMongoFactory.setup(server)
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexMongoFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMovesMongoFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
} catch (e: ClassNotFoundException) {
LOGGER.error("MongoDB driver not found.")
@@ -609,7 +593,6 @@ object Cobblemon {
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
this.implementation.registerCommandArgument(cobblemonResource("tm"), TmArgumentType::class, SingletonArgumentInfo.contextFree(TmArgumentType::tm))
}
}
@@ -9,9 +9,12 @@
package com.cobblemon.mod.common
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonBehaviours.autoPokemonBehaviours
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
@@ -19,16 +22,7 @@ import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.BehaviourSyncPacket
import com.cobblemon.mod.common.util.adapters.ActivityAdapter
import com.cobblemon.mod.common.util.adapters.BehaviourConfigAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
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.SensorTypeAdapter
import com.cobblemon.mod.common.util.adapters.TaskConfigAdapter
import com.cobblemon.mod.common.util.adapters.TranslatedTextAdapter
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.endsWith
import com.google.gson.Gson
@@ -55,6 +49,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
@@ -102,6 +98,7 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
autoNPCBehaviours.add(behaviour)
}
data[resolvedIdentifier] = behaviour
behaviour.id = resolvedIdentifier
}
}
}
@@ -8,15 +8,22 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.entity.*
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
import com.cobblemon.mod.common.block.entity.PCBlockEntity
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
import com.cobblemon.mod.common.block.entity.FossilAnalyzerBlockEntity
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
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.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.resources.ResourceKey
object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, ResourceKey<Registry<BlockEntityType<*>>>, BlockEntityType<*>>() {
@@ -32,9 +39,6 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
@JvmField
val LECTERN: BlockEntityType<LecternBlockEntity> = this.create("lectern", BlockEntityType.Builder.of(::LecternBlockEntity, CobblemonBlocks.LECTERN).build(null))
@JvmField
val DISC_SHELF: BlockEntityType<DiscShelfBlockEntity> = this.create("disc_shelf", BlockEntityType.Builder.of(::DiscShelfBlockEntity, CobblemonBlocks.DISC_SHELF).build(null))
@JvmField
val BERRY = this.create("berry", BlockEntityType.Builder.of(::BerryBlockEntity, *CobblemonBlocks.berries().values.toTypedArray()).build(null))
@@ -44,13 +48,6 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
val SIGN: BlockEntityType<CobblemonSignBlockEntity> = this.create("sign", BlockEntityType.Builder.of(::CobblemonSignBlockEntity, CobblemonBlocks.APRICORN_SIGN, CobblemonBlocks.APRICORN_WALL_SIGN, CobblemonBlocks.SACCHARINE_SIGN, CobblemonBlocks.SACCHARINE_WALL_SIGN).build(null))
@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 TM_MACHINE: BlockEntityType<TMMachineBlockEntity> = this.create("tm_machine", BlockEntityType.Builder.of(::TMMachineBlockEntity, CobblemonBlocks.TM_MACHINE).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,
@@ -11,27 +11,20 @@ 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.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.LecternBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
import com.cobblemon.mod.common.block.chest.GildedChestBlock
import com.cobblemon.mod.common.block.general.BaleBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
import com.cobblemon.mod.common.block.grower.SaccharineTreeGrower
import com.cobblemon.mod.common.block.habitat.HabitatBlock
import com.cobblemon.mod.common.block.sign.CobblemonHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
import com.cobblemon.mod.common.mixin.invoker.BlocksInvoker
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.FireBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.PressurePlateBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.StairsBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.TrapdoorBlockInvoker
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
import com.cobblemon.mod.common.mixin.invoker.*
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.BlockPos
@@ -53,6 +46,7 @@ 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>() {
@@ -127,19 +121,19 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val APRICORN_PLANKS = this.create("apricorn_planks",
Block(
BlockBehaviour.Properties.of().mapColor(MapColor.DIRT).instrument(NoteBlockInstrument.BASS)
.strength(2.0F, 3.0F).sound(SoundType.WOOD)
.strength(2.0f, 3.0f).sound(SoundType.WOOD)
)
)
@JvmField
val APRICORN_LEAVES = leaves("apricorn_leaves")
@JvmField
val APRICORN_FENCE = this.create("apricorn_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
val APRICORN_FENCE = this.create("apricorn_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val APRICORN_FENCE_GATE = this.create("apricorn_fence_gate", FenceGateBlock(APRICORN_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).strength(2.0F, 3.0F).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
val APRICORN_FENCE_GATE = this.create("apricorn_fence_gate", FenceGateBlock(APRICORN_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).strength(2.0f, 3.0f).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
@JvmField
val APRICORN_BUTTON = this.create("apricorn_button", BlocksInvoker.createWoodenButtonBlock(APRICORN_BLOCK_SET_TYPE))
@JvmField
val APRICORN_PRESSURE_PLATE = this.create("apricorn_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(APRICORN_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).noCollission().strength(0.5F).sound(SoundType.WOOD)))
val APRICORN_PRESSURE_PLATE = this.create("apricorn_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(APRICORN_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).noCollission().strength(0.5f).sound(SoundType.WOOD)))
@JvmField
val APRICORN_SIGN = this.create("apricorn_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), APRICORN_WOOD_TYPE))
@@ -156,7 +150,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val APRICORN_WALL_HANGING_SIGN = this.create("apricorn_wall_hanging_sign", CobblemonWallHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), APRICORN_WOOD_TYPE))
@JvmField
val APRICORN_SLAB = this.create("apricorn_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
val APRICORN_SLAB = this.create("apricorn_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val APRICORN_STAIRS = this.create("apricorn_stairs", StairsBlockInvoker.`cobblemon$create`(APRICORN_PLANKS.defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(APRICORN_PLANKS)))
@JvmField
@@ -193,12 +187,12 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val STRIPPED_SACCHARINE_WOOD = this.create("stripped_saccharine_wood", RotatedPillarBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0F).sound(SoundType.WOOD).ignitedByLava())) //log("stripped_saccharine_log")
@JvmField
val SACCHARINE_PLANKS = this.create("saccharine_planks", Block(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
val SACCHARINE_PLANKS = this.create("saccharine_planks", Block(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_LEAVES = this.create("saccharine_leaves", SaccharineLeafBlock(
BlockBehaviour.Properties.of().mapColor(
MapColor.PLANT
).strength(0.2F).randomTicks().sound(SoundType.GRASS).noOcclusion()
).strength(0.2f).randomTicks().sound(SoundType.GRASS).noOcclusion()
.isValidSpawn { _: BlockState?, _: BlockGetter?, _: BlockPos?, entity: EntityType<*>? ->
entity === EntityType.OCELOT || entity === EntityType.PARROT
}.isSuffocating { _: BlockState?, _: BlockGetter?, _: BlockPos? ->
@@ -210,13 +204,13 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
false
}))
@JvmField
val SACCHARINE_FENCE = this.create("saccharine_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
val SACCHARINE_FENCE = this.create("saccharine_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
@JvmField
val SACCHARINE_BUTTON = this.create("saccharine_button", BlocksInvoker.createWoodenButtonBlock(BlockSetType.OAK))
@JvmField
val SACCHARINE_PRESSURE_PLATE = this.create("saccharine_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).noCollission().strength(0.5F).sound(SoundType.WOOD)))
val SACCHARINE_PRESSURE_PLATE = this.create("saccharine_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).noCollission().strength(0.5f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_SIGN = this.create("saccharine_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
@@ -226,7 +220,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val SACCHARINE_WALL_HANGING_SIGN = this.create("saccharine_wall_hanging_sign", CobblemonWallHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_SLAB = this.create("saccharine_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
val SACCHARINE_SLAB = this.create("saccharine_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_STAIRS = this.create("saccharine_stairs", StairsBlockInvoker.`cobblemon$create`(SACCHARINE_PLANKS.defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(SACCHARINE_PLANKS)))
@JvmField
@@ -255,31 +249,31 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, true))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null, MapColor.TERRACOTTA_ORANGE)
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER, MapColor.TERRACOTTA_ORANGE)
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE, MapColor.TERRACOTTA_ORANGE)
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE, MapColor.TERRACOTTA_ORANGE)
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
@JvmField
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null, MapColor.COLOR_LIGHT_BLUE)
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER, MapColor.COLOR_LIGHT_BLUE)
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE, MapColor.COLOR_LIGHT_BLUE)
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE, MapColor.COLOR_LIGHT_BLUE)
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
@JvmField
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null, MapColor.TERRACOTTA_BLACK)
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER, MapColor.TERRACOTTA_BLACK)
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE, MapColor.TERRACOTTA_BLACK)
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE, MapColor.TERRACOTTA_BLACK)
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
@JvmField
val TUMBLESTONE_BLOCK = this.create("tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@@ -288,87 +282,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val BLACK_TUMBLESTONE_BLOCK = this.create("black_tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val TYPE_GEM_CORE = this.create("deepslate_crystal_core", TypeGemCoreBlock(BlockBehaviour.Properties.of().strength(4.0F, 10.0F).requiresCorrectToolForDrops().mapColor(MapColor.DEEPSLATE)))
private val typeGemBlocks = mutableMapOf<ResourceLocation, Block>()
@JvmField
val TYPE_GEM_BLOCK_NORMAL = typeGemBlock("normal_gem_block", MapColor.QUARTZ)
@JvmField
val TYPE_GEM_BLOCK_FIRE = typeGemBlock("fire_gem_block", MapColor.FIRE)
@JvmField
val TYPE_GEM_BLOCK_WATER = typeGemBlock("water_gem_block", MapColor.WATER)
@JvmField
val TYPE_GEM_BLOCK_GRASS = typeGemBlock("grass_gem_block", MapColor.COLOR_LIGHT_GREEN)
@JvmField
val TYPE_GEM_BLOCK_ELECTRIC = typeGemBlock("electric_gem_block", MapColor.GOLD)
@JvmField
val TYPE_GEM_BLOCK_ICE = typeGemBlock("ice_gem_block", MapColor.ICE)
@JvmField
val TYPE_GEM_BLOCK_FIGHTING = typeGemBlock("fighting_gem_block", MapColor.COLOR_RED)
@JvmField
val TYPE_GEM_BLOCK_POISON = typeGemBlock("poison_gem_block", MapColor.COLOR_PURPLE)
@JvmField
val TYPE_GEM_BLOCK_GROUND = typeGemBlock("ground_gem_block", MapColor.PODZOL)
@JvmField
val TYPE_GEM_BLOCK_FLYING = typeGemBlock("flying_gem_block", MapColor.COLOR_LIGHT_BLUE)
@JvmField
val TYPE_GEM_BLOCK_PSYCHIC = typeGemBlock("psychic_gem_block", MapColor.COLOR_MAGENTA)
@JvmField
val TYPE_GEM_BLOCK_BUG = typeGemBlock("bug_gem_block", MapColor.TERRACOTTA_LIGHT_GREEN)
@JvmField
val TYPE_GEM_BLOCK_ROCK = typeGemBlock("rock_gem_block", MapColor.TERRACOTTA_LIGHT_GRAY)
@JvmField
val TYPE_GEM_BLOCK_GHOST = typeGemBlock("ghost_gem_block", MapColor.TERRACOTTA_MAGENTA)
@JvmField
val TYPE_GEM_BLOCK_DRAGON = typeGemBlock("dragon_gem_block", MapColor.COLOR_BLUE)
@JvmField
val TYPE_GEM_BLOCK_DARK = typeGemBlock("dark_gem_block", MapColor.TERRACOTTA_BLACK)
@JvmField
val TYPE_GEM_BLOCK_STEEL = typeGemBlock("steel_gem_block", MapColor.METAL)
@JvmField
val TYPE_GEM_BLOCK_FAIRY = typeGemBlock("fairy_gem_block", MapColor.COLOR_PINK)
private val typeGemClusters = mutableMapOf<ResourceLocation, TypeGemClusterBlock>()
@JvmField
val TYPE_GEM_CLUSTER_NORMAL = typeGemCluster("normal_gem_cluster", TYPE_GEM_BLOCK_NORMAL, cobblemonResource("normal_gem"), MapColor.QUARTZ)
@JvmField
val TYPE_GEM_CLUSTER_FIRE = typeGemCluster("fire_gem_cluster", TYPE_GEM_BLOCK_FIRE, cobblemonResource("fire_gem"), MapColor.FIRE)
@JvmField
val TYPE_GEM_CLUSTER_WATER = typeGemCluster("water_gem_cluster", TYPE_GEM_BLOCK_WATER, cobblemonResource("water_gem"), MapColor.WATER)
@JvmField
val TYPE_GEM_CLUSTER_GRASS = typeGemCluster("grass_gem_cluster", TYPE_GEM_BLOCK_GRASS, cobblemonResource("grass_gem"), MapColor.COLOR_LIGHT_GREEN)
@JvmField
val TYPE_GEM_CLUSTER_ELECTRIC = typeGemCluster("electric_gem_cluster", TYPE_GEM_BLOCK_ELECTRIC, cobblemonResource("electric_gem"), MapColor.GOLD)
@JvmField
val TYPE_GEM_CLUSTER_ICE = typeGemCluster("ice_gem_cluster", TYPE_GEM_BLOCK_ICE, cobblemonResource("ice_gem"), MapColor.ICE)
@JvmField
val TYPE_GEM_CLUSTER_FIGHTING = typeGemCluster("fighting_gem_cluster", TYPE_GEM_BLOCK_FIGHTING, cobblemonResource("fighting_gem"), MapColor.COLOR_RED)
@JvmField
val TYPE_GEM_CLUSTER_POISON = typeGemCluster("poison_gem_cluster", TYPE_GEM_BLOCK_POISON, cobblemonResource("poison_gem"), MapColor.COLOR_PURPLE)
@JvmField
val TYPE_GEM_CLUSTER_GROUND = typeGemCluster("ground_gem_cluster", TYPE_GEM_BLOCK_GROUND, cobblemonResource("ground_gem"), MapColor.PODZOL)
@JvmField
val TYPE_GEM_CLUSTER_FLYING = typeGemCluster("flying_gem_cluster", TYPE_GEM_BLOCK_FLYING, cobblemonResource("flying_gem"), MapColor.COLOR_LIGHT_BLUE)
@JvmField
val TYPE_GEM_CLUSTER_PSYCHIC = typeGemCluster("psychic_gem_cluster", TYPE_GEM_BLOCK_PSYCHIC, cobblemonResource("psychic_gem"), MapColor.COLOR_MAGENTA)
@JvmField
val TYPE_GEM_CLUSTER_BUG = typeGemCluster("bug_gem_cluster", TYPE_GEM_BLOCK_BUG, cobblemonResource("bug_gem"), MapColor.TERRACOTTA_LIGHT_GREEN)
@JvmField
val TYPE_GEM_CLUSTER_ROCK = typeGemCluster("rock_gem_cluster", TYPE_GEM_BLOCK_ROCK, cobblemonResource("rock_gem"), MapColor.TERRACOTTA_LIGHT_GRAY)
@JvmField
val TYPE_GEM_CLUSTER_GHOST = typeGemCluster("ghost_gem_cluster", TYPE_GEM_BLOCK_GHOST, cobblemonResource("ghost_gem"), MapColor.TERRACOTTA_MAGENTA)
@JvmField
val TYPE_GEM_CLUSTER_DRAGON = typeGemCluster("dragon_gem_cluster", TYPE_GEM_BLOCK_DRAGON, cobblemonResource("dragon_gem"), MapColor.COLOR_BLUE)
@JvmField
val TYPE_GEM_CLUSTER_DARK = typeGemCluster("dark_gem_cluster", TYPE_GEM_BLOCK_DARK, cobblemonResource("dark_gem"), MapColor.TERRACOTTA_BLACK)
@JvmField
val TYPE_GEM_CLUSTER_STEEL = typeGemCluster("steel_gem_cluster", TYPE_GEM_BLOCK_STEEL, cobblemonResource("steel_gem"), MapColor.METAL)
@JvmField
val TYPE_GEM_CLUSTER_FAIRY = typeGemCluster("fairy_gem_cluster", TYPE_GEM_BLOCK_FAIRY, cobblemonResource("fairy_gem"), MapColor.COLOR_PINK)
@JvmField
val POLISHED_TUMBLESTONE = this.create("polished_tumblestone", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
@@ -537,18 +450,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
)
)
@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 CAMPFIRE = create("campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel{14}, false))
@@ -558,7 +459,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
@JvmField
val RELIC_COIN_SACK = create("relic_coin_sack", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_SACK_SOUNDS).pushReaction(PushReaction.DESTROY).strength(0.4F).mapColor(MapColor.GOLD), false))
val RELIC_COIN_SACK = create("relic_coin_sack", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_SACK_SOUNDS).pushReaction(PushReaction.DESTROY).strength(0.4f), false))
@JvmField
val GILDED_CHEST = create("gilded_chest", GildedChestBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.CHEST).noOcclusion().sound(CobblemonSounds.GILDED_CHEST_SOUNDS), GildedChestBlock.Type.RED))
@@ -611,14 +512,14 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val PINK_PLAQUE = create("pink_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val CLEANSE_TAG = create("cleanse_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_SMALL_SOUNDS).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
val CLEANSE_TAG = create("cleanse_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
@JvmField
val SPELL_TAG = create("spell_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_SMALL_SOUNDS).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
val SPELL_TAG = create("spell_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
@JvmField
val BLUNDER_POLICY = create("blunder_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
val BLUNDER_POLICY = create("blunder_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@@ -673,43 +574,25 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
@JvmField
val DAMAGED_MONITOR = create("damaged_monitor", DamagedMonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { when (it.getValue(DamagedMonitorBlock.SCREEN)) { DamagedMonitorBlock.BrokenMonitorScreen.GLITCHING -> 10; else -> 0 } }))
@JvmField
val FOSSIL_ANALYZER = create("fossil_analyzer", FossilAnalyzerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).noOcclusion()))
@JvmField
val RESTORATION_TANK = create("restoration_tank", RestorationTankBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.GLASS).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).noOcclusion()))
@JvmField
val HEALING_MACHINE = create("healing_machine", HealingMachineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if (it.getValue(HealingMachineBlock.CHARGE_LEVEL) >= HealingMachineBlock.MAX_CHARGE_LEVEL) 12 else 5 }))
val HEALING_MACHINE = create("healing_machine", HealingMachineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2f).noOcclusion().lightLevel { if (it.getValue(HealingMachineBlock.CHARGE_LEVEL) >= HealingMachineBlock.MAX_CHARGE_LEVEL) 12 else 5 }))
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
val TM_MACHINE = create(
"tm_machine",
TMMachineBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_GREEN)
.sound(SoundType.METAL)
.pushReaction(PushReaction.BLOCK)
.strength(2F)
.noOcclusion()
)
)
@JvmField
val LECTERN = create("lectern", LecternBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.WOOD).strength(2.5F).ignitedByLava().lightLevel { if (it.getValue(LecternBlock.EMIT_LIGHT) as Boolean) 13 else 0 }))
@JvmField
val DISC_SHELF = create("disc_shelf", DiscShelfBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.METAL).strength(2.5F)))
@JvmField
val DISPLAY_CASE = create("display_case", DisplayCaseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F).requiresCorrectToolForDrops()))
// TODO after 1.7
// @JvmField
// val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F)))
// val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3f)))
@JvmField
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
@@ -777,9 +660,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
private fun apricornBlock(name: String, apricorn: Apricorn): ApricornBlock = this.create(name, ApricornBlock(BlockBehaviour.Properties.of().mapColor(apricorn.mapColor()).randomTicks().strength(Blocks.OAK_LOG.defaultDestroyTime(), Blocks.OAK_LOG.explosionResistance).sound(SoundType.WOOD).noOcclusion(), apricorn))
private fun tumblestoneBlock(name: String, stage: Int, height: Int, xzOffset: Int, nextStage: Block?, mapColor: MapColor) : Block {
private fun tumblestoneBlock(name: String, stage: Int, height: Int, xzOffset: Int, nextStage: Block?) : Block {
return this.create(name, TumblestoneBlock(BlockBehaviour.Properties.of()
.mapColor(mapColor)
.pushReaction(PushReaction.DESTROY)
.noOcclusion()
.strength(1.5F)
@@ -1008,28 +890,4 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val block = BlocksInvoker.createLeavesBlock(SoundType.GRASS)
return this.create(name, block)
}
private fun typeGemBlock(name: String, mapColor: MapColor): Block {
val typeGemBlock = this.create(name, Block(BlockBehaviour.Properties.of().mapColor(mapColor).strength(3.0F, 6.0F).sound(CobblemonSounds.TYPE_GEM_BLOCK_SOUNDS)))
val identifier = cobblemonResource(name)
typeGemBlocks[identifier] = typeGemBlock
return typeGemBlock
}
fun typeGemBlocks() = typeGemBlocks.toMap()
private fun typeGemCluster(name: String, gemBlock: Block, dropItemId: ResourceLocation, mapColor: MapColor): Block {
val gemClusterBlock = this.create(name, TypeGemClusterBlock(BlockBehaviour.Properties.of().mapColor(mapColor).strength(2.0F, 3.0F).sound(CobblemonSounds.TYPE_GEM_CLUSTER_SOUNDS).pushReaction(PushReaction.DESTROY), gemBlock, dropItemId))
TypeGemClusterBlock.gemToClusterMap[gemBlock] = gemClusterBlock
val identifier = cobblemonResource(name)
typeGemClusters[identifier] = gemClusterBlock
return gemClusterBlock
}
fun typeGemClusters() = typeGemClusters.toMap()
}
}
@@ -71,11 +71,11 @@ object CobblemonCommands {
MarkGiveAllCommand.register(dispatcher)
PcSearchCommand.register(dispatcher)
PcTakeCommand.register(dispatcher)
GiveTmCommand.register(dispatcher)
TmCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
CobblemonInfoCommand.register(dispatcher)
CalculateSeatPositionCommand.register(dispatcher)
DebugCobblemonAICommand.register(dispatcher)
}
}
}
@@ -19,7 +19,6 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.PreparableReloadListener
import net.minecraft.tags.TagKey
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.level.GameRules
import net.minecraft.world.level.ItemLike
import net.minecraft.world.level.biome.Biome
@@ -69,12 +69,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
.build())
@JvmField
val TM_MOVE: DataComponentType<TMMoveComponent> = create("tm_move", DataComponentType.builder<TMMoveComponent>()
.persistent(TMMoveComponent.CODEC)
.networkSynchronized(TMMoveComponent.PACKET_CODEC)
.build())
@JvmField
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
.persistent(IngredientComponent.CODEC)
@@ -107,7 +101,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:flavour"), FLAVOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ride_boosts"), RIDE_BOOST)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food_colour"), FOOD_COLOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:tm_move"), TM_MOVE)
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)
@@ -21,8 +21,8 @@ import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.MintBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.campfirepot.CampfirePotColor
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.block.campfirepot.CampfirePotColor
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.item.*
import com.cobblemon.mod.common.item.armor.CobblemonArmorTrims
@@ -35,6 +35,7 @@ import com.cobblemon.mod.common.item.berry.HealingBerryItem
import com.cobblemon.mod.common.item.berry.PPRestoringBerryItem
import com.cobblemon.mod.common.item.berry.PortionHealingBerryItem
import com.cobblemon.mod.common.item.berry.StatusCuringBerryItem
import com.cobblemon.mod.common.item.interactive.PonigiriItem
import com.cobblemon.mod.common.item.food.SinisterTeaItem
import com.cobblemon.mod.common.item.interactive.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityChangeItem
@@ -405,7 +406,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 64, 7, 0.3f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false)
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 64, 7, 0.3f, false)
@JvmField
@@ -452,16 +453,10 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MONITOR = blockItem("monitor", CobblemonBlocks.MONITOR)
@JvmField
val DAMAGED_MONITOR = blockItem("damaged_monitor", CobblemonBlocks.DAMAGED_MONITOR)
@JvmField
val HEALING_MACHINE = blockItem("healing_machine", CobblemonBlocks.HEALING_MACHINE, Rarity.UNCOMMON)
@JvmField
val PC = blockItem("pc", CobblemonBlocks.PC)
@JvmField
val TM_MACHINE = blockItem("tm_machine", CobblemonBlocks.TM_MACHINE)
@JvmField
val DISC_SHELF = blockItem("disc_shelf", CobblemonBlocks.DISC_SHELF)
@JvmField
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
@JvmField
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
@@ -604,9 +599,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val BERRY_JUICE = this.create("berry_juice", BerryJuiceItem())
@JvmField
val HABITAT_BLOCK = blockItem("habitat_block", CobblemonBlocks.HABITAT_BLOCK)
@JvmField
val GALARICA_NUTS = compostableItemNameBlockItem("galarica_nuts", CobblemonBlocks.GALARICA_NUT_BUSH)
@@ -827,7 +819,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val DIRE_HIT = create("dire_hit", DireHitItem(CobblemonBlocks.DIRE_HIT))
@JvmField val GUARD_SPEC = create("guard_spec", GuardSpecItem(CobblemonBlocks.GUARD_SPEC))
@JvmField val FULL_HEAL = create("full_heal", StatusCureItem("item.cobblemon.full_heal", *Statuses.getPersistentStatuses().toTypedArray(), block = CobblemonBlocks.FULL_HEAL))
@JvmField val FULL_HEAL = create("full_heal", StatusCureItem("item.cobblemon.full_heal", block = CobblemonBlocks.FULL_HEAL))
@JvmField val ANTIDOTE = create("antidote", StatusCureItem("item.cobblemon.antidote", Statuses.POISON, Statuses.POISON_BADLY, block = CobblemonBlocks.ANTIDOTE))
@JvmField val AWAKENING = create("awakening", StatusCureItem("item.cobblemon.awakening", Statuses.SLEEP, block = CobblemonBlocks.AWAKENING))
@JvmField val BURN_HEAL = create("burn_heal", StatusCureItem("item.cobblemon.burn_heal", Statuses.BURN, block = CobblemonBlocks.BURN_HEAL))
@@ -1216,83 +1208,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BLACK_TUMBLESTONE_BLOCK = blockItem("black_tumblestone_block", CobblemonBlocks.BLACK_TUMBLESTONE_BLOCK)
@JvmField
val TYPE_GEM_CORE = blockItem("deepslate_crystal_core", CobblemonBlocks.TYPE_GEM_CORE)
@JvmField
val TYPE_GEM_CLUSTER_NORMAL = blockItem("normal_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL)
@JvmField
val TYPE_GEM_CLUSTER_FIRE = blockItem("fire_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE)
@JvmField
val TYPE_GEM_CLUSTER_WATER = blockItem("water_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_WATER)
@JvmField
val TYPE_GEM_CLUSTER_ELECTRIC = blockItem("electric_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC)
@JvmField
val TYPE_GEM_CLUSTER_GRASS = blockItem("grass_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS)
@JvmField
val TYPE_GEM_CLUSTER_ICE = blockItem("ice_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_ICE)
@JvmField
val TYPE_GEM_CLUSTER_FIGHTING = blockItem("fighting_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING)
@JvmField
val TYPE_GEM_CLUSTER_POISON = blockItem("poison_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_POISON)
@JvmField
val TYPE_GEM_CLUSTER_GROUND = blockItem("ground_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND)
@JvmField
val TYPE_GEM_CLUSTER_FLYING = blockItem("flying_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING)
@JvmField
val TYPE_GEM_CLUSTER_PSYCHIC = blockItem("psychic_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC)
@JvmField
val TYPE_GEM_CLUSTER_BUG = blockItem("bug_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_BUG)
@JvmField
val TYPE_GEM_CLUSTER_ROCK = blockItem("rock_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK)
@JvmField
val TYPE_GEM_CLUSTER_GHOST = blockItem("ghost_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST)
@JvmField
val TYPE_GEM_CLUSTER_DRAGON = blockItem("dragon_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON)
@JvmField
val TYPE_GEM_CLUSTER_DARK = blockItem("dark_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_DARK)
@JvmField
val TYPE_GEM_CLUSTER_STEEL = blockItem("steel_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL)
@JvmField
val TYPE_GEM_CLUSTER_FAIRY = blockItem("fairy_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY)
@JvmField
val TYPE_GEM_BLOCK_NORMAL = blockItem("normal_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_NORMAL)
@JvmField
val TYPE_GEM_BLOCK_FIRE = blockItem("fire_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_FIRE)
@JvmField
val TYPE_GEM_BLOCK_WATER = blockItem("water_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_WATER)
@JvmField
val TYPE_GEM_BLOCK_ELECTRIC = blockItem("electric_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_ELECTRIC)
@JvmField
val TYPE_GEM_BLOCK_GRASS = blockItem("grass_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_GRASS)
@JvmField
val TYPE_GEM_BLOCK_ICE = blockItem("ice_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_ICE)
@JvmField
val TYPE_GEM_BLOCK_FIGHTING = blockItem("fighting_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_FIGHTING)
@JvmField
val TYPE_GEM_BLOCK_POISON = blockItem("poison_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_POISON)
@JvmField
val TYPE_GEM_BLOCK_GROUND = blockItem("ground_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_GROUND)
@JvmField
val TYPE_GEM_BLOCK_FLYING = blockItem("flying_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_FLYING)
@JvmField
val TYPE_GEM_BLOCK_PSYCHIC = blockItem("psychic_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_PSYCHIC)
@JvmField
val TYPE_GEM_BLOCK_BUG = blockItem("bug_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_BUG)
@JvmField
val TYPE_GEM_BLOCK_ROCK = blockItem("rock_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_ROCK)
@JvmField
val TYPE_GEM_BLOCK_GHOST = blockItem("ghost_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_GHOST)
@JvmField
val TYPE_GEM_BLOCK_DRAGON = blockItem("dragon_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_DRAGON)
@JvmField
val TYPE_GEM_BLOCK_DARK = blockItem("dark_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_DARK)
@JvmField
val TYPE_GEM_BLOCK_STEEL = blockItem("steel_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_STEEL)
@JvmField
val TYPE_GEM_BLOCK_FAIRY = blockItem("fairy_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_FAIRY)
@JvmField
val POKEROD_SMITHING_TEMPLATE = create("pokerod_smithing_template", SmithingTemplateItem(
Component.translatable("item.minecraft.fishing_rod").blue(),
@@ -1517,49 +1432,44 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val RELIC_COIN_POUCH = blockItem("relic_coin_pouch", CobblemonBlocks.RELIC_COIN_POUCH)
@JvmField
val RELIC_COIN_SACK = blockItem("relic_coin_sack", CobblemonBlocks.RELIC_COIN_SACK)
@JvmField
val TECHNICAL_MACHINE = this.create("technical_machine", TechnicalMachineItem(Properties()))
@JvmField
val BLANK_TM = this.create("blank_tm", CobblemonItem(Item.Properties()))
// Type Gems
@JvmField
val NORMAL_GEM = this.create("normal_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL))
val NORMAL_GEM = noSettingsItem("normal_gem")
@JvmField
val FIRE_GEM = this.create("fire_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE))
val FIRE_GEM = noSettingsItem("fire_gem")
@JvmField
val WATER_GEM = this.create("water_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_WATER))
val WATER_GEM = noSettingsItem("water_gem")
@JvmField
val GRASS_GEM = this.create("grass_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS))
val GRASS_GEM = noSettingsItem("grass_gem")
@JvmField
val ELECTRIC_GEM = this.create("electric_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC))
val ELECTRIC_GEM = noSettingsItem("electric_gem")
@JvmField
val ICE_GEM = this.create("ice_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_ICE))
val ICE_GEM = noSettingsItem("ice_gem")
@JvmField
val FIGHTING_GEM = this.create("fighting_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING))
val FIGHTING_GEM = noSettingsItem("fighting_gem")
@JvmField
val POISON_GEM = this.create("poison_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_POISON))
val POISON_GEM = noSettingsItem("poison_gem")
@JvmField
val GROUND_GEM = this.create("ground_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND))
val GROUND_GEM = noSettingsItem("ground_gem")
@JvmField
val FLYING_GEM = this.create("flying_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING))
val FLYING_GEM = noSettingsItem("flying_gem")
@JvmField
val PSYCHIC_GEM = this.create("psychic_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC))
val PSYCHIC_GEM = noSettingsItem("psychic_gem")
@JvmField
val BUG_GEM = this.create("bug_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_BUG))
val BUG_GEM = noSettingsItem("bug_gem")
@JvmField
val ROCK_GEM = this.create("rock_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK))
val ROCK_GEM = noSettingsItem("rock_gem")
@JvmField
val GHOST_GEM = this.create("ghost_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST))
val GHOST_GEM = noSettingsItem("ghost_gem")
@JvmField
val DRAGON_GEM = this.create("dragon_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON))
val DRAGON_GEM = noSettingsItem("dragon_gem")
@JvmField
val DARK_GEM = this.create("dark_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_DARK))
val DARK_GEM = noSettingsItem("dark_gem")
@JvmField
val STEEL_GEM = this.create("steel_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL))
val STEEL_GEM = noSettingsItem("steel_gem")
@JvmField
val FAIRY_GEM = this.create("fairy_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY))
val FAIRY_GEM = noSettingsItem("fairy_gem")
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
@@ -15,12 +15,12 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
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.*
object CobblemonMemories {
val memories = mutableMapOf<String, MemoryModuleType<*>>()
@@ -59,12 +59,8 @@ object CobblemonMemories {
@JvmField
val DISABLE_WALK_TO_BERRY_BUSH = register<Boolean>("disable_walk_to_berry_bush")
@JvmField
val TIME_TRYING_TO_REACH_BERRY_BUSH = register<Int>("time_trying_to_reach_berry_bush")
@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")
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
@@ -20,8 +19,7 @@ import net.minecraft.world.inventory.MenuType
object CobblemonMenuType : PlatformRegistry<Registry<MenuType<*>>, ResourceKey<Registry<MenuType<*>>>, MenuType<*>>() {
val COOKING_POT = MenuType.register("cooking_pot_menu", ::CookingPotMenu)
val TM_MACHINE = MenuType.register("tm_machine_menu", ::TMMachineMenu)
val COOKING_POT = MenuType.register("cooking_pot", ::CookingPotMenu)
override val registry: Registry<MenuType<*>>
get() = BuiltInRegistries.MENU
@@ -1,55 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.moves.MoveSelector
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.MovesetBuilderAdapter
import com.cobblemon.mod.common.util.adapters.StringToObjectAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object CobblemonMovesetBuilders : JsonDataRegistry<MovesetBuilder> {
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderAdapter)
.registerTypeAdapter(MoveSelector::class.java, StringToObjectAdapter(MoveSelector.selectors))
.create()
override val typeToken = TypeToken.get(MovesetBuilder::class.java)
override val resourcePath = "moveset_builders"
override val id: ResourceLocation = cobblemonResource("moveset_builders")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<CobblemonMovesetBuilders>()
val movesetBuilders = mutableMapOf<ResourceLocation, MovesetBuilder>()
fun getOrThrow(id: ResourceLocation): MovesetBuilder {
return movesetBuilders[id]
?: throw IllegalArgumentException("Unknown MovesetBuilder id: $id")
}
override fun sync(player: ServerPlayer) { /* These don't sync. But what if they did? Nah probably shouldn't sync. */ }
override fun reload(data: Map<ResourceLocation, MovesetBuilder>) {
movesetBuilders.clear()
data.forEach { (id, value) -> value.id = id }
movesetBuilders.putAll(data)
observable.emit(this)
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.net.CalculateSeatPositionsHandler
import com.cobblemon.mod.common.client.net.OpenBehaviourEditorHandler
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
@@ -22,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
@@ -32,11 +34,7 @@ import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
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.SetActiveTMPacketHandler
import com.cobblemon.mod.common.client.net.gui.SetTMMachineContainerDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SynchronizeActiveTMHandler
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
@@ -46,7 +44,6 @@ import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHandler
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundSeatAssignmentPacketHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.riding.ClientboundUpdateDriverInputHandler
@@ -72,6 +69,7 @@ import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.OpenBehaviourEditorPacket
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
@@ -84,6 +82,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
@@ -91,7 +90,6 @@ 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
@@ -105,7 +103,6 @@ import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegis
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClearEvolutionsPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClientboundSeatAssignmentPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.RemoveEvolutionPacket
import com.cobblemon.mod.common.net.messages.client.settings.OpenCobblemonConfigScreenPacket
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
@@ -132,7 +129,6 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangeP
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.tms.TechnicalMachineRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
@@ -145,10 +141,7 @@ import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
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.SetActiveTMPacket
import com.cobblemon.mod.common.net.messages.client.ui.SetTMMachineContainerDataPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SynchronizeActiveTMPacket
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
@@ -165,7 +158,6 @@ 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.block.TMMachineTeachMovePacket
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
@@ -178,8 +170,10 @@ 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.ApplyPremadeNPCPacket
import com.cobblemon.mod.common.net.messages.server.npc.DeletePremadePacket
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
import com.cobblemon.mod.common.net.messages.server.npc.SavePremadeFromNPCPacket
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.SetPastureConflictPacket
@@ -196,7 +190,6 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePa
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdatePassengerPositionPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
@@ -223,7 +216,6 @@ 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.block.TMMachineTeachMoveHandler
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
@@ -237,8 +229,10 @@ 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.ApplyPremadeNPCHandler
import com.cobblemon.mod.common.net.serverhandling.npc.DeletePremadeHandler
import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
import com.cobblemon.mod.common.net.serverhandling.npc.SavePremadeFromNPCHandler
import com.cobblemon.mod.common.net.serverhandling.orientation.ServerboundUpdateOrientationHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.SetPastureConflictHandler
@@ -254,7 +248,6 @@ import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHan
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdatePassengerPositionHandler
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdateRiderRotationHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
import com.cobblemon.mod.common.net.serverhandling.starter.SelectStarterPacketHandler
@@ -342,8 +335,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideBoostsUpdatePacket.ID, RideBoostsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideStaminaUpdatePacket.ID, RideStaminaUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(AlphaUpdatePacket.ID, AlphaUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(ScaleModifierUpdatePacket.ID, ScaleModifierUpdatePacket::decode, PokemonUpdatePacketHandler()))
// Evolution start
list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler()))
@@ -377,7 +368,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PlayerInteractOptionsPacket.ID, PlayerInteractOptionsPacket::decode, PlayerInteractOptionsHandler))
list.add(PacketRegisterInfo(PokedexUIPacket.ID, PokedexUIPacket::decode, PokedexUIPacketHandler))
list.add(PacketRegisterInfo(ExpGainedDataPacket.ID, ExpGainedDataPacket::decode, ExpGainedDataPacketHandler))
list.add(PacketRegisterInfo(SynchronizeActiveTMPacket.ID, SynchronizeActiveTMPacket::decode, SynchronizeActiveTMHandler))
// Starter packets
list.add(PacketRegisterInfo(OpenStarterUIPacket.ID, OpenStarterUIPacket::decode, StarterUIPacketHandler))
@@ -443,7 +433,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
list.add(PacketRegisterInfo(TechnicalMachineRegistrySyncPacket.ID, TechnicalMachineRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PremadeNPCRegistrySyncPacket.ID, PremadeNPCRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -506,17 +496,16 @@ object CobblemonNetwork {
// Pokédex scanning
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
// Debug / cheats
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
list.add(PacketRegisterInfo(ClientboundSeatAssignmentPacket.ID, ClientboundSeatAssignmentPacket::decode, ClientboundSeatAssignmentPacketHandler))
// 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
}
@@ -597,7 +586,6 @@ object CobblemonNetwork {
// Block entity
list.add(PacketRegisterInfo(AdjustBlockEntityViewerCountPacket.ID, AdjustBlockEntityViewerCountPacket::decode, AdjustBlockEntityViewerCountHandler))
list.add(PacketRegisterInfo(TMMachineTeachMovePacket.ID, TMMachineTeachMovePacket::decode, TMMachineTeachMoveHandler))
// Move select packets
list.add(PacketRegisterInfo(MoveSelectedPacket.ID, MoveSelectedPacket::decode, MoveSelectedHandler))
@@ -620,6 +608,9 @@ object CobblemonNetwork {
// NPC packets
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
list.add(PacketRegisterInfo(SavePremadeFromNPCPacket.ID, SavePremadeFromNPCPacket::decode, SavePremadeFromNPCHandler))
list.add(PacketRegisterInfo(DeletePremadePacket.ID, DeletePremadePacket::decode, DeletePremadeHandler))
list.add(PacketRegisterInfo(ApplyPremadeNPCPacket.ID, ApplyPremadeNPCPacket::decode, ApplyPremadeNPCHandler))
// Riding packet(s)
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, ServerboundUpdateOrientationHandler))
@@ -630,26 +621,17 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRiderRotationPacket.ID, ServerboundUpdateRiderRotationPacket::decode, ServerboundUpdateRiderRotationHandler))
list.add(PacketRegisterInfo(ServerboundUpdatePassengerPositionPacket.ID, ServerboundUpdatePassengerPositionPacket::decode, ServerboundUpdatePassengerPositionHandler))
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
// TM Machine
list.add(PacketRegisterInfo(SetActiveTMPacket.ID, SetActiveTMPacket::decode, SetActiveTMPacketHandler))
list.add(PacketRegisterInfo(SetTMMachineContainerDataPacket.ID, SetTMMachineContainerDataPacket::decode, SetTMMachineContainerDataPacketHandler))
// Behaviour Packets
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
// Habitat blocks
list.add(PacketRegisterInfo(SaveHabitatBlockSettingsPacket.ID, SaveHabitatBlockSettingsPacket::decode, SaveHabitatBlockSettingsHandler))
// Debug / cheats
// Debug
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
return list
}
@@ -1,47 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.npc.PartyComposition
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object CobblemonPartyCompositions : JsonDataRegistry<PartyComposition> {
override val id: ResourceLocation = cobblemonResource("party_compositions")
override val type = PackType.SERVER_DATA
override val typeToken: TypeToken<PartyComposition> = TypeToken.get(PartyComposition::class.java)
override val resourcePath: String = "party_compositions"
override val observable = SimpleObservable<CobblemonPartyCompositions>()
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.setPrettyPrinting()
.create()
val partyCompositions = mutableMapOf<ResourceLocation, PartyComposition>()
override fun sync(player: ServerPlayer) {
// probably worth syncing which exist, yeah. the deeper details probably not. I'll get to this eventually.
}
override fun reload(data: Map<ResourceLocation, PartyComposition>) {
data.entries.forEach { it.value.id = it.key }
partyCompositions.clear()
partyCompositions.putAll(data)
observable.emit(this)
Cobblemon.LOGGER.info("Loaded ${data.size} party compositions.")
}
}
@@ -1,60 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.npc.PartyPool
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.ScriptableIntRange
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.MovesetBuilderReferenceAdapter
import com.cobblemon.mod.common.util.adapters.PokemonPropertiesAdapter
import com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object CobblemonPartyPools : JsonDataRegistry<PartyPool> {
override val id: ResourceLocation = cobblemonResource("party_pools")
override val type = PackType.SERVER_DATA
override val typeToken: TypeToken<PartyPool> = TypeToken.get(PartyPool::class.java)
override val resourcePath: String = "party_pools"
override val observable = SimpleObservable<CobblemonPartyPools>()
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, PokemonPropertiesAdapter(saveLong = false))
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(ScriptableIntRange::class.java, ScriptableIntRangeAdapter)
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.setPrettyPrinting()
.create()
val partyPools = mutableMapOf<ResourceLocation, PartyPool>()
override fun sync(player: ServerPlayer) {
// probably worth syncing. Knowing the details might help exploit the server? lot of effort for little though.
}
override fun reload(data: Map<ResourceLocation, PartyPool>) {
data.entries.forEach { it.value.id = it.key }
partyPools.clear()
partyPools.putAll(data)
observable.emit(this)
}
}
@@ -18,6 +18,7 @@ import net.minecraft.resources.ResourceKey
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.Blocks
object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Registry<PoiType>>, PoiType>() {
override val registry: Registry<PoiType> = BuiltInRegistries.POINT_OF_INTEREST_TYPE
@@ -31,9 +32,7 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
// @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))
val JUKEBOX_KEY = createKey("vanilla_jukebox", Blocks.JUKEBOX, 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 ->
@@ -37,9 +37,6 @@ object CobblemonSensors {
@JvmField
val POKEMON_DEFEND_OWNER = register("pokemon_owner_under_attack", ::DefendOwnerSensor)
@JvmField
val ALPHA_LEVEL_MATCHING = register("alpha_level_matching", ::AlphaLevelMatchingSensor)
@JvmField
val NEARBY_GROWABLE_CROPS = register("nearby_growable_crops", ::PokemonGrowableCropSensor)
@@ -49,9 +46,6 @@ object CobblemonSensors {
@JvmField
val NEARBY_FLOWER = register("nearby_flower", ::FlowerSensor)
@JvmField
val NEARBY_SWEET_BERRY_BUSH = register("nearby_sweet_berry_bush", ::SweetBerryBushSensor)
@JvmField
val POKEMON_NEARBY_WANTED_ITEM = register("pokemon_nearby_wanted_item", ::PokemonItemSensor)
@@ -108,9 +108,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val LEVELUP = this.create("gui.levelup")
@JvmField
val MOVE_LEARN = this.create("gui.move_learn")
@JvmField
val PVN_BATTLE = this.create("battle.pvn.default")
@JvmField
@@ -139,15 +136,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val MULCH_REMOVE = this.create("block.mulch.remove")
@JvmField
val MONITOR_BREAK = this.create("block.monitor.break")
@JvmField
val MONITOR_INSERT = this.create("block.monitor.insert")
@JvmField
val MONITOR_LOADING = this.create("block.monitor.loading")
@JvmField
val MONITOR_GLITCHING = this.create("block.monitor.glitching")
@JvmField
val FOSSIL_MACHINE_ACTIVATE = this.create("block.fossil_machine.activate")
@JvmField
@@ -212,28 +200,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val TUMBLESTONE_STEP = this.create("block.tumblestone.step")
@JvmField
val TM_USE = this.create("item.tm.use")
@JvmField
val TM_MACHINE_BURN_LOOP = this.create("block.tm_machine.burn_loop")
@JvmField
val TM_MACHINE_CRAFT = this.create("block.tm_machine.craft")
@JvmField
val TM_MACHINE_START = this.create("block.tm_machine.start")
@JvmField
val TM_MACHINE_OPEN = this.create("block.tm_machine.open")
@JvmField
val TM_MACHINE_CLOSE = this.create("block.tm_machine.close")
@JvmField
val TM_MACHINE_TM_INSERT = this.create("block.tm_machine.tm_insert")
@JvmField
val TM_MACHINE_TM_RETRIEVE = this.create("block.tm_machine.tm_retrieve")
@JvmField
val TM_MACHINE_ITEM_INSERT = this.create("block.tm_machine.item_insert")
@JvmField
val TM_MACHINE_ITEM_RETRIEVE = this.create("block.tm_machine.item_retrieve")
@JvmField
val EVOLUTION_STONE_BLOCK_BREAK = this.create("block.evolution_stone_block.break")
@JvmField
@@ -272,9 +238,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val BERRY_EAT_FULL = this.create("item.berry.eat.full")
@JvmField
val APRICORN_HARVEST = this.create("block.apricorn.harvest")
@JvmField
val BIG_ROOT_BREAK = this.create("block.big_root.break")
@JvmField
@@ -349,13 +312,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
)
@JvmField
val RIDE_LOOP_LEATHER_MONO = create("ride.loop.leather.mono")
val RIDE_LOOP_LEATHER = create("ride.loop.leather")
@JvmField
val RIDE_LOOP_PLUMAGE_MONO = create("ride.loop.plumage.mono")
@JvmField
val RIDE_LOOP_LEATHER_STEREO = create("ride.loop.leather.stereo")
@JvmField
val RIDE_LOOP_PLUMAGE_STEREO = create("ride.loop.plumage.stereo")
val RIDE_LOOP_PLUMAGE = create("ride.loop.plumage")
@JvmField
val MOVE_QUICKATTACK_TARGET = this.create("move.quickattack.target")
@@ -421,35 +380,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
TUMBLESTONE_STEP
)
@JvmField
val TYPE_GEM_BLOCK_BREAK = this.create("block.type_gem_block.break")
@JvmField
val TYPE_GEM_CLUSTER_BREAK = this.create("block.type_gem_cluster.break")
@JvmField
val TYPE_GEM_BLOCK_HIT = this.create("block.type_gem_block.hit")
@JvmField
val TYPE_GEM_BLOCK_PLACE = this.create("block.type_gem_block.place")
@JvmField
val TYPE_GEM_BLOCK_STEP = this.create("block.type_gem_block.step")
@JvmField
val TYPE_GEM_CLUSTER_SOUNDS = SoundType(1f, 1.1f,
TYPE_GEM_CLUSTER_BREAK,
TYPE_GEM_BLOCK_STEP,
TYPE_GEM_BLOCK_PLACE,
TYPE_GEM_BLOCK_HIT,
TYPE_GEM_BLOCK_STEP
)
@JvmField
val TYPE_GEM_BLOCK_SOUNDS = SoundType(1f, 1.1f,
TYPE_GEM_BLOCK_BREAK,
TYPE_GEM_BLOCK_STEP,
TYPE_GEM_BLOCK_PLACE,
TYPE_GEM_BLOCK_HIT,
TYPE_GEM_BLOCK_STEP
)
@JvmField
val EVOLUTION_STONE_BLOCK_SOUNDS = SoundType(1f, 1.1f,
EVOLUTION_STONE_BLOCK_BREAK,
@@ -631,31 +561,5 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
CAMPFIRE_POT_STEP
)
@JvmField
val ITEM_BLOCK_PAPER_SMALL_PLACE = this.create("block.item_block.paper_small.place")
@JvmField
val ITEM_BLOCK_PAPER_SMALL_BREAK = this.create("block.item_block.paper_small.break")
@JvmField
val ITEM_BLOCK_PAPER_SMALL_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_PAPER_SMALL_BREAK,
SoundEvents.WOOL_STEP,
ITEM_BLOCK_PAPER_SMALL_PLACE,
SoundEvents.WOOL_HIT,
SoundEvents.WOOL_FALL
)
@JvmField
val ITEM_BLOCK_PAPER_LARGE_PLACE = this.create("block.item_block.paper_large.place")
@JvmField
val ITEM_BLOCK_PAPER_LARGE_BREAK = this.create("block.item_block.paper_large.break")
@JvmField
val ITEM_BLOCK_PAPER_LARGE_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_PAPER_LARGE_BREAK,
SoundEvents.WOOL_STEP,
ITEM_BLOCK_PAPER_LARGE_PLACE,
SoundEvents.WOOL_HIT,
SoundEvents.WOOL_FALL
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
}
@@ -32,9 +32,6 @@ object CobblemonCriteria : PlatformRegistry<Registry<CriterionTrigger<*>>, Resou
@JvmField
val CATCH_SHINY_POKEMON = this.create("catch_shiny_pokemon", SimpleCriterionTrigger(CountableCriterion.CODEC))
@JvmField
val CATCH_ALPHA_POKEMON = this.create("catch_alpha_pokemon", SimpleCriterionTrigger(CountableCriterion.CODEC))
@JvmField
val EGG_COLLECT = this.create("eggs_collected", SimpleCriterionTrigger(CountableCriterion.CODEC))
@@ -81,20 +78,8 @@ object CobblemonCriteria : PlatformRegistry<Registry<CriterionTrigger<*>>, Resou
@JvmField
val PLANT_TUMBLESTONE = this.create("plant_tumblestone", SimpleCriterionTrigger(PlantTumblestoneCriterion.CODEC))
@JvmField
val PLANT_TYPE_GEM = this.create("plant_type_gem", SimpleCriterionTrigger(PlantTypeGemCriterion.CODEC))
val RIDING_STAT_BOOST = this.create("riding_stat_boost", SimpleCriterionTrigger(RidingStatBoostCriterion.CODEC))
@JvmField
val LEARN_TM = this.create("has_learn_specific_tm", SimpleCriterionTrigger(LearnTMCriterion.CODEC))
@JvmField
val LEARN_ALL_TM = this.create("has_learn_all_tm", SimpleCriterionTrigger(LearnAllTMCriterion.CODEC))
@JvmField
val LEARN_TM_COUNT = this.create("tms_learned", SimpleCriterionTrigger(CountableCriterion.CODEC))
override val registry = BuiltInRegistries.TRIGGER_TYPES
override val resourceKey = Registries.TRIGGER_TYPE
}
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.advancement.criterion
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import java.util.Optional
class LearnTMContext(val tm: ResourceLocation)
class LearnTMCriterion(
playerCtx: Optional<ContextAwarePredicate>,
val tm: String
) : SimpleCriterionCondition<LearnTMContext>(playerCtx) {
companion object {
val CODEC: Codec<LearnTMCriterion> = RecordCodecBuilder.create { it.group(
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(LearnTMCriterion::playerCtx),
Codec.STRING.optionalFieldOf("tm", "any").forGetter(LearnTMCriterion::tm)
).apply(it, ::LearnTMCriterion) }
}
override fun matches(player: ServerPlayer, context: LearnTMContext): Boolean {
return tm == "any" || context.tm == tm.asIdentifierDefaultingNamespace()
}
}
class LearnAllTMContext
class LearnAllTMCriterion(
playerCtx: Optional<ContextAwarePredicate>
) : SimpleCriterionCondition<LearnAllTMContext>(playerCtx) {
companion object {
val CODEC: Codec<LearnAllTMCriterion> = RecordCodecBuilder.create { it.group(
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(LearnAllTMCriterion::playerCtx)
).apply(it, ::LearnAllTMCriterion) }
}
override fun matches(player: ServerPlayer, context: LearnAllTMContext) = true
}
@@ -14,7 +14,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import java.util.*
import java.util.Optional
class PlantTumblestoneContext(val pos: BlockPos, val block: TumblestoneBlock)
@@ -29,6 +29,42 @@ class PlantTumblestoneCriterion(
}
override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
return context.block.canGrow(player.level().getBlockState(context.pos), context.pos, player.level())
return context.block.canGrow(context.pos, player.level())
}
}
}
///**
// * A criterion condition for when a tumblestone block is planted
// * @param id The identifier of the criterion
// * @param predicate The predicate for the criterion
// *
// * @author Aethen
// * @since 02/28/2024
// */
//class PlantTumblestoneCriterionCondition(id: Identifier, predicate: LootContextPredicate) :
// SimpleCriterionCondition<PlantTumblestoneContext>(id, predicate) {
// override fun toJson(json: JsonObject) {
// // Add properties to json if needed. None needed for this criterion
// }
//
// override fun fromJson(json: JsonObject) {
// // Parse properties from json if needed. None needed for this criterion
// }
//
// /**
// * Checks if the tumblestone block can grow at the given position
// * @param player The player that planted the tumblestone block
// * @param context The context of the criterion
// * @return True if the tumblestone block can grow at the given position, false otherwise
// */
// override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
// return context.tumbleStoneBlock.canGrow(context.pos, player.level())
// }
//}
//
///**
// * The context of the [PlantTumblestoneCriterionCondition]
// * @param pos The position of the tumblestone block
// * @param tumbleStoneBlock The tumblestone block
// */
//open class PlantTumblestoneContext(var pos: BlockPos, var tumbleStoneBlock: TumblestoneBlock)
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.advancement.criterion
import com.cobblemon.mod.common.block.TypeGemClusterBlock
import com.cobblemon.mod.common.block.TypeGemCoreBlock
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import java.util.*
class PlantTypeGemContext(val pos: BlockPos, val block: TypeGemClusterBlock)
class PlantTypeGemCriterion(
playerCtx: Optional<ContextAwarePredicate>
): SimpleCriterionCondition<PlantTypeGemContext>(playerCtx) {
companion object {
val CODEC: Codec<PlantTypeGemCriterion> = RecordCodecBuilder.create { it.group(
ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(PlantTypeGemCriterion::playerCtx)
).apply(it, ::PlantTypeGemCriterion) }
}
override fun matches(player: ServerPlayer, context: PlantTypeGemContext): Boolean {
return player.level().getBlockState(context.pos).block is TypeGemCoreBlock
}
}
@@ -48,7 +48,7 @@ class RidingStatBoostCriterion(
val max = context.pokemon.pokemon.getMaxRideBoost(rideStat).toDouble()
val min = context.pokemon.pokemon.getBaseRideStat(rideStat).toDouble()
val value = context.pokemon.getRideStat(rideStat, ridingStyle, min, max)
val isSufficient = (value >= this.statValue && this.statValue > 0.0) || !context.pokemon.pokemon.canApplyRideBoost(rideStat)
val isSufficient = (value >= this.statValue && this.statValue > 0.0) || !context.pokemon.pokemon.canAddRideBoost(rideStat)
if (isSufficient && this.rideStat == "any") {
return true
} else if (!isSufficient && this.rideStat == "all") {
@@ -0,0 +1,25 @@
package com.cobblemon.mod.common.api.ai
/**
* A classification of what a behaviour is related to, used for organisational purposes. It doesn't have any technical
* implications beyond how they are displayed to players modifying an entity's Behaviours.
*
* @author Hiroku
* @since April 25th, 2026
*/
enum class BehaviourCategory {
/** Relates to movement, reactions, general entity behaviour. */
AI,
/** Relates to how the entity appears. */
APPEARANCE,
/** Relates to the entity's interactions with players or other entities. */
INTERACTION,
/** Relates to the entity's role in gameplay. */
ROLE,
/** Relates to the entity's ability to do Pokémon battles. */
BATTLE,
/** Relates to invisible, technical configuration. */
TECHNICAL,
/** Doesn't fit other categories. */
OTHER,
}
@@ -12,13 +12,15 @@ 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.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,6 +48,9 @@ class BehaviourConfigurationContext {
val memories = mutableSetOf<MemoryModuleType<*>>()
val sensors = mutableSetOf<SensorType<*>>()
val objectives = mutableMapOf<Int, MutableList<Objective>>()
val senses = mutableSetOf<Sense>()
val onAdd = mutableListOf<ExpressionLike>()
fun getOrCreateActivity(activity: Activity): ActivityConfigurationContext {
@@ -68,6 +73,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 +109,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)
@@ -13,8 +13,8 @@ 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.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.annotations.SerializedName
@@ -23,14 +23,15 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
class CobblemonBehaviour(
@Transient
var id: ResourceLocation = cobblemonResource("a"),
val name: Component = "".asTranslated(),
val category: BehaviourCategory = BehaviourCategory.OTHER,
val description: Component = "".asTranslated(),
/** Some behaviours aren't really worth showing on the client, namely ones that are simply conditional bundles of other behaviours. */
val visible: Boolean = true,
val entityType: ResourceLocation? = null,
val configurations: List<BehaviourConfig> = mutableListOf(),
// I feel like the onAdd+onAddScript etc could be merged using some interface and a clever deserializer,
// detect if it's a ResourceLocation and failing that, Expression. The on[..]Script fields are kinda fringe though.
@SerializedName("onRemove", alternate = ["undo"])
val onRemove: ExpressionLike? = null,
val onAdd: ExpressionLike? = null,
@@ -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,52 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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,
val onMovementComplete: ((entity: LivingEntity, brain: BiggerBrain) -> Unit)? = null
): 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()
val continueFixation = tickMovementFixation() == MovementStatus.PENDING
if (!continueFixation && onMovementComplete != null) {
onMovementComplete.invoke(entity, brain)
}
return continueFixation
}
}
@@ -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,158 @@
/*
* 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.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.idleBoredomDistraction
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.ai.resolveFloat
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getMemorySafely
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* Stops movement and plays a Pokemon's dance animation, if available.
*
*/
class Dance(
val minDuration: Int,
val danceChance: Float
) : Objective {
var fixation: EntityFixation? = null
var remainingCooldownTicks = 0
override fun getDescription(entity: LivingEntity) = "Dancing"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (brain.isFixation(fixation)) {
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
} else if (brain.fixation != null) {
fixation = null
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.INACTIVE
}
// Only dance if we feel like it
val danceMemory = brain.getMemory(MemoryModuleType.DANCING) ?: false
if (!danceMemory || entity.random.nextFloat() > danceChance || entity.ticksLived < 4 * 20) {
return ObjectiveStatus.INACTIVE
}
if (!brain.distract(idleBoredomDistraction)) {
return ObjectiveStatus.BLOCKED
}
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
entity.navigation.stop()
// Become fixated
fixation = brain.fixate(
DanceFixation(
this,
null,
minDuration
)
)
return ObjectiveStatus.ACTIVE
}
class DanceFixation(
val objective: Dance,
override val movementTracker: MovementTracker?,
val minDuration: Int
) : EntityFixation, MovementTrackerFixation {
var ticksSinceStarted = 0
override fun getDescription(entity: LivingEntity) = "Dancing"
override fun tickMovementFixation() = MovementStatus.PENDING
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
super.onDistract(distraction, entity)
stopDancing(entity)
}
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
if (distraction.isIdleBoredom) {
return false
}
return true
}
fun stopDancing(entity: LivingEntity) {
val pokemon = entity as? PokemonEntity ?: return
pokemon.setBehaviourFlag(PokemonBehaviourFlag.DANCING, false)
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (ticksSinceStarted == 0) { // Makes make me want a Start() for fixations
val pokemon = entity as? PokemonEntity ?: return false
RunPosableMoLangPacket(entity.id, setOf("v.bpm = 100;")).sendToPlayersAround(entity, 64.0)
pokemon.setBehaviourFlag(PokemonBehaviourFlag.DANCING, true)
}
ticksSinceStarted++
if (ticksSinceStarted > minDuration && !entity.brain.getMemorySafely(MemoryModuleType.DANCING).orElse(false)) {
stopDancing(entity)
return false
}
return true
}
}
class Config : ObjectiveConfig {
var danceChance: ExpressionOrEntityVariable = Either.left("1/2".asExpression())
var minDuration: ExpressionOrEntityVariable = Either.left("60".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
minDuration,
danceChance
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
val minDuration = minDuration.resolveInt(behaviourConfigurationContext.runtime)
val danceChance = danceChance.resolveFloat(behaviourConfigurationContext.runtime)
return Dance(
minDuration,
danceChance)
}
}
companion object {
private const val ticksBetweenAttempts = 20
}
}
@@ -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,170 @@
/*
* 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.biggerbrain.objective.PickupItem.Companion.dropItem
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.PokemonItemSense.Companion.PICKUP_ITEMS
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.pokemon.ai.ObtainableItem
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.findMatchingEntry
import com.cobblemon.mod.common.util.getObjectList
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.core.component.DataComponents
import net.minecraft.core.particles.ItemParticleOption
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
/**
* Eat the currently held item, apply the effect, and drop if it's an undesired item
*
* @author JazzMcNade
* @since February 16th, 2026
*/
class EatHeldItem(
val cooldownTicks: Int,
val pickupItems: List<ObtainableItem>
) : Objective {
var fixation: EntityFixation? = null
var remainingCooldownTicks = cooldownTicks
override fun getDescription(entity: LivingEntity) = "Eat Held Item"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (brain.isFixation(fixation)) {
return ObjectiveStatus.FIXATED
} else if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.INACTIVE
}
val itemStack: ItemStack = entity.pokemon.heldItem()
if (canEat(itemStack, entity)) {
fixation = brain.fixate(EatHeldItemFixation(this))
remainingCooldownTicks = 120
return ObjectiveStatus.FIXATED
}
return ObjectiveStatus.INACTIVE
}
fun canEat(item: ItemStack, entity: PokemonEntity): Boolean {
return item.has(DataComponents.FOOD) || pickupItems.findMatchingEntry(entity.registryAccess(), item)?.onUseEffect != null
}
class EatHeldItemFixation(val objective: EatHeldItem) : EntityFixation {
var ticksRemaining = 40
override fun getDescription(entity: LivingEntity) = "Eating held item"
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isComfort || distraction.isDamage || distraction.isForce
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
val world = entity.level()
if (entity is PokemonEntity && entity.isAlive) {
val itemStack: ItemStack = entity.pokemon.heldItem()
val itemConfig = objective.pickupItems.findMatchingEntry(entity.registryAccess(), itemStack)
if (objective.canEat(itemStack, entity)) {
if (ticksRemaining < 0) {
var resultItemStack = itemStack.finishUsingItem(world, entity)
val configuredReturnItem = itemConfig?.returnItem
resultItemStack = if (configuredReturnItem != ItemStack.EMPTY) ItemStack(BuiltInRegistries.ITEM.get(configuredReturnItem))
else if (resultItemStack.item == itemStack.item)
ItemStack.EMPTY // Items that aren't normally edible return themselves
else
resultItemStack
entity.pokemon.swapHeldItem(resultItemStack)
val onUseEffect = itemConfig?.onUseEffect
if (onUseEffect != null) {
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
mainThreadRuntime.resolve(onUseEffect)
}
if (itemConfig != null && itemConfig.fullnessValue > 0) {
entity.pokemon.feedPokemon(itemConfig.fullnessValue, false)
}
// Check if the result item is something that should be dropped
resultItemStack = entity.pokemon.heldItem()
if ((objective.pickupItems.findMatchingEntry(entity.registryAccess(), resultItemStack)?.pickupPriority ?: 0) < 0) {
// Drop item
resultItemStack = entity.pokemon.swapHeldItem(resultItemStack)
if (!resultItemStack.isEmpty && !entity.level().isClientSide) {
dropItem(entity, resultItemStack)
}
}
return false
} else if ((itemConfig?.fullnessValue ?: 0) > 0 && entity.random.nextFloat() < 0.4f) {
ticksRemaining--
entity.playSound(entity.getEatingSound(itemStack), 1.0f, 1.0f)
world.broadcastEntityEvent(entity, 45.toByte())
spawnFoodParticles(entity, itemStack)
}
}
return true
}
return false
}
private fun spawnFoodParticles(entity: LivingEntity, itemStack: ItemStack) {
val serverLevel = entity.level() as ServerLevel
//TODO: Figure out how to this with snowstorm so we can use mouth/face/head locators
// The issue as of this writing is we don't have a clean way to utilize the item texture for the particle in snowstorm
serverLevel.sendParticles(
ItemParticleOption(ParticleTypes.ITEM, itemStack),
entity.x, entity.y + 0.5, entity.z, // particle position (centered on entity)
5, // count
0.1, 0.1, 0.1, // x, y, z offset spread
0.05 // speed
)
}
}
class Config : ObjectiveConfig {
var cooldownTicks: ExpressionOrEntityVariable = Either.left("120".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
cooldownTicks
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective? {
var pickupItems = emptyList<ObtainableItem>()
if (entity is PokemonEntity) {
pickupItems = entity.config.getObjectList<ObtainableItem>(PICKUP_ITEMS)
}
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return EatHeldItem(
cooldownTicks = cooldownTicks,
pickupItems = pickupItems
)
}
}
}
@@ -0,0 +1,244 @@
/*
* 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.api.scheduling.afterOnServer
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
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 (brain.isFixation(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)
// Handle kick starting flight for flying pokemon over water
if (entity is PokemonEntity && entity.exposedForm.behaviour.moving.fly.canFly) {
if (entity.ticksLived >= 0 && entity.navigation.isAirborne(entity.level(), entity.blockPosition())) {
// Let flyers fly in battle if they're in the air
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
afterOnServer(0.1F) {
entity.navigation.moveTo(entity.x, entity.y + 0.2, entity.z, 1.0)
}
}
}
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,508 @@
/*
* 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.CobblemonBlocks
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.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.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.config.task.PollinateFlowerTaskConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.block.SaccharineLeafBlock
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.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.level.block.BeehiveBlock
import net.minecraft.world.level.block.entity.BeehiveBlockEntity
import net.minecraft.world.phys.Vec3
/**
* Mimics the vanilla bee behavior, with added interactions with Saccharine leaves.
* Only wild bees are allowed to enter hives because of dupe risks with pasture blocks.
*
* @author JazzMcNade
* @since February 23th, 2026
*/
class GatherNectar(
val ticksBetweenAttempts: Int = 60,
val maxDistance: Expression = "14.0".asExpression(),
val speedMultiplier: Expression = "0.2".asExpression()
) : Objective {
var workerFixation: EntityFixation? = null
var shelterFixation: EntityFixation? = null
var remainingCooldownTicks = ticksBetweenAttempts
override fun getDescription(entity: LivingEntity) = "Worker Bee"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (brain.isFixation(shelterFixation)) {
// Never preempt the shelter fixation
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
} else if (!entity.biggerBrain.hasMemoryValue(CobblemonMemories.HIVE_LOCATION) && !entity.biggerBrain.hasMemoryValue(CobblemonMemories.NEARBY_SACC_LEAVES)) {
return ObjectiveStatus.INACTIVE
}
val world = entity.level()
if ((world.isRaining || world.isNight) ) {
if (!brain.distract(comfortDistraction)) {
return ObjectiveStatus.BLOCKED
}
// Return to hive or do nothing
if (entity.pokemon.isWild()) {
// Only wilds can path to hives
val hivePos = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_LOCATION)
if (hivePos != null && wantsToEnterHive(entity)) {
// Deliver to hive
if (!tryEnterHive(entity)) { // Attempt to enter the hive, if we fail, probably too far away
val hiveEntrance = getHiveEntrance(hivePos, entity)
if (hiveEntrance != null) {
// Move to hive
shelterFixation = tryMovementFixation(entity, hiveEntrance, "Sheltering in Hive", ::onHiveMovementComplete)
} else {
// Blacklist the hive
dropAndBlackListHive(entity)
}
} // no else the entity is gone and is stored in the hive
}
} else {
return ObjectiveStatus.INACTIVE
}
} else if (brain.isFixation(workerFixation)) {
// Already fixated on the work
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
} else {
// Perform nectar gathering/delivery
if (!brain.distract(idleBoredomDistraction)) {
return ObjectiveStatus.BLOCKED
}
if (entity.biggerBrain.hasMemoryValue(CobblemonMemories.HAS_NECTAR) ) {
// Deliver nectar to hive or Sacc leaves
val hivePos = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_LOCATION)
if (hivePos != null && wantsToEnterHive(entity) && entity.pokemon.isWild()) {
// Deliver to hive
if (!tryEnterHive(entity)) { // Attempt to enter the hive, if we fail, probably too far away
val hiveEntrance = getHiveEntrance(hivePos, entity)
if (hiveEntrance != null) {
// Move to hive
workerFixation = tryMovementFixation(entity, hiveEntrance, "Delivering nectar to hive", ::onHiveMovementComplete)
} else {
// Blacklist the hive
dropAndBlackListHive(entity)
}
} // no else the entity is gone and is stored in the hive
} else {
// Deliver to Saccharine Leaves
val sacLeafPos : BlockPos = entity.biggerBrain.getMemory(CobblemonMemories.NEARBY_SACC_LEAVES)
?: return ObjectiveStatus.INACTIVE
if (entity.blockPosition() != sacLeafPos) {
workerFixation = tryMovementFixation(entity, sacLeafPos, "Delivering nectar to Saccharine Leaves")
return ObjectiveStatus.ACTIVE
} else {
// Do nectar deposit fixation
workerFixation = brain.fixate(DepositNectarInSaccharineLeavesFixation())
return ObjectiveStatus.ACTIVE
}
}
} else {
// Gather Nectar
val flowerPos =
entity.biggerBrain.getMemory(CobblemonMemories.NEARBY_FLOWER) ?: return ObjectiveStatus.INACTIVE
val distance = entity.distanceTo(flowerPos)
val maxDistance = mainThreadRuntime.resolveFloat(maxDistance)
if (distance < 1.0) {
// Pollinate/Gather nectar from the flower
if (workerFixation !is PollinateFlowerFixation && brain.distract(idleBoredomDistraction)) {
workerFixation = brain.fixate(PollinateFlowerFixation())
return ObjectiveStatus.FIXATED
}
} else if (distance < maxDistance) {
// Move to the flower
if (remainingCooldownTicks <= 0 && brain.distract(idleBoredomDistraction) && !brain.hasMemoryValue(
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN
)
) {
remainingCooldownTicks = ticksBetweenAttempts
workerFixation = tryMovementFixation(entity, flowerPos, "Moving to flower")
}
}
}
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
}
return ObjectiveStatus.ACTIVE
}
/*
* This Fixation has movement, but it doesn't utilize pathfinding, and it's more about flavor than accomplishing a goal
*
*
*/
class PollinateFlowerFixation(
) : EntityFixation {
var successfulPollinationTicks = 0
var lastSoundPlayedTick = 0
var hoverPos: Vec3? = null
override fun getDescription(entity: LivingEntity) = "Pollinating Flower"
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.isDamage || distraction.isForce || distraction.isComfort || entity.brain.hasMemoryValue(
CobblemonMemories.HAS_NECTAR
)
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
hoverPos = null
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
entity.brain.setMemory(CobblemonMemories.HAS_NECTAR, true)
}
successfulPollinationTicks = 0
entity.brain.setMemoryWithExpiry(
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN, true,
PollinateFlowerTaskConfig.Companion.PATH_TO_FLOWER_COOLDOWN_TICKS
)
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (entity !is PokemonEntity) return false
val flowerPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWER).orElse(null)
if (!(flowerPos != null && Vec3.atCenterOf(flowerPos).distanceTo(entity.position()) <= 1.0)) {
onStop(entity)
return false
}
++successfulPollinationTicks
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
onStop(entity)
return false
}
if (entity.random.nextFloat() < 0.05f && successfulPollinationTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = successfulPollinationTicks
entity.playSound(SoundEvents.BEE_POLLINATE, 1.0f, 1.0f)
}
val bl = hoverPos == null || entity.position().distanceTo(hoverPos!!) <= 0.1
val bl3 = entity.random.nextInt(40) == 0
if (bl && bl3) {
// mimics the wriggling over a flower the vanilla bees do
if (hoverPos == null) {
hoverPos = flowerPos.bottomCenter.add(0.0, 0.4, 0.0)
}
hoverPos = flowerPos.bottomCenter.add(
Vec3(
(entity.random.nextDouble() * 2.0 - 1.0) * (1.0 / 5.0),
0.3 + (entity.random.nextDouble() * 2.0 - 1.0) * (1.0 / 7.0),
(entity.random.nextDouble() * 2.0 - 1.0) * (1.0 / 5.0)
)
)
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
entity.lookControl.setLookAt(flowerPos.bottomCenter.add(0.0, 0.5, 0.0))
} else if (!bl) {
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
}
entity.lookControl.setLookAt(flowerPos.bottomCenter.add(0.0, 0.5, 0.0))
return true
}
fun onStop(entity: LivingEntity) {
hoverPos = null
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
entity.brain.setMemory(CobblemonMemories.HAS_NECTAR, true)
}
successfulPollinationTicks = 0
entity.brain.setMemoryWithExpiry(
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN, true,
PATH_TO_FLOWER_COOLDOWN_TICKS
)
}
}
class DepositNectarInSaccharineLeavesFixation() : EntityFixation {
var successfulPollinationTicks = 0
var lastSoundPlayedTick = 0
var hoverPos: Vec3? = null
override fun getDescription(entity: LivingEntity) = "Depositing nectar in Saccharine Leaves"
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.isDamage || distraction.isForce || distraction.isComfort || entity.brain.hasMemoryValue(
CobblemonMemories.HAS_NECTAR
)
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
hoverPos = null
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
entity.brain.setMemory(CobblemonMemories.HAS_NECTAR, true)
}
successfulPollinationTicks = 0
entity.brain.setMemoryWithExpiry(
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN, true,
PollinateFlowerTaskConfig.Companion.PATH_TO_FLOWER_COOLDOWN_TICKS
)
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (entity !is PokemonEntity) return false
++successfulPollinationTicks
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
val blockPos = brain.getMemory(CobblemonMemories.NEARBY_SACC_LEAVES)
blockPos?.let {
val world = entity.level()
val blockState = world.getBlockState(blockPos)
if (blockState.block == CobblemonBlocks.SACCHARINE_LEAVES) {
val saccAge = blockState.getValue( SaccharineLeafBlock.AGE)
if (saccAge < SaccharineLeafBlock.MAX_AGE) {
brain.eraseMemory(CobblemonMemories.HAS_NECTAR)
val newBlockState = blockState.setValue(SaccharineLeafBlock.AGE, saccAge + 1)
world.setBlock(blockPos, newBlockState, 3)
}
}
}
return false
}
if (entity.random.nextFloat() < 0.05f && successfulPollinationTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = successfulPollinationTicks
entity.playSound(SoundEvents.BEEHIVE_DRIP, 1.0f, 1.0f)
}
val leavesPos = brain.getMemory(CobblemonMemories.NEARBY_SACC_LEAVES) ?: return false
val bl = hoverPos == null || entity.position().distanceTo(hoverPos!!) <= 0.1
val bl3 = entity.random.nextInt(40) == 0
if (bl && bl3) {
// mimics the wriggling over a flower the vanilla bees do
if (hoverPos == null) {
hoverPos = leavesPos.bottomCenter.add(0.0, 0.4, 0.0)
}
hoverPos = leavesPos.bottomCenter.add(Vec3((entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0), 0.3 + (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/7.0), (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0)))
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
entity.lookControl.setLookAt(leavesPos.bottomCenter.add(0.0,0.5,0.0))
} else if (!bl) {
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
}
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 GatherNectar(
ticksBetweenAttempts = ticksBetweenAttempts,
maxDistance = maxDistance.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
)
}
}
fun onHiveMovementComplete(entity: LivingEntity, brain: BiggerBrain) {
if (entity !is PokemonEntity) return
if(!tryEnterHive(entity)) {
dropAndBlackListHive(entity)
}
}
fun tryMovementFixation(
entity: PokemonEntity,
blockPos: BlockPos,
description : String,
onMovementComplete : ((entity: LivingEntity, brain: BiggerBrain) -> Unit)? = null,
) : EntityFixation? {
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
val movementTracker = RegularMovementTracker.attempt(
entity = entity,
walkTarget = WalkTarget(
blockPos,
speedMultiplier,
0
)
)
if (movementTracker != null) {
val fixation = entity.biggerBrain.fixate(
LookAndMoveFixation(
distractionTypes = setOf(
DistractionType.DAMAGE,
DistractionType.COMFORT,
DistractionType.FORCE
),
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(
entity,
movementTracker.walkTarget
),
movementTracker = movementTracker,
description = description,
onMovementComplete = onMovementComplete
)
)
return fixation
}
return null
}
fun getHiveEntrance(hiveLocation: BlockPos, entity: LivingEntity) : BlockPos? {
val sidesByClosest = listOf(
hiveLocation.north(),
hiveLocation.south(),
hiveLocation.east(),
hiveLocation.west()
).sortedBy { it.distSqr(entity.blockPosition()) }
val world = entity.level()
var openSide = BlockPos.ZERO
for (side in sidesByClosest) {
if (world.getBlockState(side).isAir) {
openSide = side
break
}
}
if (openSide == BlockPos.ZERO) {
if (world.getBlockState(hiveLocation.above()).isAir) {
// If the above position is also air, we can use it
openSide = hiveLocation.above() // Fallback to above if no open sides found
} else if (world.getBlockState(hiveLocation.below()).isAir) {
// If the below position is also air, we can use it
openSide = hiveLocation.below() // Fallback to below if no open sides found
} else {
// If no open sides or above/below positions are found, we cannot proceed
return null
}
}
return openSide
}
fun tryEnterHive(entity: LivingEntity): Boolean {
if (entity !is PokemonEntity || !wantsToEnterHive(entity)) {
return false
}
val hiveLocation = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_LOCATION) ?: return false
val targetVec = Vec3.atCenterOf(hiveLocation)
// if we are not close to it then end early
if (entity.distanceToSqr(targetVec) > 1.6 * 1.6) {
return false
}
val state = entity.level().getBlockState(hiveLocation)
val block = state.block
if (block is BeehiveBlock) {
val blockEntity = entity.level().getBlockEntity(hiveLocation)
if (blockEntity !is BeehiveBlockEntity) return false
if (blockEntity.isFull) {
// Erase Hive memory and give the bee a moment to locate a new hive
dropAndBlackListHive(entity)
} else {
blockEntity.addOccupant(entity)
return true
}
}
return false
}
fun wantsToEnterHive(entity: LivingEntity): Boolean {
val result = !entity.brain.checkMemory(CobblemonMemories.HIVE_COOLDOWN, MemoryStatus.VALUE_PRESENT)
&& (entity.level().isRaining || entity.level().isNight || entity.brain.getMemory(CobblemonMemories.HAS_NECTAR)
.orElse(false))
if (result) {
val blockPos = entity.brain.getMemorySafely(CobblemonMemories.HIVE_LOCATION).orElse(null)
val blockEntity = entity.level().getBlockEntity(blockPos)
return blockEntity is BeehiveBlockEntity && !blockEntity.isFireNearby
}
return false
}
fun dropAndBlackListHive(entity: PokemonEntity) {
val hivePos = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_LOCATION)
if (hivePos == null) return
val blackList : MutableList<BlockPos> = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_BLACKLIST)?.toMutableList() ?: emptyList<BlockPos>().toMutableList()
blackList.add(hivePos)
if (blackList.count() > 3) {
blackList.removeFirst()
}
entity.brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
entity.brain.setMemory(CobblemonMemories.HIVE_BLACKLIST, blackList)
}
companion object {
const val PATH_TO_FLOWER_COOLDOWN_TICKS: Long = 200
const val HAS_NECTAR_ASPECT = "has_nectar"
const val MIN_DURATION: Int = 300
const val MAX_DURATION: Int = 400
const val REQUIRED_SUCCESSFUL_POLLINATION_TICKS = 250
}
}
@@ -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,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,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.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.MovementTracker
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.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
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.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.sounds.SoundEvents
import net.minecraft.tags.FluidTags
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.level.material.FluidState
import kotlin.math.atan2
/**
* Swimming Pokemon perform a cool little jump out of the water if they're near the surface
*
*/
class JumpOutOfWater(
val ticksBetweenAttempts: Int = 60,
) : Objective {
var inWater = false
var remainingCooldownTicks = ticksBetweenAttempts
var fixation: EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Jump out of water"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (brain.isFixation(fixation)) {
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.ACTIVE
}
if (entity.random.nextInt(CHANCE) != 0) {
return ObjectiveStatus.INACTIVE
} else {
if (!brain.distract(idleBoredomDistraction)) return ObjectiveStatus.BLOCKED
val direction = entity.motionDirection
val x = direction.stepX
val z = direction.stepZ
val blockPos = entity.blockPosition()
val offsets = OFFSET_MULTIPLIERS
for (offset in offsets) {
if (!this.isWater(entity, blockPos, x, z, offset) || !this.isAirAbove(entity, blockPos, x, z, offset)) {
return ObjectiveStatus.ACTIVE
}
}
val newVelocity = entity.deltaMovement.add(direction.stepX.toDouble() * 0.6, 0.7, direction.stepZ.toDouble() * 0.6)
entity.deltaMovement = newVelocity
entity.getNavigation().stop()
fixation = brain.fixate(
fixation = JumpOutOfWaterFixation(
this,
movementTracker = null
)
)
}
return ObjectiveStatus.ACTIVE
}
class JumpOutOfWaterFixation(
val objective: JumpOutOfWater,
override val movementTracker: MovementTracker?,
) : EntityFixation, MovementTrackerFixation {
override fun getDescription(entity: LivingEntity) = "Jumping out of water"
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.isDamage || distraction.isForce
}
override fun onDistract(distraction: Distraction, entity: LivingEntity) {}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
val bl: Boolean = objective.inWater
if (!bl) {
val fluidState: FluidState = entity.level().getFluidState(entity.blockPosition())
objective.inWater = fluidState.`is`(FluidTags.WATER)
}
if (objective.inWater && !bl) {
// Transition tick from in water to out of water
entity.playSound(SoundEvents.DOLPHIN_JUMP, 1.0f, 1.0f)
}
val vec3d = entity.deltaMovement
if (vec3d.y * vec3d.y < 0.029999999329447746 && entity.xRot != 0.0f) {
entity.xRot = Mth.rotLerp(0.2f, entity.xRot, 0.0f)
} else if (vec3d.length() > 9.999999747378752E-6) {
val d = vec3d.horizontalDistance()
val e = atan2(-vec3d.y, d) * 57.2957763671875
entity.xRot = e.toFloat()
}
val d: Double = vec3d.y
return (!(d * d < 0.029999999329447746) || entity.xRot == 0.0f || !(Math.abs(entity.xRot) < 10.0f) || !entity.isInWater) && !entity.onGround()
}
}
class Config : ObjectiveConfig {
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
ticksBetweenAttempts,
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
return JumpOutOfWater(
ticksBetweenAttempts = ticksBetweenAttempts,
)
}
}
private fun isWater(entity: PokemonEntity, pos: BlockPos, offsetX: Int, offsetZ: Int, multiplier: Int): Boolean {
val blockPos = pos.offset(offsetX * multiplier, 0, offsetZ * multiplier)
return entity.level().getFluidState(blockPos).`is`(FluidTags.WATER) && !entity.level().getBlockState(blockPos)
.blocksMotion()
}
private fun isAirAbove(entity: PokemonEntity, pos: BlockPos, offsetX: Int, offsetZ: Int, multiplier: Int): Boolean {
return entity.level().getBlockState(pos.offset(offsetX * multiplier, 1, offsetZ * multiplier))
.isAir && entity.level().getBlockState(pos.offset(offsetX * multiplier, 2, offsetZ * multiplier))
.isAir
}
companion object {
private const val CHANCE = 10
private val OFFSET_MULTIPLIERS = intArrayOf(0, 1, 4, 5, 6, 7)
}
}
@@ -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,234 @@
/*
* 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.LookAndMoveFixation
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.getMemorySafely
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveDouble
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.Entity
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.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.GameRules
import kotlin.math.ceil
import kotlin.math.min
/**
* Paths to whatever item entity is marked by NEAREST_VISIBLE_WANTED_ITEM
* Upon reaching the target attempts to place it in held item slot
* held item slot if possible.
* Requires NEARBY_SWEET_BERRY_BUSH memory
*
* @author JazzMcNade
* @since February 8th, 2026
*/
class PickupItem(
val ticksBetweenAttempts: Int = 60,
val maxDistance: Expression = "14.0".asExpression(),
val speedMultiplier: Expression = "0.4".asExpression(),
val maxReach: Expression = "1.0".asExpression(),
) : Objective {
var collectItemTask: EntityFixation? = null
var remainingCooldownTicks = ticksBetweenAttempts
override fun getDescription(entity: LivingEntity) = "Pickup item"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (brain.isFixation(collectItemTask)) {
remainingCooldownTicks = ticksBetweenAttempts
return ObjectiveStatus.FIXATED
}
val itemEntity = entity.biggerBrain.getMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM) ?: return ObjectiveStatus.INACTIVE
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.ACTIVE
}
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
// See if the item can be picked up without needing to path to it
val success = tryPickupItem(entity, itemEntity)
if (!success) {
// Item is too far away, need to path to it
if (remainingCooldownTicks <= 0 && brain.distract(idleBoredomDistraction)) {
remainingCooldownTicks = ticksBetweenAttempts
val horizontalHitboxRadius = maxOf(entity.boundingBox.xsize, entity.boundingBox.zsize) / 2
val movementTracker = RegularMovementTracker.attempt(
entity = entity,
walkTarget = WalkTarget(
itemEntity,
speedMultiplier,
min(ceil(horizontalHitboxRadius).toInt() - 1, 1),
)
)
if (movementTracker != null) {
val lookingTracker = LookingTracker(entity = entity, lookTarget = movementTracker.walkTarget.target)
collectItemTask = brain.fixate(
fixation = PickupItemFixation(
objective = this,
itemEntity = itemEntity,
movementTracker = movementTracker,
lookingTracker = lookingTracker,
)
)
}
}
} else {
remainingCooldownTicks = ticksBetweenAttempts
}
return ObjectiveStatus.ACTIVE
}
fun tryPickupItem(entity: PokemonEntity, itemEntity: ItemEntity) : Boolean {
val maxReach = mainThreadRuntime.resolveDouble(maxReach) + maxOf(entity.boundingBox.xsize, entity.boundingBox.zsize) / 2
if (itemEntity.distanceTo(entity) > maxReach || !entity.hasLineOfSight(itemEntity)) {
return false
}
entity.brain.eraseMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM)
entity.take(itemEntity, 1) // This creates the effect of the item going into the entity along with the item pickup sound
val stack = entity.pokemon.swapHeldItem(itemEntity.item)
if (!stack.isEmpty && !entity.level().isClientSide) {
dropItem(entity, stack)
}
return true
}
class PickupItemFixation(
val objective: PickupItem,
val itemEntity: ItemEntity,
override val movementTracker: MovementTracker,
override val lookingTracker : LookingTracker,
): EntityFixation, MovementTrackerFixation, LookingTrackerFixation {
override fun getDescription(entity: LivingEntity) = "Moving to desired item"
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 || movementTracker.lastStatus == MovementStatus.FAILED) {
// May have failed but let's check if we're still close enoug to grab it
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
}
if (!itemEntity.isAlive || !entity.pokemon.canDropHeldItem) {
return false
}
val succeeded = objective.tryPickupItem(entity, itemEntity)
if (!succeeded && movementTracker.lastStatus == MovementStatus.FAILED) {
// Cannot reach, give up for a while
entity.brain.setMemoryWithExpiry(CobblemonMemories.DISABLE_WALK_TO_WANTED_ITEM, true, 200)
entity.brain.eraseMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM)
}
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 PickupItem(
ticksBetweenAttempts = ticksBetweenAttempts,
maxDistance = maxDistance.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
)
}
}
companion object {
fun dropItem(entity: Entity, stack: ItemStack) {
val itemEntity = ItemEntity(
entity.level(),
entity.x + entity.lookAngle.x,
entity.y + 1.0,
entity.z + entity.lookAngle.z,
stack
)
itemEntity.setPickUpDelay(40)
itemEntity.setThrower(entity)
entity.playSound(SoundEvents.FOX_SPIT, 1.0f, 1.0f) // TODO: customize this sound?
entity.level().addFreshEntity(itemEntity)
}
}
}
@@ -0,0 +1,134 @@
/*
* 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 net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import kotlin.math.pow
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.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.phys.Vec3
/**
* Randomly points toward spawn. Very easily distracted once doing so.
*
*/
class PointToSpawn(
val minDuration: Int = 20,
val maxDuration: Int = 200
) : Objective {
var fixation: EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Point towards spawn"
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
} else if (!entity.brain.checkMemory(MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED)
|| !entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT)
) {
return ObjectiveStatus.BLOCKED
}
val blockPos = Vec3.atCenterOf(entity.level().sharedSpawnPos).with(Direction.Axis.Y, entity.eyeY)
brain.setMemory(
MemoryModuleType.LOOK_TARGET,
BlockPosTracker(blockPos)
)
// Become fixated
fixation = brain.fixate(
IdleLookFixation(
this,
blockPos,
(minDuration..maxDuration).random()
)
)
return ObjectiveStatus.FIXATED
}
class IdleLookFixation(
val objective: PointToSpawn,
val targetPosition: Vec3,
var remainingTicks: Int
) : EntityFixation, LookingTrackerFixation {
override var lookingTracker: LookingTracker? = null
override fun getDescription(entity: LivingEntity): String {
return "Looking at spawn position ($remainingTicks ticks left)"
}
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return true
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (entity !is PathfinderMob) {
return false
}
if (lookingTracker == null) {
lookingTracker = LookingTracker(entity, BlockPosTracker(targetPosition))
}
val lookingTracker = lookingTracker!!
remainingTicks--
lookingTracker.tick()
return remainingTicks > 0
}
}
class Config : ObjectiveConfig {
val minDuration: ExpressionOrEntityVariable = Either.left("20".asExpression())
val maxDuration: ExpressionOrEntityVariable = Either.left("80".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
minDuration,
maxDuration
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
val minDuration = minDuration.resolveInt(behaviourConfigurationContext.runtime)
val maxDuration = maxDuration.resolveInt(behaviourConfigurationContext.runtime)
return PointToSpawn(
minDuration = minDuration,
maxDuration = maxDuration
)
}
}
}
@@ -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,159 @@
/*
* 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.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.PoiTypeTags
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiRecord
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BeehiveBlockEntity
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class BeeHiveSense(
private var cooldownTicks: Int = SCAN_INTERVAL
) : Sense {
override var ticksUntilNextExecution: Int = cooldownTicks
override val memories = setOf(
CobblemonMemories.HIVE_LOCATION,
CobblemonMemories.HIVE_BLACKLIST,
)
override fun merge(other: Sense): Boolean {
if (other is BeeHiveSense) {
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 brain = entity.brain
val currentHive = brain.getMemory(CobblemonMemories.HIVE_LOCATION).orElse(null)
if (currentHive != null) {
val state = world.getBlockState(currentHive)
if (!isValidHive(state) || !hasReachableAdjacentSide(world, currentHive)) {
brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
} else {
return // We already have a valid hive, no need to search for another
}
}
var closestHivePos: BlockPos? = null
val list = findNearbyHivesWithSpace(entity)
if (!list.isEmpty()) {
val blackList = brain.getMemory(CobblemonMemories.HIVE_BLACKLIST).orElse(emptyList())
for (blockPos in list) {
if (blackList.contains(blockPos)) continue
if (hasReachableAdjacentSide(world, blockPos)) {
closestHivePos = blockPos
break
}
}
if (closestHivePos == null) {
brain.eraseMemory(CobblemonMemories.HIVE_BLACKLIST)
closestHivePos = list.first()
}
}
if (closestHivePos != null) {
brain.setMemory(CobblemonMemories.HIVE_LOCATION, closestHivePos)
}
}
private fun isValidHive(state: BlockState): Boolean {
return isHiveBlock(state)
}
private fun isHiveBlock(state: BlockState): Boolean {
return state.`is`(Blocks.BEEHIVE) || state.`is`(Blocks.BEE_NEST)
}
private fun hasReachableAdjacentSide(world: ServerLevel, pos: BlockPos): Boolean {
for (dir in Direction.entries) {
val adjacentPos = pos.relative(dir)
if (world.isEmptyBlock(adjacentPos)) {
return true
}
}
return false
}
private fun findNearbyHivesWithSpace(entity: PokemonEntity): List<BlockPos> {
val blockPos = entity.blockPosition()
val poiManager = (entity.level() as ServerLevel).poiManager
val stream = poiManager.getInRange(
{ holder -> holder.`is`(PoiTypeTags.BEE_HOME) },
blockPos,
20,
PoiManager.Occupancy.ANY
)
return stream
.map(PoiRecord::getPos)
.filter { pos -> doesHiveHaveSpace(pos, entity.level()) }
.sorted { pos1, pos2 -> pos1.distSqr(blockPos).toInt() - pos2.distSqr(blockPos).toInt() }
.toList()
}
private fun doesHiveHaveSpace(hivePos: BlockPos, world: Level): Boolean {
val blockEntity: BlockEntity? = world.getBlockEntity(hivePos)
if (blockEntity is BeehiveBlockEntity) {
return !blockEntity.isFull
} else {
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 BeeHiveSense(
cooldownTicks = cooldownTicks
)
}
}
companion object {
const val SCAN_INTERVAL = 40
}
}
@@ -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,134 @@
/*
* 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.distanceTo
import com.cobblemon.mod.common.util.getMemorySafely
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Vec3i
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BlockTags
import net.minecraft.tags.PoiTypeTags
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiRecord
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.DoublePlantBlock
import net.minecraft.world.level.block.entity.BeehiveBlockEntity
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
class FlowerSense(
private var cooldownTicks: Int = SCAN_INTERVAL
) : Sense {
override var ticksUntilNextExecution: Int = cooldownTicks
override val memories = setOf(
CobblemonMemories.NEARBY_FLOWER,
CobblemonMemories.HAS_NECTAR,
)
override fun merge(other: Sense): Boolean {
if (other is FlowerSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
val brain = entity.brain
val world = entity.level()
// Check curr memory
val currPos = brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWER).orElse(null)
if (currPos != null && isFlower(entity.level().getBlockState(currPos)) && entity.distanceTo(currPos) <= 32) {
return
}
val searchRadius = 5
val centerPos = entity.blockPosition()
var flowerPos: BlockPos? = null
var shortestDist = Double.MAX_VALUE
BlockPos.betweenClosedStream(
centerPos.offset(-searchRadius, -2, -searchRadius),
centerPos.offset(searchRadius, 2, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (isFlower(state)) {
val distance = pos.distSqr(Vec3i(centerPos.x, centerPos.y, centerPos.z))
if (distance < shortestDist) {
flowerPos = BlockPos(pos)
shortestDist = distance
}
}
}
if (flowerPos != null) {
brain.setMemory(CobblemonMemories.NEARBY_FLOWER, flowerPos)
} else {
brain.eraseMemory(CobblemonMemories.NEARBY_FLOWER)
}
}
private fun isFlower(state: BlockState): Boolean {
// Borrowed from Bee.class
if (state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(
BlockStateProperties.WATERLOGGED
) as Boolean
) {
return false
} else if (state.`is`(BlockTags.FLOWERS)) {
if (state.`is`(Blocks.SUNFLOWER)) {
return state.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.UPPER
} else {
return true
}
} else {
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 FlowerSense(
cooldownTicks = cooldownTicks
)
}
}
companion object {
const val SCAN_INTERVAL = 40
}
}
@@ -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,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.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonPoiTypes
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.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.mojang.datafixers.util.Either
import net.minecraft.core.Holder
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.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.JukeboxBlockEntity
/**
* Senses nearby jukeboxes that are currently playing music.
*
*/
class JukeboxSense(
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.DANCING)
override fun merge(other: Sense): Boolean {
if (other is JukeboxSense) {
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 world = entity.level()
val poiManager = (entity.level() as ServerLevel).poiManager
val jukeboxPositions = poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.JUKEBOX_KEY) },
{ true },
entity.blockPosition(),
10,
PoiManager.Occupancy.ANY
).toList()
val jukeboxPos = jukeboxPositions.firstOrNull() { blockPos ->
if (world.getBlockState(blockPos).block != Blocks.JUKEBOX) return@firstOrNull false
val blockEntity = world.getBlockEntity(blockPos)
if (blockEntity !is JukeboxBlockEntity) return@firstOrNull false
!blockEntity.theItem.isEmpty
}
brain.setMemory(MemoryModuleType.DANCING, jukeboxPos != null)
}
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 JukeboxSense(
condition = condition.asSimplifiedExpression(entity),
radiusXZ = radiusXZ.resolveDouble(behaviourConfigurationContext.runtime),
radiusY = radiusY.resolveDouble(behaviourConfigurationContext.runtime),
cooldownTicks = cooldownTicks.resolveDouble(behaviourConfigurationContext.runtime).toInt()
)
}
}
}
@@ -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,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,130 @@
/*
* 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.resolveDouble
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.ai.ObtainableItem
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.findMatchingEntry
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.getObjectList
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
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.level.GameRules
import java.util.Optional
/**
* Senses nearby item entities. Returns the closest item that the Pokemon would desire to pickup.
*
* @author JazzMcNade
* @since February 15th, 2026
*/
class PokemonItemSense(
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_VISIBLE_WANTED_ITEM, CobblemonMemories.DISABLE_WALK_TO_WANTED_ITEM)
override fun merge(other: Sense): Boolean {
if (other is PokemonItemSense) {
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 (entity !is PokemonEntity || !brain.runtime.resolveBoolean(condition)) {
return
}
val world = entity.level()
if (!world.gameRules.getBoolean(GameRules.RULE_MOBGRIEFING) || !entity.pokemon.canDropHeldItem || entity.brain.getMemorySafely(
CobblemonMemories.DISABLE_WALK_TO_WANTED_ITEM
).orElse(false)
) {
// Mob griefing is disabled, the Pokemon cannot swap out its item, or it's exhausted from attempting to go to an unreachable item
// so don't bother to search
return
}
val pickupItems = entity.config.getObjectList<ObtainableItem>(PICKUP_ITEMS)
val heldItemValue =
pickupItems.findMatchingEntry(entity.registryAccess(), entity.pokemon.heldItem())?.pickupPriority ?: 0
if (heldItemValue >= (pickupItems.maxOfOrNull { it.pickupPriority } ?: 0)) {
// It's already holding the highest value item it can have, no need to look for better.
return
}
val list = world.getEntitiesOfClass(
ItemEntity::class.java,
entity.boundingBox.inflate(radiusXZ, radiusY, radiusXZ)
) {
(pickupItems.findMatchingEntry(entity.registryAccess(), it.item)?.pickupPriority ?: 0) > heldItemValue
&& entity.hasLineOfSight(it)
}
// Find the closest item to the entity
val nearestItemEntity = list.minByOrNull { it.distanceTo(entity) }
entity.getBrain()
.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearestItemEntity))
}
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 PokemonItemSense(
condition = condition.asSimplifiedExpression(entity),
radiusXZ = radiusXZ.resolveDouble(behaviourConfigurationContext.runtime),
radiusY = radiusY.resolveDouble(behaviourConfigurationContext.runtime),
cooldownTicks = cooldownTicks.resolveDouble(behaviourConfigurationContext.runtime).toInt()
)
}
}
companion object {
const val PICKUP_ITEMS = "pickup_items"
}
}
@@ -0,0 +1,131 @@
/*
* 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.block.SaccharineLeafBlock
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.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Vec3i
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.PoiTypeTags
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiRecord
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BeehiveBlockEntity
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockStateProperties
class SaccLeafSense(
private var cooldownTicks: Int = SCAN_INTERVAL
) : Sense {
override var ticksUntilNextExecution: Int = cooldownTicks
override val memories = setOf(
CobblemonMemories.NEARBY_SACC_LEAVES,
)
override fun merge(other: Sense): Boolean {
if (other is SaccLeafSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
val brain = entity.brain
val searchRadius = 7
val tooFarDistance = 30
val wayTooFarDistance = 64
val currPos: BlockPos? = brain.getMemorySafely(CobblemonMemories.NEARBY_SACC_LEAVES).orElse(null)
if (currPos != null && isValidLeafBlock(entity.level().getBlockState(currPos))) {
val distance = entity.distanceTo(currPos)
if (distance <= tooFarDistance) {
return
} else if (distance > wayTooFarDistance) {
brain.eraseMemory(CobblemonMemories.NEARBY_SACC_LEAVES)
}
}
val centerPos = entity.blockPosition()
var leavesPos: BlockPos? = null
var shortestDist = Double.MAX_VALUE
val world = entity.level()
BlockPos.betweenClosedStream(
centerPos.offset(-searchRadius, -2, -searchRadius),
centerPos.offset(searchRadius, searchRadius, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (isValidLeafBlock(state)) {
val distance = pos.distSqr(Vec3i(centerPos.x, centerPos.y, centerPos.z))
if (distance < shortestDist) {
leavesPos = BlockPos(pos)
shortestDist = distance
}
}
}
if (leavesPos != null) {
brain.setMemory(CobblemonMemories.NEARBY_SACC_LEAVES, leavesPos)
} else {
brain.eraseMemory(CobblemonMemories.NEARBY_SACC_LEAVES)
}
}
private fun isValidLeafBlock(state: BlockState): Boolean {
if (state.block !is SaccharineLeafBlock) return false
if (state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(
BlockStateProperties.WATERLOGGED
) as Boolean
) {
return false
}
return state.getValue(SaccharineLeafBlock.AGE) != SaccharineLeafBlock.MAX_AGE
}
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 SaccLeafSense(
cooldownTicks = cooldownTicks
)
}
}
companion object {
const val SCAN_INTERVAL = 40
}
}
@@ -0,0 +1,54 @@
/*
* 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.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("jukebox_playing") to JukeboxSense::class.java,
cobblemonResource("nearby_sweet_berry_bush") to SweetBerryBushSense::class.java,
cobblemonResource("pokemon_nearby_wanted_item") to PokemonItemSense.Config::class.java,
cobblemonResource("flower_sense") to FlowerSense.Config::class.java,
cobblemonResource("beehive_sense") to BeeHiveSense.Config::class.java,
cobblemonResource("sacc_leaf_sense") to SaccLeafSense.Config::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) })
}
}
@@ -38,7 +38,7 @@ class ApplyBehaviours : BehaviourConfig {
override fun preconfigure(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) {
if (!checkCondition(behaviourConfigurationContext, condition)) return
val configurations = behaviours.map { CobblemonBehaviours.behaviours[it]?.takeIf { it.canBeApplied(entity) } ?: return }
val configurations = behaviours.mapNotNull { CobblemonBehaviours.behaviours[it]?.takeIf { it.canBeApplied(entity) } }
configurations.forEach {
it.configurations.forEach { it.preconfigure(entity, behaviourConfigurationContext) }
}
@@ -50,9 +50,9 @@ class ApplyBehaviours : BehaviourConfig {
val applicableBehaviours = behaviours.mapNotNull {
val resolvedBehaviour = CobblemonBehaviours.behaviours[it]
if (resolvedBehaviour == null) {
Cobblemon.LOGGER.warn("Behaviour $it not found while configuring entity of type ${entity.type}")
Cobblemon.LOGGER.warn("Behaviour $it not found while configuring entity of type ${entity.type} at location ${entity.blockPosition()}")
} else if (!resolvedBehaviour.canBeApplied(entity)) {
Cobblemon.LOGGER.warn("Behaviour $it cannot be applied to entity ${entity.id}")
Cobblemon.LOGGER.warn("Behaviour $it is not compatible with entity ${entity.id} at location ${entity.blockPosition()}")
} else {
return@mapNotNull resolvedBehaviour
}

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